From a8a532f9d6201d76f7177e30b9abd367c37c1000 Mon Sep 17 00:00:00 2001 From: leongross Date: Fri, 2 Aug 2024 08:22:46 -0700 Subject: [PATCH] os: add file.Truncate --- src/os/file.go | 10 ------ src/os/file_unix.go | 19 ++++++++++++ src/os/file_windows.go | 14 +++++++++ src/os/truncate_test.go | 61 +++++++++++++++++++++++++++++++++++++ src/syscall/libc_wasip2.go | 10 ++++-- src/syscall/syscall_libc.go | 14 +++++++++ 6 files changed, 116 insertions(+), 12 deletions(-) create mode 100644 src/os/truncate_test.go diff --git a/src/os/file.go b/src/os/file.go index acf33f850d..7c3c0db125 100644 --- a/src/os/file.go +++ b/src/os/file.go @@ -290,16 +290,6 @@ func (f *File) Sync() (err error) { return } -// Truncate is a stub, not yet implemented -func (f *File) Truncate(size int64) (err error) { - if f.handle == nil { - err = ErrClosed - } else { - err = ErrNotImplemented - } - return &PathError{Op: "truncate", Path: f.name, Err: err} -} - // LinkError records an error during a link or symlink or rename system call and // the paths that caused it. type LinkError struct { diff --git a/src/os/file_unix.go b/src/os/file_unix.go index badfc71ff9..ef7abcbac8 100644 --- a/src/os/file_unix.go +++ b/src/os/file_unix.go @@ -125,6 +125,25 @@ func Readlink(name string) (string, error) { } } +// Truncate changes the size of the file. +// It does not change the I/O offset. +// If there is an error, it will be of type *PathError. +// Alternatively just use 'raw' syscall by file name +func (f *File) Truncate(size int64) (err error) { + if f.handle == nil { + return ErrClosed + } + + e := ignoringEINTR(func() error { + return syscall.Truncate(f.name, size) + }) + + if e != nil { + return &PathError{Op: "truncate", Path: f.name, Err: e} + } + return +} + // ReadAt reads up to len(b) bytes from the File starting at the given absolute offset. // It returns the number of bytes read and any error encountered, possibly io.EOF. // At end of file, Pread returns 0, io.EOF. diff --git a/src/os/file_windows.go b/src/os/file_windows.go index 70f3a3dd0c..5860c76b04 100644 --- a/src/os/file_windows.go +++ b/src/os/file_windows.go @@ -59,6 +59,10 @@ func Pipe() (r *File, w *File, err error) { return } +func (f *unixFileHandle) Truncate(size int64) error { + return ErrNotImplemented +} + func tempDir() string { n := uint32(syscall.MAX_PATH) for { @@ -106,6 +110,16 @@ func (f unixFileHandle) Sync() error { return ErrNotImplemented } +func (f *File) Truncate(size int64) error { + var err error + if f.handle == nil { + err = ErrClosed + } else { + err = ErrNotImplemented + } + return &PathError{Op: "truncate", Path: f.name, Err: err} +} + // isWindowsNulName reports whether name is os.DevNull ('NUL') on Windows. // True is returned if name is 'NUL' whatever the case. func isWindowsNulName(name string) bool { diff --git a/src/os/truncate_test.go b/src/os/truncate_test.go new file mode 100644 index 0000000000..2b1d982ba2 --- /dev/null +++ b/src/os/truncate_test.go @@ -0,0 +1,61 @@ +//go:build darwin || (linux && !baremetal && !js && !wasi) + +// Copyright 2024 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_test + +import ( + . "os" + "path/filepath" + "runtime" + "testing" +) + +func TestTruncate(t *testing.T) { + // Truncate is not supported on Windows or wasi at the moment + if runtime.GOOS == "windows" || runtime.GOOS == "wasip1" || runtime.GOOS == "wasip2" { + t.Logf("skipping test on %s", runtime.GOOS) + return + } + + tmpDir := t.TempDir() + file := filepath.Join(tmpDir, "truncate_test") + + fd, err := Create(file) + if err != nil { + t.Fatalf("create %q: got %v, want nil", file, err) + } + defer fd.Close() + + // truncate up to 0x100 + if err := fd.Truncate(0x100); err != nil { + t.Fatalf("truncate %q: got %v, want nil", file, err) + } + + // check if size is 0x100 + fi, err := Stat(file) + if err != nil { + t.Fatalf("stat %q: got %v, want nil", file, err) + } + + if fi.Size() != 0x100 { + t.Fatalf("size of %q is %d; want 0x100", file, fi.Size()) + } + + // truncate down to 0x80 + if err := fd.Truncate(0x80); err != nil { + t.Fatalf("truncate %q: got %v, want nil", file, err) + } + + // check if size is 0x80 + fi, err = Stat(file) + if err != nil { + t.Fatalf("stat %q: got %v, want nil", file, err) + } + + if fi.Size() != 0x80 { + t.Fatalf("size of %q is %d; want 0x80", file, fi.Size()) + } +} diff --git a/src/syscall/libc_wasip2.go b/src/syscall/libc_wasip2.go index 1e985c1da9..3aadf877f3 100644 --- a/src/syscall/libc_wasip2.go +++ b/src/syscall/libc_wasip2.go @@ -432,7 +432,6 @@ func chmod(pathname *byte, mode uint32) int32 { // //export mkdir func mkdir(pathname *byte, mode uint32) int32 { - path := goString(pathname) dir, relPath := findPreopenForPath(path) if dir.d == cm.ResourceNone { @@ -773,7 +772,6 @@ var libcCWD wasiDir var wasiPreopens map[string]types.Descriptor func populatePreopens() { - var cwd string // find CWD @@ -1354,3 +1352,11 @@ func getcwd(buf *byte, size uint) *byte { copy(s, cwd) return buf } + +// int truncate(const char *path, off_t length); +// +//export truncate +func truncate(path *byte, length int64) int32 { + libcErrno = ENOSYS + return -1 +} diff --git a/src/syscall/syscall_libc.go b/src/syscall/syscall_libc.go index 0dec4c74d5..2321292d98 100644 --- a/src/syscall/syscall_libc.go +++ b/src/syscall/syscall_libc.go @@ -203,6 +203,15 @@ func Execve(pathname string, argv []string, envv []string) (err error) { return } +func Truncate(path string, length int64) (err error) { + data := cstring(path) + fail := int(libc_truncate(&data[0], length)) + if fail < 0 { + err = getErrno() + } + return +} + func Faccessat(dirfd int, path string, mode uint32, flags int) (err error) func Kill(pid int, sig Signal) (err error) { @@ -451,3 +460,8 @@ func libc_fork() int32 // //export execve func libc_execve(filename *byte, argv **byte, envp **byte) int + +// int truncate(const char *path, off_t length); +// +//export truncate +func libc_truncate(path *byte, length int64) int32