diff --git a/cl/import.go b/cl/import.go index 031b1b46..5d9b8b25 100644 --- a/cl/import.go +++ b/cl/import.go @@ -532,7 +532,7 @@ func ignoreName(name string) bool { func supportedInternal(name string) bool { return strings.HasPrefix(name, "abi.") || strings.HasPrefix(name, "bytealg.") || strings.HasPrefix(name, "oserror.") || strings.HasPrefix(name, "reflectlite.") || - strings.HasPrefix(name, "syscall/execenv.") + strings.HasPrefix(name, "syscall/unix.") || strings.HasPrefix(name, "syscall/execenv.") } // ----------------------------------------------------------------------------- diff --git a/internal/build/build.go b/internal/build/build.go index 1f8e26d3..0d2165e5 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -753,6 +753,7 @@ var hasAltPkg = map[string]none{ "internal/oserror": {}, "internal/reflectlite": {}, "internal/syscall/execenv": {}, + "internal/syscall/unix": {}, "math": {}, "math/cmplx": {}, "reflect": {}, diff --git a/internal/lib/internal/syscall/unix/nonblocking_js.go b/internal/lib/internal/syscall/unix/nonblocking_js.go new file mode 100644 index 00000000..cfe78c58 --- /dev/null +++ b/internal/lib/internal/syscall/unix/nonblocking_js.go @@ -0,0 +1,15 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build js && wasm + +package unix + +func IsNonblock(fd int) (nonblocking bool, err error) { + return false, nil +} + +func HasNonblockFlag(flag int) bool { + return false +} diff --git a/internal/lib/internal/syscall/unix/nonblocking_unix.go b/internal/lib/internal/syscall/unix/nonblocking_unix.go new file mode 100644 index 00000000..10f43d56 --- /dev/null +++ b/internal/lib/internal/syscall/unix/nonblocking_unix.go @@ -0,0 +1,23 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build unix + +package unix + +import "github.com/goplus/llgo/c/syscall" + +/* TODO(xsw): +func IsNonblock(fd int) (nonblocking bool, err error) { + flag, e1 := Fcntl(fd, syscall.F_GETFL, 0) + if e1 != nil { + return false, e1 + } + return flag&syscall.O_NONBLOCK != 0, nil +} +*/ + +func HasNonblockFlag(flag int) bool { + return flag&syscall.O_NONBLOCK != 0 +} diff --git a/internal/lib/internal/syscall/unix/nonblocking_wasip1.go b/internal/lib/internal/syscall/unix/nonblocking_wasip1.go new file mode 100644 index 00000000..8f808207 --- /dev/null +++ b/internal/lib/internal/syscall/unix/nonblocking_wasip1.go @@ -0,0 +1,37 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build wasip1 + +package unix + +import "github.com/goplus/llgo/c/syscall" + +/* TODO(xsw): +import ( + "syscall" + _ "unsafe" // for go:linkname +) + +func IsNonblock(fd int) (nonblocking bool, err error) { + flags, e1 := fd_fdstat_get_flags(fd) + if e1 != nil { + return false, e1 + } + return flags&syscall.FDFLAG_NONBLOCK != 0, nil +} +*/ + +func HasNonblockFlag(flag int) bool { + return flag&syscall.FDFLAG_NONBLOCK != 0 +} + +/* TODO(xsw): +// This helper is implemented in the syscall package. It means we don't have +// to redefine the fd_fdstat_get host import or the fdstat struct it +// populates. +// +//-go:linkname fd_fdstat_get_flags syscall.fd_fdstat_get_flags +func fd_fdstat_get_flags(fd int) (uint32, error) +*/ diff --git a/internal/lib/internal/syscall/unix/unix.go b/internal/lib/internal/syscall/unix/unix.go new file mode 100644 index 00000000..8b0cffd8 --- /dev/null +++ b/internal/lib/internal/syscall/unix/unix.go @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package unix + +// llgo:skipall +import ( + _ "unsafe" +) diff --git a/internal/lib/os/file.go b/internal/lib/os/file.go index a48f759b..5b309661 100644 --- a/internal/lib/os/file.go +++ b/internal/lib/os/file.go @@ -208,7 +208,6 @@ func (f *File) WriteString(s string) (n int, err error) { panic("todo: os.(*File).WriteString") } -/* // setStickyBit adds ModeSticky to the permission bits of path, non atomic. func setStickyBit(name string) error { fi, err := Stat(name) @@ -217,7 +216,6 @@ func setStickyBit(name string) error { } return Chmod(name, fi.Mode()|ModeSticky) } -*/ // Open opens the named file for reading. If successful, methods on // the returned file can be used for reading; the associated file diff --git a/internal/lib/os/file_posix.go b/internal/lib/os/file_posix.go index 7877a6aa..03f1806c 100644 --- a/internal/lib/os/file_posix.go +++ b/internal/lib/os/file_posix.go @@ -48,7 +48,6 @@ func (f *File) pwrite(b []byte, off int64) (n int, err error) { panic("todo: os.(*File).pwrite") } -/* // syscallMode returns the syscall-specific mode bits from Go's portable mode bits. func syscallMode(i FileMode) (o uint32) { o |= uint32(i.Perm()) @@ -65,6 +64,7 @@ func syscallMode(i FileMode) (o uint32) { return } +/* TODO(xsw): // See docs in file.go:Chmod. func chmod(name string, mode FileMode) error { longName := fixLongPath(name) diff --git a/internal/lib/os/file_unix.go b/internal/lib/os/file_unix.go index fe390562..918890a0 100644 --- a/internal/lib/os/file_unix.go +++ b/internal/lib/os/file_unix.go @@ -8,6 +8,9 @@ package os import ( "runtime" + "syscall" + + "github.com/goplus/llgo/internal/lib/internal/syscall/unix" ) // Fd returns the integer Unix file descriptor referencing the open file. @@ -30,15 +33,6 @@ func (f *File) Fd() uintptr { return f.fd } -// NewFile returns a new File with the given file descriptor and -// name. The returned value will be nil if fd is not a valid file -// descriptor. On Unix systems, if the file descriptor is in -// non-blocking mode, NewFile will attempt to return a pollable File -// (one for which the SetDeadline methods work). -// -// After passing it to NewFile, fd may become invalid under the same -// conditions described in the comments of the Fd method, and the same -// constraints apply. func NewFile(fd uintptr, name string) *File { return &File{fd: fd, name: name} } @@ -113,6 +107,83 @@ const ( kindNoPoll ) +// newFile is like NewFile, but if called from OpenFile or Pipe +// (as passed in the kind parameter) it tries to add the file to +// the runtime poller. +func newFile(fd int, name string, kind newFileKind) *File { + f := &File{ + fd: uintptr(fd), + name: name, + stdoutOrErr: fd == 1 || fd == 2, + } + + /* TODO(xsw): + pollable := kind == kindOpenFile || kind == kindPipe || kind == kindNonBlock + + // If the caller passed a non-blocking filedes (kindNonBlock), + // we assume they know what they are doing so we allow it to be + // used with kqueue. + if kind == kindOpenFile { + switch runtime.GOOS { + case "darwin", "ios", "dragonfly", "freebsd", "netbsd", "openbsd": + var st syscall.Stat_t + err := ignoringEINTR(func() error { + return syscall.Fstat(fd, &st) + }) + typ := st.Mode & syscall.S_IFMT + // Don't try to use kqueue with regular files on *BSDs. + // On FreeBSD a regular file is always + // reported as ready for writing. + // On Dragonfly, NetBSD and OpenBSD the fd is signaled + // only once as ready (both read and write). + // Issue 19093. + // Also don't add directories to the netpoller. + if err == nil && (typ == syscall.S_IFREG || typ == syscall.S_IFDIR) { + pollable = false + } + + // In addition to the behavior described above for regular files, + // on Darwin, kqueue does not work properly with fifos: + // closing the last writer does not cause a kqueue event + // for any readers. See issue #24164. + if (runtime.GOOS == "darwin" || runtime.GOOS == "ios") && typ == syscall.S_IFIFO { + pollable = false + } + } + } + + clearNonBlock := false + if pollable { + if kind == kindNonBlock { + // The descriptor is already in non-blocking mode. + // We only set f.nonblock if we put the file into + // non-blocking mode. + } else if err := syscall.SetNonblock(fd, true); err == nil { + f.nonblock = true + clearNonBlock = true + } else { + pollable = false + } + } + + // An error here indicates a failure to register + // with the netpoll system. That can happen for + // a file descriptor that is not supported by + // epoll/kqueue; for example, disk files on + // Linux systems. We assume that any real error + // will show up in later I/O. + // We do restore the blocking behavior if it was set by us. + if pollErr := f.pfd.Init("file", pollable); pollErr != nil && clearNonBlock { + if err := syscall.SetNonblock(fd, false); err == nil { + f.nonblock = false + } + } + + runtime.SetFinalizer(f.file, (*file).close) + */ + return f +} + // TODO(xsw): // func sigpipe() // implemented in package runtime @@ -135,7 +206,48 @@ const DevNull = "/dev/null" // openFileNolog is the Unix implementation of OpenFile. // Changes here should be reflected in openFdAt, if relevant. func openFileNolog(name string, flag int, perm FileMode) (*File, error) { - panic("todo: os.openFileNolog") + setSticky := false + if !supportsCreateWithStickyBit && flag&O_CREATE != 0 && perm&ModeSticky != 0 { + if _, err := Stat(name); IsNotExist(err) { + setSticky = true + } + } + + var r int + for { + var e error + r, e = syscall.Open(name, flag|syscall.O_CLOEXEC, syscallMode(perm)) + if e == nil { + break + } + + // We have to check EINTR here, per issues 11180 and 39237. + if e == syscall.EINTR { + continue + } + + return nil, &PathError{Op: "open", Path: name, Err: e} + } + + // open(2) itself won't handle the sticky bit on *BSD and Solaris + if setSticky { + setStickyBit(name) + } + + // There's a race here with fork/exec, which we are + // content to live with. See ../syscall/exec_unix.go. + if !supportsCloseOnExec { + syscall.CloseOnExec(r) + } + + kind := kindOpenFile + if unix.HasNonblockFlag(flag) { + kind = kindNonBlock + panic("todo: os.openFileNolog: unix.HasNonblockFlag") + } + + f := newFile(r, name, kind) + return f, nil } func tempDir() string { diff --git a/internal/lib/os/sticky_bsd.go b/internal/lib/os/sticky_bsd.go new file mode 100644 index 00000000..a6d93395 --- /dev/null +++ b/internal/lib/os/sticky_bsd.go @@ -0,0 +1,11 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build aix || darwin || dragonfly || freebsd || (js && wasm) || netbsd || openbsd || solaris || wasip1 + +package os + +// According to sticky(8), neither open(2) nor mkdir(2) will create +// a file with the sticky bit set. +const supportsCreateWithStickyBit = false diff --git a/internal/lib/os/sticky_nonbsd.go b/internal/lib/os/sticky_nonbsd.go new file mode 100644 index 00000000..1d289b0f --- /dev/null +++ b/internal/lib/os/sticky_nonbsd.go @@ -0,0 +1,9 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !aix && !darwin && !dragonfly && !freebsd && !js && !netbsd && !openbsd && !solaris && !wasip1 + +package os + +const supportsCreateWithStickyBit = true diff --git a/internal/lib/os/sys_js.go b/internal/lib/os/sys_js.go new file mode 100644 index 00000000..4fd0e2d7 --- /dev/null +++ b/internal/lib/os/sys_js.go @@ -0,0 +1,11 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build js && wasm + +package os + +// supportsCloseOnExec reports whether the platform supports the +// O_CLOEXEC flag. +const supportsCloseOnExec = false diff --git a/internal/lib/os/sys_unix.go b/internal/lib/os/sys_unix.go new file mode 100644 index 00000000..79005c2c --- /dev/null +++ b/internal/lib/os/sys_unix.go @@ -0,0 +1,14 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build unix + +package os + +// supportsCloseOnExec reports whether the platform supports the +// O_CLOEXEC flag. +// On Darwin, the O_CLOEXEC flag was introduced in OS X 10.7 (Darwin 11.0.0). +// See https://support.apple.com/kb/HT1633. +// On FreeBSD, the O_CLOEXEC flag was introduced in version 8.3. +const supportsCloseOnExec = true diff --git a/internal/lib/os/sys_wasip1.go b/internal/lib/os/sys_wasip1.go new file mode 100644 index 00000000..5a29aa53 --- /dev/null +++ b/internal/lib/os/sys_wasip1.go @@ -0,0 +1,11 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build wasip1 + +package os + +// supportsCloseOnExec reports whether the platform supports the +// O_CLOEXEC flag. +const supportsCloseOnExec = false diff --git a/internal/lib/os/types.go b/internal/lib/os/types.go index 40a66ae4..af840c98 100644 --- a/internal/lib/os/types.go +++ b/internal/lib/os/types.go @@ -19,10 +19,11 @@ func Getpagesize() int { return syscall.Getpagesize() } // File represents an open file descriptor. type File struct { - fd uintptr - name string - appendMode bool - nonblock bool + fd uintptr + name string + appendMode bool + nonblock bool + stdoutOrErr bool } // write writes len(b) bytes to the File.