diff --git a/src/concurrent/scheduler.cr b/src/concurrent/scheduler.cr index 843650298345..b7bcb6830d97 100644 --- a/src/concurrent/scheduler.cr +++ b/src/concurrent/scheduler.cr @@ -28,15 +28,15 @@ class Scheduler end end - def self.create_fd_write_event(io : IO::FileDescriptor, edge_triggered : Bool = false) + def self.create_fd_write_event(handle : Crystal::System::FileHandle, edge_triggered : Bool = false) flags = LibEvent2::EventFlags::Write flags |= LibEvent2::EventFlags::Persist | LibEvent2::EventFlags::ET if edge_triggered - event = @@eb.new_event(io.fd, flags, io) do |s, flags, data| - fd_io = data.as(IO::FileDescriptor) + event = @@eb.new_event(handle.platform_specific, flags, handle) do |s, flags, data| + fd_handle = data.as(Crystal::System::FileHandle) if flags.includes?(LibEvent2::EventFlags::Write) - fd_io.resume_write + fd_handle.resume_write elsif flags.includes?(LibEvent2::EventFlags::Timeout) - fd_io.resume_write(timed_out: true) + fd_handle.resume_write(timed_out: true) end end event @@ -56,15 +56,15 @@ class Scheduler event end - def self.create_fd_read_event(io : IO::FileDescriptor, edge_triggered : Bool = false) + def self.create_fd_read_event(handle : Crystal::System::FileHandle, edge_triggered : Bool = false) flags = LibEvent2::EventFlags::Read flags |= LibEvent2::EventFlags::Persist | LibEvent2::EventFlags::ET if edge_triggered - event = @@eb.new_event(io.fd, flags, io) do |s, flags, data| - fd_io = data.as(IO::FileDescriptor) + event = @@eb.new_event(handle.platform_specific, flags, handle) do |s, flags, data| + fd_handle = data.as(Crystal::System::FileHandle) if flags.includes?(LibEvent2::EventFlags::Read) - fd_io.resume_read + fd_handle.resume_read elsif flags.includes?(LibEvent2::EventFlags::Timeout) - fd_io.resume_read(timed_out: true) + fd_handle.resume_read(timed_out: true) end end event diff --git a/src/crystal/system/file_handle.cr b/src/crystal/system/file_handle.cr new file mode 100644 index 000000000000..08e4d227353c --- /dev/null +++ b/src/crystal/system/file_handle.cr @@ -0,0 +1,81 @@ +class Crystal::System::FileHandle + include IO + + # Create a new `FileHandle` using the platform-specific representation of this + # handle. + # + # On unix-like platforms this constructor takes an Int32 file descriptor. + # def self.new(platform_specific) : FileHandle + + # Returns the platform-specific representation of this handle. + # + # On unix-like platforms this returns an Int32 file descriptor. + # def platform_specific + + # Reads at most `slice.size` bytes from this `FileHandle` into *slice*. + # Returns the number of bytes read. + # def read(slice : Bytes) : Int32 + + # Writes the contents of *slice* into this `FileHandle`. + # def write(slice : Bytes) : Nil + + # Used by the scheduler to call back to the `FileHandle` once a read is ready. + # def resume_read(timed_out : Bool = false) : Nil + + # Used by the scheduler to call back to the `FileHandle` once a write is + # ready. + # def resume_write(timed_out : Bool = false) : Nil + + # Returns true if this `FileHandle` has been closed. + # def closed? : Bool + + # Closes the `FileHandle`. + # def close : Nil + + # Returns true if the `FileHandle` uses blocking IO. + # def blocking? : Bool + + # Sets whether this `FileHandle` uses blocking IO. + # def blocking=(value : Bool) : Bool + + # Returns true if this `FileHandle` is closed when `Process.exec` is called. + # def close_on_exec? : Bool + + # Sets if this `FileHandle` is closed when `Process.exec` is called. + # def close_on_exec=(value : Bool) : Bool + + # Returns the time to wait when reading before raising an `IO::Timeout`. + # def read_timeout : Time::Span? + + # Sets the time to wait when reading before raising an `IO::Timeout`. + # def read_timeout=(timeout : Time::Span?) : Time::Span? + + # Returns the time to wait when writing before raising an `IO::Timeout`. + # def write_timeout : Time::Span? + + # Sets the time to wait when writing before raising an `IO::Timeout`. + # def write_timeout=(timeout : Time::Span?) : Time::Span? + + # Seeks to a given offset relative to either the beginning, current position, + # or end - depending on *whence*. Returns the new position in the file + # measured in bytes from the beginning of the file. + # def seek(offset : Number, whence : IO::Seek = IO::Seek::Set) : Int64 + + # Returns true if this `FileHandle` is a handle of a terminal device (tty). + # TODO: rename this to `terminal?` + # def tty? : Bool + + # Modifies this `FileHandle` to be a handle of the same resource as *other*. + # def reopen(other : FileHandle) : FileHandle + + # Returns a `File::Stat` object containing information about the file that + # this `FileHandle` represents. + # def stat : File::Stat + + # Implement `IO#rewind` using `seek` for all implementations. + def rewind + seek(0, IO::Seek::Set) + end +end + +require "./unix/file_handle" diff --git a/src/crystal/system/unix/file_handle.cr b/src/crystal/system/unix/file_handle.cr new file mode 100644 index 000000000000..4266c46ca701 --- /dev/null +++ b/src/crystal/system/unix/file_handle.cr @@ -0,0 +1,172 @@ +require "c/fcntl" +require "io/syscall" + +class Crystal::System::FileHandle + include IO::Syscall + + @fd : Int32 + + @read_event : Event::Event? + @write_event : Event::Event? + + @closed = false + + def initialize(platform_specific : Int32) + @fd = platform_specific + end + + def platform_specific : Int32 + @fd + end + + def read(slice : Bytes) : Int32 + read_syscall_helper(slice, "Error reading file") do + # `to_i32` is acceptable because `Slice#size` is a Int32 + LibC.read(@fd, slice, slice.size).to_i32 + end + end + + def write(slice : Bytes) : Nil + write_syscall_helper(slice, "Error writing file") do |slice| + LibC.write(@fd, slice, slice.size).tap do |return_code| + if return_code == -1 && Errno.value == Errno::EBADF + raise IO::Error.new "File not open for writing" + end + end + end + end + + def closed? : Bool + @closed + end + + def close : Nil + return if @closed + + err = nil + if LibC.close(@fd) != 0 + case Errno.value + when Errno::EINTR, Errno::EINPROGRESS + # ignore + else + err = Errno.new("Error closing file") + end + end + + @closed = true + + @read_event.try &.free + @read_event = nil + + @write_event.try &.free + @write_event = nil + + reschedule_waiting + + raise err if err + end + + def blocking? : Bool + (fcntl(LibC::F_GETFL) & LibC::O_NONBLOCK) == 0 + end + + def blocking=(value : Bool) : Bool + flags = fcntl(LibC::F_GETFL) + if value + flags &= ~LibC::O_NONBLOCK + else + flags |= LibC::O_NONBLOCK + end + fcntl(LibC::F_SETFL, flags) + + value + end + + def close_on_exec? : Bool + (fcntl(LibC::F_GETFD) & LibC::FD_CLOEXEC) == LibC::FD_CLOEXEC + end + + def close_on_exec=(value : Bool) : Bool + flags = fcntl(LibC::F_GETFD) + if value + flags |= LibC::FD_CLOEXEC + else + flags &= ~LibC::FD_CLOEXEC + end + fcntl(LibC::F_SETFD, flags) + + value + end + + def seek(offset : Number, whence : IO::Seek = IO::Seek::Set) : Int64 + check_open + + seek_value = LibC.lseek(@fd, offset.to_i64, whence) + + if seek_value == -1 + raise Errno.new "Unable to seek" + end + + seek_value.to_i64 + end + + def tty? : Bool + LibC.isatty(@fd) == 1 + end + + def reopen(other : FileHandle) : FileHandle + {% if LibC.methods.includes? "dup3".id %} + # dup doesn't copy the CLOEXEC flag, so copy it manually using dup3 + flags = other.close_on_exec? ? LibC::O_CLOEXEC : 0 + if LibC.dup3(other.platform_specific, self.platform_specific, flags) == -1 + raise Errno.new("Could not reopen file descriptor") + end + {% else %} + # dup doesn't copy the CLOEXEC flag, copy it manually to the new + if LibC.dup2(other.platform_specific, self.platform_specific) == -1 + raise Errno.new("Could not reopen file descriptor") + end + + if other.close_on_exec? + self.close_on_exec = true + end + {% end %} + + # We are now pointing to a new file descriptor, we need to re-register + # events with libevent and enqueue readers and writers again. + @read_event.try &.free + @read_event = nil + + @write_event.try &.free + @write_event = nil + + reschedule_waiting + + other + end + + def stat : File::Stat + if LibC.fstat(@fd, out stat) != 0 + raise Errno.new("Unable to get stat") + end + File::Stat.new(stat) + end + + private def fcntl(cmd, arg = 0) + LibC.fcntl(@fd, cmd, arg).tap do |ret| + raise Errno.new("fcntl() failed") if ret == -1 + end + end + + private def add_read_event(timeout = @read_timeout) + event = @read_event ||= Scheduler.create_fd_read_event(self) + event.add(timeout) + nil + end + + private def add_write_event(timeout = @write_timeout) + event = @write_event ||= Scheduler.create_fd_write_event(self) + event.add(timeout) + nil + end +end diff --git a/src/file.cr b/src/file.cr index b67b0c7c98fe..5c195edb8f5a 100644 --- a/src/file.cr +++ b/src/file.cr @@ -32,7 +32,7 @@ class File < IO::FileDescriptor @path = filename self.set_encoding(encoding, invalid: invalid) if encoding - super(fd, blocking: true) + super(Crystal::System::FileHandle.new(fd), blocking: true) end protected def open_flag(mode) @@ -620,7 +620,7 @@ class File < IO::FileDescriptor # for writing. def truncate(size = 0) flush - code = LibC.ftruncate(fd, size) + code = LibC.ftruncate(@handle.platform_specific, size) if code != 0 raise Errno.new("Error truncating file '#{path}'") end @@ -644,7 +644,7 @@ class File < IO::FileDescriptor raise ArgumentError.new("Bytesize out of bounds") end - io = PReader.new(fd, offset, bytesize) + io = PReader.new(@handle.platform_specific, offset, bytesize) yield io ensure io.close end diff --git a/src/file/flock.cr b/src/file/flock.cr index b88634251ea5..8253c6d10599 100644 --- a/src/file/flock.cr +++ b/src/file/flock.cr @@ -52,7 +52,7 @@ class File private def flock(op : LibC::FlockOp, blocking : Bool = true) op |= LibC::FlockOp::NB unless blocking - if LibC.flock(@fd, op) != 0 + if LibC.flock(@handle.platform_specific, op) != 0 raise Errno.new("flock") end diff --git a/src/io/console.cr b/src/io/console.cr index e138c61bb999..ee2eb0604ba7 100644 --- a/src/io/console.cr +++ b/src/io/console.cr @@ -21,7 +21,7 @@ class IO::FileDescriptor # This will prevent displaying back to the user what they enter on the terminal. # Only call this when this IO is a TTY, such as a not redirected stdin. def noecho! - if LibC.tcgetattr(fd, out mode) != 0 + if LibC.tcgetattr(@handle.platform_specific, out mode) != 0 raise Errno.new "can't set IO#noecho!" end noecho_from_tc_mode! @@ -29,7 +29,7 @@ class IO::FileDescriptor macro noecho_from_tc_mode! mode.c_lflag &= ~(Termios::LocalMode.flags(ECHO, ECHOE, ECHOK, ECHONL).value) - LibC.tcsetattr(fd, Termios::LineControl::TCSANOW, pointerof(mode)) + LibC.tcsetattr(@handle.platform_specific, Termios::LineControl::TCSANOW, pointerof(mode)) end # Enable character processing for the duration of the given block. @@ -50,7 +50,7 @@ class IO::FileDescriptor # the program on a newline. # Only call this when this IO is a TTY, such as a not redirected stdin. def cooked! - if LibC.tcgetattr(fd, out mode) != 0 + if LibC.tcgetattr(@handle.platform_specific, out mode) != 0 raise Errno.new "can't set IO#cooked!" end cooked_from_tc_mode! @@ -69,7 +69,7 @@ class IO::FileDescriptor Termios::LocalMode::ICANON | Termios::LocalMode::ISIG | Termios::LocalMode::IEXTEN).value - LibC.tcsetattr(fd, Termios::LineControl::TCSANOW, pointerof(mode)) + LibC.tcsetattr(@handle.platform_specific, Termios::LineControl::TCSANOW, pointerof(mode)) end # Enable raw mode for the duration of the given block. @@ -88,7 +88,7 @@ class IO::FileDescriptor # is done by the terminal. # Only call this when this IO is a TTY, such as a not redirected stdin. def raw! - if LibC.tcgetattr(fd, out mode) != 0 + if LibC.tcgetattr(@handle.platform_specific, out mode) != 0 raise Errno.new "can't set IO#raw!" end @@ -97,18 +97,18 @@ class IO::FileDescriptor macro raw_from_tc_mode! LibC.cfmakeraw(pointerof(mode)) - LibC.tcsetattr(fd, Termios::LineControl::TCSANOW, pointerof(mode)) + LibC.tcsetattr(@handle.platform_specific, Termios::LineControl::TCSANOW, pointerof(mode)) end private def preserving_tc_mode(msg) - if LibC.tcgetattr(fd, out mode) != 0 + if LibC.tcgetattr(@handle.platform_specific, out mode) != 0 raise Errno.new msg end before = mode begin yield mode ensure - LibC.tcsetattr(fd, Termios::LineControl::TCSANOW, pointerof(before)) + LibC.tcsetattr(@handle.platform_specific, Termios::LineControl::TCSANOW, pointerof(before)) end end end diff --git a/src/io/file_descriptor.cr b/src/io/file_descriptor.cr index 7f061e8b65b4..d8840d607757 100644 --- a/src/io/file_descriptor.cr +++ b/src/io/file_descriptor.cr @@ -1,96 +1,115 @@ -require "./syscall" -require "c/fcntl" +require "crystal/system/file_handle" # An `IO` over a file descriptor. class IO::FileDescriptor include IO::Buffered - include IO::Syscall - @read_event : Event::Event? - @write_event : Event::Event? + getter handle : Crystal::System::FileHandle - def initialize(@fd : Int32, blocking = false) - @closed = false + # Creates a new `IO::FileDescriptor` using the file descriptor *fd*. + # TODO: deprecate this constructor in favour of creating a `FileHandle` manually. + def self.new(fd : Int32, blocking = false) + handle = Crystal::System::FileHandle.new(fd) + new(handle, blocking) + end + # Creates a new `IO::FileDescriptor` using the file handle *handle*. + def initialize(@handle : Crystal::System::FileHandle, blocking = false) unless blocking - self.blocking = false + handle.blocking = false end end - def blocking - fcntl(LibC::F_GETFL) & LibC::O_NONBLOCK == 0 + # Returns the time to wait when reading before raising an `IO::Timeout`. + def read_timeout : Time::Span? + @handle.read_timeout end - def blocking=(value) - flags = fcntl(LibC::F_GETFL) - if value - flags &= ~LibC::O_NONBLOCK - else - flags |= LibC::O_NONBLOCK - end - fcntl(LibC::F_SETFL, flags) + # Sets the time to wait when reading before raising an `IO::Timeout`. + def read_timeout=(value : Time::Span?) : Time::Span? + @handle.read_timeout = value end - def close_on_exec? - flags = fcntl(LibC::F_GETFD) - (flags & LibC::FD_CLOEXEC) == LibC::FD_CLOEXEC + # Set the number of seconds to wait when reading before raising an `IO::Timeout`. + def read_timeout=(read_timeout : Number) : Number + self.read_timeout = read_timeout.seconds + + read_timeout end - def close_on_exec=(arg : Bool) - fcntl(LibC::F_SETFD, arg ? LibC::FD_CLOEXEC : 0) - arg + # Returns the time to wait when writing before raising an `IO::Timeout`. + def write_timeout : Time::Span? + @handle.write_timeout end - def self.fcntl(fd, cmd, arg = 0) - r = LibC.fcntl fd, cmd, arg - raise Errno.new("fcntl() failed") if r == -1 - r + # Sets the time to wait when writing before raising an `IO::Timeout`. + def write_timeout=(value : Time::Span?) : Time::Span? + @handle.write_timeout = value end - def fcntl(cmd, arg = 0) - self.class.fcntl @fd, cmd, arg + # Set the number of seconds to wait when writing before raising an `IO::Timeout`. + def write_timeout=(write_timeout : Number) : Number + self.write_timeout = write_timeout.seconds + + write_timeout end - def stat - if LibC.fstat(@fd, out stat) != 0 - raise Errno.new("Unable to get stat") - end - File::Stat.new(stat) + # Returns true if the `FileHandle` uses blocking IO. + def blocking? : Bool + @handle.blocking? + end + + # Sets whether this `FileHandle` uses blocking IO. + def blocking=(value : Bool) : Bool + @handle.blocking = value + end + + # Returns true if this `FileHandle` is closed when `Process.exec` is called. + def close_on_exec? : Bool + @handle.close_on_exec? + end + + # Sets if this `FileHandle` is closed when `Process.exec` is called. + def close_on_exec=(value : Bool) : Bool + @handle.close_on_exec = value + end + + # Returns a `File::Stat` object containing information about the file that + # this `FileHandle` represents. + def stat : File::Stat + @handle.stat end - # Seeks to a given *offset* (in bytes) according to the *whence* argument. - # Returns `self`. + # Seeks to a given offset relative to either the beginning, current position, + # or end - depending on *whence*. Returns the new position in the file + # measured in bytes from the beginning of the file. # # ``` # File.write("testfile", "abc") # # file = File.new("testfile") - # file.gets(3) # => "abc" - # file.seek(1, IO::Seek::Set) - # file.gets(2) # => "bc" - # file.seek(-1, IO::Seek::Current) - # file.gets(1) # => "c" + # file.gets(3) # => "abc" + # file.seek(1, IO::Seek::Set) # => 1 + # file.gets(2) # => "bc" + # file.seek(-1, IO::Seek::Current) # => 2 + # file.gets(1) # => "c" # ``` - def seek(offset, whence : Seek = Seek::Set) + def seek(offset : Number, whence : Seek = Seek::Set) : Int64 check_open flush offset -= @in_buffer_rem.size if whence.current? - seek_value = LibC.lseek(@fd, offset, whence) - - if seek_value == -1 - raise Errno.new "Unable to seek" - end + position = @handle.seek(offset, whence) @in_buffer_rem = Bytes.empty - self + position end # Same as `seek` but yields to the block after seeking and eventually seeks # back to the original position when the block returns. - def seek(offset, whence : Seek = Seek::Set) - original_pos = tell + def seek(offset : Number, whence : Seek = Seek::Set) : Nil + original_pos = pos begin seek(offset, whence) yield @@ -115,12 +134,7 @@ class IO::FileDescriptor # file.pos # => 2 # ``` def pos - check_open - - seek_value = LibC.lseek(@fd, 0, Seek::Current) - raise Errno.new "Unable to tell" if seek_value == -1 - - seek_value - @in_buffer_rem.size + seek(0, Seek::Current) end # Sets the current position (in bytes) in this `IO`. @@ -132,13 +146,10 @@ class IO::FileDescriptor # file.pos = 3 # file.gets_to_end # => "lo" # ``` - def pos=(value) - seek value - value - end + def pos=(value : Number) : Number + seek(value, Seek::Set) - def fd - @fd + value end def finalize @@ -147,22 +158,19 @@ class IO::FileDescriptor close rescue nil end - def closed? - @closed + # Returns true if this `IO::FileDescriptor` has been closed. + def closed? : Bool + @handle.closed? end - def tty? - LibC.isatty(fd) == 1 + # Returns true if this `IO::FileDescriptor` is a handle of a terminal device (tty). + # TODO: rename this to `terminal?` + def tty? : Bool + @handle.tty? end - def reopen(other : IO::FileDescriptor) - if LibC.dup2(other.fd, self.fd) == -1 - raise Errno.new("Could not reopen file descriptor") - end - - # flag is lost after dup - self.close_on_exec = true - + def reopen(other : IO::FileDescriptor) : IO::FileDescriptor + @handle.reopen(other.handle) other end @@ -171,7 +179,7 @@ class IO::FileDescriptor if closed? io << "(closed)" else - io << " fd=" << @fd + io << " fd=" << @handle.platform_specific end io << ">" io @@ -182,62 +190,19 @@ class IO::FileDescriptor end private def unbuffered_read(slice : Bytes) - read_syscall_helper(slice, "Error reading file") do - # `to_i32` is acceptable because `Slice#size` is a Int32 - LibC.read(@fd, slice, slice.size).to_i32 - end + @handle.read(slice) end private def unbuffered_write(slice : Bytes) - write_syscall_helper(slice, "Error writing file") do |slice| - LibC.write(@fd, slice, slice.size).tap do |return_code| - if return_code == -1 && Errno.value == Errno::EBADF - raise IO::Error.new "File not open for writing" - end - end - end - end - - private def add_read_event(timeout = @read_timeout) - event = @read_event ||= Scheduler.create_fd_read_event(self) - event.add timeout - nil - end - - private def add_write_event(timeout = @write_timeout) - event = @write_event ||= Scheduler.create_fd_write_event(self) - event.add timeout - nil + @handle.write(slice) end private def unbuffered_rewind - seek(0, IO::Seek::Set) - self + @handle.rewind end private def unbuffered_close - return if @closed - - err = nil - if LibC.close(@fd) != 0 - case Errno.value - when Errno::EINTR, Errno::EINPROGRESS - # ignore - else - err = Errno.new("Error closing file") - end - end - - @closed = true - - @read_event.try &.free - @read_event = nil - @write_event.try &.free - @write_event = nil - - reschedule_waiting - - raise err if err + @handle.close end private def unbuffered_flush diff --git a/src/lib_c/aarch64-linux-gnu/c/unistd.cr b/src/lib_c/aarch64-linux-gnu/c/unistd.cr index 7789c246d9f3..be1adf20da39 100644 --- a/src/lib_c/aarch64-linux-gnu/c/unistd.cr +++ b/src/lib_c/aarch64-linux-gnu/c/unistd.cr @@ -14,6 +14,7 @@ lib LibC fun chown(file : Char*, owner : UidT, group : GidT) : Int fun close(fd : Int) : Int fun dup2(fd : Int, fd2 : Int) : Int + fun dup3(fd : Int, fd2 : Int, flags : Int) : Int fun _exit(status : Int) : NoReturn fun execvp(file : Char*, argv : Char**) : Int @[ReturnsTwice] diff --git a/src/lib_c/amd64-unknown-openbsd/c/unistd.cr b/src/lib_c/amd64-unknown-openbsd/c/unistd.cr index b01f56b68ab7..dd48b25f7163 100644 --- a/src/lib_c/amd64-unknown-openbsd/c/unistd.cr +++ b/src/lib_c/amd64-unknown-openbsd/c/unistd.cr @@ -13,6 +13,7 @@ lib LibC fun chown(x0 : Char*, x1 : UidT, x2 : GidT) : Int fun close(x0 : Int) : Int fun dup2(x0 : Int, x1 : Int) : Int + fun dup3(fd : Int, fd2 : Int, flags : Int) : Int fun _exit(x0 : Int) : NoReturn fun execvp(x0 : Char*, x1 : Char**) : Int @[ReturnsTwice] diff --git a/src/lib_c/arm-linux-gnueabihf/c/unistd.cr b/src/lib_c/arm-linux-gnueabihf/c/unistd.cr index 092672bbd211..c28eeb876e4a 100644 --- a/src/lib_c/arm-linux-gnueabihf/c/unistd.cr +++ b/src/lib_c/arm-linux-gnueabihf/c/unistd.cr @@ -14,6 +14,7 @@ lib LibC fun chown(file : Char*, owner : UidT, group : GidT) : Int fun close(fd : Int) : Int fun dup2(fd : Int, fd2 : Int) : Int + fun dup3(fd : Int, fd2 : Int, flags : Int) : Int fun _exit(status : Int) : NoReturn fun execvp(file : Char*, argv : Char**) : Int @[ReturnsTwice] diff --git a/src/lib_c/i686-linux-gnu/c/unistd.cr b/src/lib_c/i686-linux-gnu/c/unistd.cr index 7789c246d9f3..be1adf20da39 100644 --- a/src/lib_c/i686-linux-gnu/c/unistd.cr +++ b/src/lib_c/i686-linux-gnu/c/unistd.cr @@ -14,6 +14,7 @@ lib LibC fun chown(file : Char*, owner : UidT, group : GidT) : Int fun close(fd : Int) : Int fun dup2(fd : Int, fd2 : Int) : Int + fun dup3(fd : Int, fd2 : Int, flags : Int) : Int fun _exit(status : Int) : NoReturn fun execvp(file : Char*, argv : Char**) : Int @[ReturnsTwice] diff --git a/src/lib_c/i686-linux-musl/c/unistd.cr b/src/lib_c/i686-linux-musl/c/unistd.cr index 274c5f9b3127..d1b8bd4c3ff4 100644 --- a/src/lib_c/i686-linux-musl/c/unistd.cr +++ b/src/lib_c/i686-linux-musl/c/unistd.cr @@ -14,6 +14,7 @@ lib LibC fun chown(x0 : Char*, x1 : UidT, x2 : GidT) : Int fun close(x0 : Int) : Int fun dup2(x0 : Int, x1 : Int) : Int + fun dup3(fd : Int, fd2 : Int, flags : Int) : Int fun _exit(x0 : Int) : NoReturn fun execvp(x0 : Char*, x1 : Char**) : Int @[ReturnsTwice] diff --git a/src/lib_c/x86_64-linux-gnu/c/unistd.cr b/src/lib_c/x86_64-linux-gnu/c/unistd.cr index 7789c246d9f3..be1adf20da39 100644 --- a/src/lib_c/x86_64-linux-gnu/c/unistd.cr +++ b/src/lib_c/x86_64-linux-gnu/c/unistd.cr @@ -14,6 +14,7 @@ lib LibC fun chown(file : Char*, owner : UidT, group : GidT) : Int fun close(fd : Int) : Int fun dup2(fd : Int, fd2 : Int) : Int + fun dup3(fd : Int, fd2 : Int, flags : Int) : Int fun _exit(status : Int) : NoReturn fun execvp(file : Char*, argv : Char**) : Int @[ReturnsTwice] diff --git a/src/lib_c/x86_64-linux-musl/c/unistd.cr b/src/lib_c/x86_64-linux-musl/c/unistd.cr index 274c5f9b3127..d1b8bd4c3ff4 100644 --- a/src/lib_c/x86_64-linux-musl/c/unistd.cr +++ b/src/lib_c/x86_64-linux-musl/c/unistd.cr @@ -14,6 +14,7 @@ lib LibC fun chown(x0 : Char*, x1 : UidT, x2 : GidT) : Int fun close(x0 : Int) : Int fun dup2(x0 : Int, x1 : Int) : Int + fun dup3(fd : Int, fd2 : Int, flags : Int) : Int fun _exit(x0 : Int) : NoReturn fun execvp(x0 : Char*, x1 : Char**) : Int @[ReturnsTwice] diff --git a/src/lib_c/x86_64-portbld-freebsd/c/unistd.cr b/src/lib_c/x86_64-portbld-freebsd/c/unistd.cr index def065442a82..aff6504d1ba6 100644 --- a/src/lib_c/x86_64-portbld-freebsd/c/unistd.cr +++ b/src/lib_c/x86_64-portbld-freebsd/c/unistd.cr @@ -13,6 +13,7 @@ lib LibC fun chown(x0 : Char*, x1 : UidT, x2 : GidT) : Int fun close(x0 : Int) : Int fun dup2(x0 : Int, x1 : Int) : Int + fun dup3(fd : Int, fd2 : Int, flags : Int) : Int fun _exit(x0 : Int) : NoReturn fun execvp(x0 : Char*, x1 : Char**) : Int @[ReturnsTwice] diff --git a/src/tempfile.cr b/src/tempfile.cr index 6278eb9625fa..0a481a0afa30 100644 --- a/src/tempfile.cr +++ b/src/tempfile.cr @@ -35,12 +35,15 @@ class Tempfile < IO::FileDescriptor # Creates a `Tempfile` with the given filename. def initialize(name) tmpdir = self.class.dirname + File::SEPARATOR + + # mkstemp modifies @path to replace XXXXXX @path = "#{tmpdir}#{name}.XXXXXX" - fileno = LibC.mkstemp(@path) - if fileno == -1 + fd = LibC.mkstemp(@path) + if fd == -1 raise Errno.new("mkstemp") end - super(fileno, blocking: true) + + super(Crystal::System::FileHandle.new(fd), blocking: true) end # Retrieves the full path of a this tempfile.