//go:build linux package update import ( domain "RenewCMS/internal/domain/update" "errors" "io" "io/fs" "os" "path/filepath" "syscall" ) const ( privateTempDirPrefix = "renewcms-update-" readNoFollowFlags = os.O_RDONLY | syscall.O_NOFOLLOW | syscall.O_NONBLOCK createNoFollowFlags = os.O_WRONLY | os.O_CREATE | os.O_EXCL | syscall.O_NOFOLLOW directoryFlags = os.O_RDONLY | syscall.O_NOFOLLOW | syscall.O_DIRECTORY ) var errNotRegularFile = errors.New("not a regular file") type NoFollowFilesystem struct{} var _ domain.HelperFilesystem = &NoFollowFilesystem{} func NewNoFollowFilesystem() *NoFollowFilesystem { return &NoFollowFilesystem{} } func (f *NoFollowFilesystem) Lstat(path string) (domain.FileInfo, error) { info, err := os.Lstat(path) if err != nil { return domain.FileInfo{}, err } mode := info.Mode() return domain.FileInfo{ IsSymlink: mode&fs.ModeSymlink != 0, IsDir: mode.IsDir(), IsRegular: mode.IsRegular(), }, nil } func (f *NoFollowFilesystem) ReadFile(path string) ([]byte, error) { file, err := openRegularNoFollow(path) if err != nil { return nil, err } defer file.Close() return io.ReadAll(file) } func (f *NoFollowFilesystem) CreateExclusive(path string, content []byte, mode uint32) error { file, err := os.OpenFile(path, createNoFollowFlags, fs.FileMode(mode)) if err != nil { return err } if _, err := file.Write(content); err != nil { file.Close() return err } return finishWrite(file, mode) } func (f *NoFollowFilesystem) Copy(source string, destination string, mode uint32) error { input, err := openRegularNoFollow(source) if err != nil { return err } defer input.Close() output, err := os.OpenFile(destination, createNoFollowFlags, fs.FileMode(mode)) if err != nil { return err } if _, err := io.Copy(output, input); err != nil { output.Close() return err } return finishWrite(output, mode) } func (f *NoFollowFilesystem) Rename(oldPath string, newPath string) error { return os.Rename(oldPath, newPath) } func (f *NoFollowFilesystem) Remove(path string) error { return os.Remove(path) } func (f *NoFollowFilesystem) RemoveAll(path string) error { info, err := os.Lstat(path) if errors.Is(err, fs.ErrNotExist) { return nil } if err != nil { return err } if !info.IsDir() { return os.Remove(path) } names, err := readDirectoryNames(path) if err != nil { return err } for _, name := range names { if err := f.RemoveAll(filepath.Join(path, name)); err != nil { return err } } return os.Remove(path) } func (f *NoFollowFilesystem) Chmod(path string, mode uint32) error { file, err := os.OpenFile(path, readNoFollowFlags, 0) if err != nil { return err } defer file.Close() return file.Chmod(fs.FileMode(mode)) } func (f *NoFollowFilesystem) MakePrivateTempDir() (string, error) { return os.MkdirTemp("", privateTempDirPrefix) } func openRegularNoFollow(path string) (*os.File, error) { file, err := os.OpenFile(path, readNoFollowFlags, 0) if err != nil { return nil, err } info, err := file.Stat() if err != nil { file.Close() return nil, err } if !info.Mode().IsRegular() { file.Close() return nil, errNotRegularFile } return file, nil } func finishWrite(file *os.File, mode uint32) error { if err := file.Chmod(fs.FileMode(mode)); err != nil { file.Close() return err } if err := file.Sync(); err != nil { file.Close() return err } return file.Close() } func readDirectoryNames(path string) ([]string, error) { directory, err := os.OpenFile(path, directoryFlags, 0) if err != nil { return nil, err } defer directory.Close() return directory.Readdirnames(-1) }