/* * 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 os // llgo:skipall import ( "errors" "runtime" _ "unsafe" "github.com/goplus/llgo/c" "github.com/goplus/llgo/c/os" ) const ( LLGoPackage = true ) // LinkError records an error during a link or symlink or rename // system call and the paths that caused it. type LinkError struct { Op string Old string New string Err error } func (e *LinkError) Error() string { return e.Op + " " + e.Old + " " + e.New + ": " + e.Err.Error() } func (e *LinkError) Unwrap() error { return e.Err } func toMode(mode FileMode) os.ModeT { panic("todo: toMode") } func toSyscallErr(errno c.Int) error { panic("todo: toSyscallErr") } func toPathErr(op, path string, errno c.Int) error { return &PathError{Op: op, Path: path, Err: toSyscallErr(errno)} } func Chdir(dir string) error { ret := os.Chdir(c.AllocaCStr(dir)) if ret == 0 { return nil } return toPathErr("chdir", dir, ret) } /* TODO(xsw): // Chdir changes the current working directory to the named directory. // If there is an error, it will be of type *PathError. func Chdir(dir string) error { if e := syscall.Chdir(dir); e != nil { testlog.Open(dir) // observe likely non-existent directory return &PathError{Op: "chdir", Path: dir, Err: e} } if log := testlog.Logger(); log != nil { wd, err := Getwd() if err == nil { log.Chdir(wd) } } return nil } */ func Chmod(name string, mode FileMode) error { ret := os.Chmod(c.AllocaCStr(name), toMode(mode)) if ret == 0 { return nil } return toPathErr("chmod", name, ret) } /* TODO(xsw): // Chmod changes the mode of the named file to mode. // If the file is a symbolic link, it changes the mode of the link's target. // If there is an error, it will be of type *PathError. // // A different subset of the mode bits are used, depending on the // operating system. // // On Unix, the mode's permission bits, ModeSetuid, ModeSetgid, and // ModeSticky are used. // // On Windows, only the 0200 bit (owner writable) of mode is used; it // controls whether the file's read-only attribute is set or cleared. // The other bits are currently unused. For compatibility with Go 1.12 // and earlier, use a non-zero mode. Use mode 0400 for a read-only // file and 0600 for a readable+writable file. // // On Plan 9, the mode's permission bits, ModeAppend, ModeExclusive, // and ModeTemporary are used. func Chmod(name string, mode FileMode) error { return chmod(name, mode) } */ func Chown(name string, uid, gid int) error { ret := os.Chown(c.AllocaCStr(name), os.UidT(uid), os.GidT(gid)) if ret == 0 { return nil } return toPathErr("chown", name, ret) } // TODO(xsw): // func Chtimes(name string, atime time.Time, mtime time.Time) error //go:linkname Clearenv C.clearenv func Clearenv() // TODO(xsw): // func DirFS(dir string) fs.FS // func Environ() []string // func Executable() (string, error) func Exit(code int) { os.Exit(c.Int(code)) } // TODO(xsw): // func Expand(s string, mapping func(string) string) string // func ExpandEnv(s string) string func Getegid() int { return int(os.Getegid()) } func Getenv(key string) string { return c.GoString(os.Getenv(c.AllocaCStr(key))) } func Geteuid() int { return int(os.Geteuid()) } func Getgid() int { return int(os.Getgid()) } // TODO(xsw): // func Getgroups() ([]int, error) // func Getpagesize() int func Getpid() int { return int(os.Getpid()) } func Getppid() int { return int(os.Getppid()) } func Getuid() int { return int(os.Getuid()) } func Getwd() (dir string, err error) { wd := os.Getcwd(c.Alloca(os.PATH_MAX), os.PATH_MAX) if wd != nil { return c.GoString(wd), nil } return "", toSyscallErr(os.Errno) } // TODO(xsw): // func Hostname() (name string, err error) // func IsExist(err error) bool // func IsNotExist(err error) bool // func IsPathSeparator(c uint8) bool // func IsPermission(err error) bool // func IsTimeout(err error) bool func Lchown(name string, uid, gid int) error { ret := os.Lchown(c.AllocaCStr(name), os.UidT(uid), os.GidT(gid)) if ret == 0 { return nil } return toPathErr("lchown", name, ret) } func Link(oldname, newname string) error { ret := os.Link(c.AllocaCStr(oldname), c.AllocaCStr(newname)) if ret == 0 { return nil } return &LinkError{"link", oldname, newname, toSyscallErr(ret)} } // TODO(xsw): // func LookupEnv(key string) (string, bool) func Mkdir(name string, perm FileMode) error { ret := os.Mkdir(c.AllocaCStr(name), toMode(perm)) if ret == 0 { return nil } return toPathErr("mkdir", name, ret) } /* TODO(xsw): // Mkdir creates a new directory with the specified name and permission // bits (before umask). // If there is an error, it will be of type *PathError. func Mkdir(name string, perm FileMode) error { longName := fixLongPath(name) e := ignoringEINTR(func() error { return syscall.Mkdir(longName, syscallMode(perm)) }) if e != nil { return &PathError{Op: "mkdir", Path: name, Err: e} } // mkdir(2) itself won't handle the sticky bit on *BSD and Solaris if !supportsCreateWithStickyBit && perm&ModeSticky != 0 { e = setStickyBit(name) if e != nil { Remove(name) return e } } return nil } */ // TODO(xsw): // func MkdirAll(path string, perm FileMode) error // func MkdirTemp(dir, pattern string) (string, error) // func NewSyscallError(syscall string, err error) error // func Pipe() (r *File, w *File, err error) // func ReadFile(name string) ([]byte, error) func Readlink(name string) (string, error) { ptr := c.Alloca(os.PATH_MAX) ret := os.Readlink(c.AllocaCStr(name), ptr, os.PATH_MAX) if ret < os.PATH_MAX { return c.GoString((*c.Char)(ptr), ret), nil } panic("todo: buffer too small") } func Remove(name string) error { ret := os.Remove(c.AllocaCStr(name)) if ret == 0 { return nil } return toPathErr("remove", name, ret) } // TODO(xsw): // func RemoveAll(path string) error func Rename(oldpath, newpath string) error { ret := os.Rename(c.AllocaCStr(oldpath), c.AllocaCStr(newpath)) if ret == 0 { return nil } return &LinkError{"rename", oldpath, newpath, toSyscallErr(ret)} } /* TODO(xsw): // Rename renames (moves) oldpath to newpath. // If newpath already exists and is not a directory, Rename replaces it. // OS-specific restrictions may apply when oldpath and newpath are in different directories. // Even within the same directory, on non-Unix platforms Rename is not an atomic operation. // If there is an error, it will be of type *LinkError. func Rename(oldpath, newpath string) error { return rename(oldpath, newpath) } */ // TODO(xsw): // func SameFile(fi1, fi2 FileInfo) bool func Setenv(key, value string) error { ret := os.Setenv(c.AllocaCStr(key), c.AllocaCStr(value), 1) if ret == 0 { return nil } return &SyscallError{"setenv", toSyscallErr(ret)} } func Symlink(oldname, newname string) error { ret := os.Symlink(c.AllocaCStr(oldname), c.AllocaCStr(newname)) if ret == 0 { return nil } return &LinkError{"symlink", oldname, newname, toSyscallErr(ret)} } // TODO(xsw): // func TempDir() string func Truncate(name string, size int64) error { ret := os.Truncate(c.AllocaCStr(name), os.OffT(size)) if ret == 0 { return nil } return toPathErr("truncate", name, ret) } func Unsetenv(key string) error { ret := os.Unsetenv(c.AllocaCStr(key)) if ret == 0 { return nil } return toSyscallErr(ret) } // UserCacheDir returns the default root directory to use for user-specific // cached data. Users should create their own application-specific subdirectory // within this one and use that. // // On Unix systems, it returns $XDG_CACHE_HOME as specified by // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html if // non-empty, else $HOME/.cache. // On Darwin, it returns $HOME/Library/Caches. // On Windows, it returns %LocalAppData%. // On Plan 9, it returns $home/lib/cache. // // If the location cannot be determined (for example, $HOME is not defined), // then it will return an error. func UserCacheDir() (string, error) { var dir string switch runtime.GOOS { case "windows": dir = Getenv("LocalAppData") if dir == "" { return "", errors.New("%LocalAppData% is not defined") } case "darwin", "ios": dir = Getenv("HOME") if dir == "" { return "", errors.New("$HOME is not defined") } dir += "/Library/Caches" case "plan9": dir = Getenv("home") if dir == "" { return "", errors.New("$home is not defined") } dir += "/lib/cache" default: // Unix dir = Getenv("XDG_CACHE_HOME") if dir == "" { dir = Getenv("HOME") if dir == "" { return "", errors.New("neither $XDG_CACHE_HOME nor $HOME are defined") } dir += "/.cache" } } return dir, nil } // UserConfigDir returns the default root directory to use for user-specific // configuration data. Users should create their own application-specific // subdirectory within this one and use that. // // On Unix systems, it returns $XDG_CONFIG_HOME as specified by // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html if // non-empty, else $HOME/.config. // On Darwin, it returns $HOME/Library/Application Support. // On Windows, it returns %AppData%. // On Plan 9, it returns $home/lib. // // If the location cannot be determined (for example, $HOME is not defined), // then it will return an error. func UserConfigDir() (string, error) { var dir string switch runtime.GOOS { case "windows": dir = Getenv("AppData") if dir == "" { return "", errors.New("%AppData% is not defined") } case "darwin", "ios": dir = Getenv("HOME") if dir == "" { return "", errors.New("$HOME is not defined") } dir += "/Library/Application Support" case "plan9": dir = Getenv("home") if dir == "" { return "", errors.New("$home is not defined") } dir += "/lib" default: // Unix dir = Getenv("XDG_CONFIG_HOME") if dir == "" { dir = Getenv("HOME") if dir == "" { return "", errors.New("neither $XDG_CONFIG_HOME nor $HOME are defined") } dir += "/.config" } } return dir, nil } // UserHomeDir returns the current user's home directory. // // On Unix, including macOS, it returns the $HOME environment variable. // On Windows, it returns %USERPROFILE%. // On Plan 9, it returns the $home environment variable. // // If the expected variable is not set in the environment, UserHomeDir // returns either a platform-specific default value or a non-nil error. func UserHomeDir() (string, error) { env, enverr := "HOME", "$HOME" switch runtime.GOOS { case "windows": env, enverr = "USERPROFILE", "%userprofile%" case "plan9": env, enverr = "home", "$home" } if v := Getenv(env); v != "" { return v, nil } // On some geese the home directory is not always defined. switch runtime.GOOS { case "android": return "/sdcard", nil case "ios": return "/", nil } return "", errors.New(enverr + " is not defined") } // TODO(xsw): // func WriteFile(name string, data []byte, perm FileMode) error