diff --git a/internal/lib/internal/bytealg/bytealg.go b/internal/lib/internal/bytealg/bytealg.go index 26979f76..64652abf 100644 --- a/internal/lib/internal/bytealg/bytealg.go +++ b/internal/lib/internal/bytealg/bytealg.go @@ -105,3 +105,21 @@ type sliceHead struct { len int cap int } + +func LastIndexByte(s []byte, c byte) int { + for i := len(s) - 1; i >= 0; i-- { + if s[i] == c { + return i + } + } + return -1 +} + +func LastIndexByteString(s string, c byte) int { + for i := len(s) - 1; i >= 0; i-- { + if s[i] == c { + return i + } + } + return -1 +} diff --git a/internal/lib/os/file.go b/internal/lib/os/file.go index f0f87abc..f72ba520 100644 --- a/internal/lib/os/file.go +++ b/internal/lib/os/file.go @@ -286,7 +286,6 @@ func (f *File) wrapErr(op string, err error) error { return &PathError{Op: op, Path: f.name, Err: err} } -/* // TempDir returns the default directory to use for temporary files. // // On Unix systems, it returns $TMPDIR if non-empty, else /tmp. @@ -299,7 +298,6 @@ func (f *File) wrapErr(op string, err error) error { func TempDir() string { return tempDir() } -*/ // Chmod changes the mode of the file to mode. // If there is an error, it will be of type *PathError. diff --git a/internal/lib/os/os.go b/internal/lib/os/os.go index 591f2ced..cf609934 100644 --- a/internal/lib/os/os.go +++ b/internal/lib/os/os.go @@ -272,7 +272,6 @@ func Mkdir(name string, perm FileMode) error { // TODO(xsw): // func MkdirAll(path string, perm FileMode) error -// func MkdirTemp(dir, pattern string) (string, error) // func NewSyscallError(syscall string, err error) error // func ReadFile(name string) ([]byte, error) @@ -327,9 +326,6 @@ func Symlink(oldname, newname string) error { return &LinkError{"symlink", oldname, newname, syscall.Errno(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 { diff --git a/internal/lib/os/tempfile.go b/internal/lib/os/tempfile.go new file mode 100644 index 00000000..4589472c --- /dev/null +++ b/internal/lib/os/tempfile.go @@ -0,0 +1,115 @@ +// Copyright 2009 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. + +package os + +import ( + "errors" + + "github.com/goplus/llgo/c" + "github.com/goplus/llgo/internal/lib/internal/bytealg" + "github.com/goplus/llgo/internal/lib/internal/itoa" +) + +func nextRandom() string { + return itoa.Uitoa(uint(uint32(c.Rand()))) +} + +// CreateTemp creates a new temporary file in the directory dir, +// opens the file for reading and writing, and returns the resulting file. +// The filename is generated by taking pattern and adding a random string to the end. +// If pattern includes a "*", the random string replaces the last "*". +// If dir is the empty string, CreateTemp uses the default directory for temporary files, as returned by TempDir. +// Multiple programs or goroutines calling CreateTemp simultaneously will not choose the same file. +// The caller can use the file's Name method to find the pathname of the file. +// It is the caller's responsibility to remove the file when it is no longer needed. +func CreateTemp(dir, pattern string) (*File, error) { + if dir == "" { + dir = TempDir() + } + + prefix, suffix, err := prefixAndSuffix(pattern) + if err != nil { + return nil, &PathError{Op: "createtemp", Path: pattern, Err: err} + } + prefix = joinPath(dir, prefix) + + try := 0 + for { + name := prefix + nextRandom() + suffix + f, err := OpenFile(name, O_RDWR|O_CREATE|O_EXCL, 0600) + if IsExist(err) { + if try++; try < 10000 { + continue + } + return nil, &PathError{Op: "createtemp", Path: prefix + "*" + suffix, Err: ErrExist} + } + return f, err + } +} + +var errPatternHasSeparator = errors.New("pattern contains path separator") + +// prefixAndSuffix splits pattern by the last wildcard "*", if applicable, +// returning prefix as the part before "*" and suffix as the part after "*". +func prefixAndSuffix(pattern string) (prefix, suffix string, err error) { + for i := 0; i < len(pattern); i++ { + if IsPathSeparator(pattern[i]) { + return "", "", errPatternHasSeparator + } + } + if pos := bytealg.LastIndexByteString(pattern, '*'); pos != -1 { + prefix, suffix = pattern[:pos], pattern[pos+1:] + } else { + prefix = pattern + } + return prefix, suffix, nil +} + +// MkdirTemp creates a new temporary directory in the directory dir +// and returns the pathname of the new directory. +// The new directory's name is generated by adding a random string to the end of pattern. +// If pattern includes a "*", the random string replaces the last "*" instead. +// If dir is the empty string, MkdirTemp uses the default directory for temporary files, as returned by TempDir. +// Multiple programs or goroutines calling MkdirTemp simultaneously will not choose the same directory. +// It is the caller's responsibility to remove the directory when it is no longer needed. +func MkdirTemp(dir, pattern string) (string, error) { + if dir == "" { + dir = TempDir() + } + + prefix, suffix, err := prefixAndSuffix(pattern) + if err != nil { + return "", &PathError{Op: "mkdirtemp", Path: pattern, Err: err} + } + prefix = joinPath(dir, prefix) + + try := 0 + for { + name := prefix + nextRandom() + suffix + err := Mkdir(name, 0700) + if err == nil { + return name, nil + } + if IsExist(err) { + if try++; try < 10000 { + continue + } + return "", &PathError{Op: "mkdirtemp", Path: dir + string(PathSeparator) + prefix + "*" + suffix, Err: ErrExist} + } + if IsNotExist(err) { + if _, err := Stat(dir); IsNotExist(err) { + return "", err + } + } + return "", err + } +} + +func joinPath(dir, name string) string { + if len(dir) > 0 && IsPathSeparator(dir[len(dir)-1]) { + return dir + name + } + return dir + string(PathSeparator) + name +}