diff options
Diffstat (limited to 'src/pkg/os')
-rw-r--r-- | src/pkg/os/file.go | 22 | ||||
-rw-r--r-- | src/pkg/os/file_plan9.go | 33 | ||||
-rw-r--r-- | src/pkg/os/file_posix.go | 4 | ||||
-rw-r--r-- | src/pkg/os/file_unix.go | 33 | ||||
-rw-r--r-- | src/pkg/os/file_windows.go | 71 | ||||
-rw-r--r-- | src/pkg/os/inotify/inotify_linux.go | 2 | ||||
-rw-r--r-- | src/pkg/os/os_test.go | 34 | ||||
-rw-r--r-- | src/pkg/os/user/Makefile | 26 | ||||
-rw-r--r-- | src/pkg/os/user/lookup_stubs.go | 19 | ||||
-rw-r--r-- | src/pkg/os/user/lookup_unix.go | 104 | ||||
-rw-r--r-- | src/pkg/os/user/user.go | 35 | ||||
-rw-r--r-- | src/pkg/os/user/user_test.go | 61 |
12 files changed, 430 insertions, 14 deletions
diff --git a/src/pkg/os/file.go b/src/pkg/os/file.go index 3aad80234..dff8fa862 100644 --- a/src/pkg/os/file.go +++ b/src/pkg/os/file.go @@ -2,12 +2,13 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// The os package provides a platform-independent interface to operating -// system functionality. The design is Unix-like. +// Package os provides a platform-independent interface to operating system +// functionality. The design is Unix-like. package os import ( "runtime" + "sync" "syscall" ) @@ -15,8 +16,9 @@ import ( type File struct { fd int name string - dirinfo *dirInfo // nil unless directory being read - nepipe int // number of consecutive EPIPE in Write + dirinfo *dirInfo // nil unless directory being read + nepipe int // number of consecutive EPIPE in Write + l sync.Mutex // used to implement windows pread/pwrite } // Fd returns the integer Unix file descriptor referencing the open file. @@ -30,7 +32,7 @@ func NewFile(fd int, name string) *File { if fd < 0 { return nil } - f := &File{fd, name, nil, 0} + f := &File{fd: fd, name: name} runtime.SetFinalizer(f, (*File).Close) return f } @@ -85,7 +87,7 @@ func (file *File) Read(b []byte) (n int, err Error) { if file == nil { return 0, EINVAL } - n, e := syscall.Read(file.fd, b) + n, e := file.read(b) if n < 0 { n = 0 } @@ -107,7 +109,7 @@ func (file *File) ReadAt(b []byte, off int64) (n int, err Error) { return 0, EINVAL } for len(b) > 0 { - m, e := syscall.Pread(file.fd, b, off) + m, e := file.pread(b, off) if m == 0 && !iserror(e) { return n, EOF } @@ -129,7 +131,7 @@ func (file *File) Write(b []byte) (n int, err Error) { if file == nil { return 0, EINVAL } - n, e := syscall.Write(file.fd, b) + n, e := file.write(b) if n < 0 { n = 0 } @@ -150,7 +152,7 @@ func (file *File) WriteAt(b []byte, off int64) (n int, err Error) { return 0, EINVAL } for len(b) > 0 { - m, e := syscall.Pwrite(file.fd, b, off) + m, e := file.pwrite(b, off) if iserror(e) { err = &PathError{"write", file.name, Errno(e)} break @@ -167,7 +169,7 @@ func (file *File) WriteAt(b []byte, off int64) (n int, err Error) { // relative to the current offset, and 2 means relative to the end. // It returns the new offset and an Error, if any. func (file *File) Seek(offset int64, whence int) (ret int64, err Error) { - r, e := syscall.Seek(file.fd, offset, whence) + r, e := file.seek(offset, whence) if !iserror(e) && file.dirinfo != nil && r != 0 { e = syscall.EISDIR } diff --git a/src/pkg/os/file_plan9.go b/src/pkg/os/file_plan9.go index c8d0efba4..7b473f802 100644 --- a/src/pkg/os/file_plan9.go +++ b/src/pkg/os/file_plan9.go @@ -117,6 +117,39 @@ func (f *File) Sync() (err Error) { return nil } +// read reads up to len(b) bytes from the File. +// It returns the number of bytes read and an error, if any. +func (f *File) read(b []byte) (n int, err syscall.Error) { + return syscall.Read(f.fd, b) +} + +// pread reads len(b) bytes from the File starting at byte offset off. +// It returns the number of bytes read and the error, if any. +// EOF is signaled by a zero count with err set to nil. +func (f *File) pread(b []byte, off int64) (n int, err syscall.Error) { + return syscall.Pread(f.fd, b, off) +} + +// write writes len(b) bytes to the File. +// It returns the number of bytes written and an error, if any. +func (f *File) write(b []byte) (n int, err syscall.Error) { + return syscall.Write(f.fd, b) +} + +// pwrite writes len(b) bytes to the File starting at byte offset off. +// It returns the number of bytes written and an error, if any. +func (f *File) pwrite(b []byte, off int64) (n int, err syscall.Error) { + return syscall.Pwrite(f.fd, b, off) +} + +// seek sets the offset for the next Read or Write on file to offset, interpreted +// according to whence: 0 means relative to the origin of the file, 1 means +// relative to the current offset, and 2 means relative to the end. +// It returns the new offset and an error, if any. +func (f *File) seek(offset int64, whence int) (ret int64, err syscall.Error) { + return syscall.Seek(f.fd, offset, whence) +} + // Truncate changes the size of the named file. // If the file is a symbolic link, it changes the size of the link's target. func Truncate(name string, size int64) Error { diff --git a/src/pkg/os/file_posix.go b/src/pkg/os/file_posix.go index 5151df498..f1191d61f 100644 --- a/src/pkg/os/file_posix.go +++ b/src/pkg/os/file_posix.go @@ -10,11 +10,13 @@ import ( "syscall" ) +func sigpipe() // implemented in package runtime + func epipecheck(file *File, e int) { if e == syscall.EPIPE { file.nepipe++ if file.nepipe >= 10 { - Exit(syscall.EPIPE) + sigpipe() } } else { file.nepipe = 0 diff --git a/src/pkg/os/file_unix.go b/src/pkg/os/file_unix.go index f2b94f4c2..2fb28df65 100644 --- a/src/pkg/os/file_unix.go +++ b/src/pkg/os/file_unix.go @@ -96,6 +96,39 @@ func (file *File) Readdir(count int) (fi []FileInfo, err Error) { return } +// read reads up to len(b) bytes from the File. +// It returns the number of bytes read and an error, if any. +func (f *File) read(b []byte) (n int, err int) { + return syscall.Read(f.fd, b) +} + +// pread reads len(b) bytes from the File starting at byte offset off. +// It returns the number of bytes read and the error, if any. +// EOF is signaled by a zero count with err set to 0. +func (f *File) pread(b []byte, off int64) (n int, err int) { + return syscall.Pread(f.fd, b, off) +} + +// write writes len(b) bytes to the File. +// It returns the number of bytes written and an error, if any. +func (f *File) write(b []byte) (n int, err int) { + return syscall.Write(f.fd, b) +} + +// pwrite writes len(b) bytes to the File starting at byte offset off. +// It returns the number of bytes written and an error, if any. +func (f *File) pwrite(b []byte, off int64) (n int, err int) { + return syscall.Pwrite(f.fd, b, off) +} + +// seek sets the offset for the next Read or Write on file to offset, interpreted +// according to whence: 0 means relative to the origin of the file, 1 means +// relative to the current offset, and 2 means relative to the end. +// It returns the new offset and an error, if any. +func (f *File) seek(offset int64, whence int) (ret int64, err int) { + return syscall.Seek(f.fd, offset, whence) +} + // Truncate changes the size of the named file. // If the file is a symbolic link, it changes the size of the link's target. func Truncate(name string, size int64) Error { diff --git a/src/pkg/os/file_windows.go b/src/pkg/os/file_windows.go index 862baf6b9..95f60b735 100644 --- a/src/pkg/os/file_windows.go +++ b/src/pkg/os/file_windows.go @@ -165,6 +165,77 @@ func (file *File) Readdir(count int) (fi []FileInfo, err Error) { return fi, nil } +// read reads up to len(b) bytes from the File. +// It returns the number of bytes read and an error, if any. +func (f *File) read(b []byte) (n int, err int) { + f.l.Lock() + defer f.l.Unlock() + return syscall.Read(f.fd, b) +} + +// pread reads len(b) bytes from the File starting at byte offset off. +// It returns the number of bytes read and the error, if any. +// EOF is signaled by a zero count with err set to 0. +func (f *File) pread(b []byte, off int64) (n int, err int) { + f.l.Lock() + defer f.l.Unlock() + curoffset, e := syscall.Seek(f.fd, 0, 1) + if e != 0 { + return 0, e + } + defer syscall.Seek(f.fd, curoffset, 0) + o := syscall.Overlapped{ + OffsetHigh: uint32(off >> 32), + Offset: uint32(off), + } + var done uint32 + e = syscall.ReadFile(int32(f.fd), b, &done, &o) + if e != 0 { + return 0, e + } + return int(done), 0 +} + +// write writes len(b) bytes to the File. +// It returns the number of bytes written and an error, if any. +func (f *File) write(b []byte) (n int, err int) { + f.l.Lock() + defer f.l.Unlock() + return syscall.Write(f.fd, b) +} + +// pwrite writes len(b) bytes to the File starting at byte offset off. +// It returns the number of bytes written and an error, if any. +func (f *File) pwrite(b []byte, off int64) (n int, err int) { + f.l.Lock() + defer f.l.Unlock() + curoffset, e := syscall.Seek(f.fd, 0, 1) + if e != 0 { + return 0, e + } + defer syscall.Seek(f.fd, curoffset, 0) + o := syscall.Overlapped{ + OffsetHigh: uint32(off >> 32), + Offset: uint32(off), + } + var done uint32 + e = syscall.WriteFile(int32(f.fd), b, &done, &o) + if e != 0 { + return 0, e + } + return int(done), 0 +} + +// seek sets the offset for the next Read or Write on file to offset, interpreted +// according to whence: 0 means relative to the origin of the file, 1 means +// relative to the current offset, and 2 means relative to the end. +// It returns the new offset and an error, if any. +func (f *File) seek(offset int64, whence int) (ret int64, err int) { + f.l.Lock() + defer f.l.Unlock() + return syscall.Seek(f.fd, offset, whence) +} + // Truncate changes the size of the named file. // If the file is a symbolic link, it changes the size of the link's target. func Truncate(name string, size int64) Error { diff --git a/src/pkg/os/inotify/inotify_linux.go b/src/pkg/os/inotify/inotify_linux.go index 8b5c30e0d..7c7b7698f 100644 --- a/src/pkg/os/inotify/inotify_linux.go +++ b/src/pkg/os/inotify/inotify_linux.go @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. /* -This package implements a wrapper for the Linux inotify system. +Package inotify implements a wrapper for the Linux inotify system. Example: watcher, err := inotify.NewWatcher() diff --git a/src/pkg/os/os_test.go b/src/pkg/os/os_test.go index 551b86508..65475c118 100644 --- a/src/pkg/os/os_test.go +++ b/src/pkg/os/os_test.go @@ -567,8 +567,8 @@ func checkSize(t *testing.T, f *File, size int64) { } } -func TestTruncate(t *testing.T) { - f := newFile("TestTruncate", t) +func TestFTruncate(t *testing.T) { + f := newFile("TestFTruncate", t) defer Remove(f.Name()) defer f.Close() @@ -585,6 +585,24 @@ func TestTruncate(t *testing.T) { checkSize(t, f, 13+9) // wrote at offset past where hello, world was. } +func TestTruncate(t *testing.T) { + f := newFile("TestTruncate", t) + defer Remove(f.Name()) + defer f.Close() + + checkSize(t, f, 0) + f.Write([]byte("hello, world\n")) + checkSize(t, f, 13) + Truncate(f.Name(), 10) + checkSize(t, f, 10) + Truncate(f.Name(), 1024) + checkSize(t, f, 1024) + Truncate(f.Name(), 0) + checkSize(t, f, 0) + f.Write([]byte("surprise!")) + checkSize(t, f, 13+9) // wrote at offset past where hello, world was. +} + // Use TempDir() to make sure we're on a local file system, // so that timings are not distorted by latency and caching. // On NFS, timings can be off due to caching of meta-data on @@ -886,6 +904,18 @@ func TestAppend(t *testing.T) { if s != "new|append" { t.Fatalf("writeFile: have %q want %q", s, "new|append") } + s = writeFile(t, f, O_CREATE|O_APPEND|O_RDWR, "|append") + if s != "new|append|append" { + t.Fatalf("writeFile: have %q want %q", s, "new|append|append") + } + err := Remove(f) + if err != nil { + t.Fatalf("Remove: %v", err) + } + s = writeFile(t, f, O_CREATE|O_APPEND|O_RDWR, "new&append") + if s != "new&append" { + t.Fatalf("writeFile: have %q want %q", s, "new&append") + } } func TestStatDirWithTrailingSlash(t *testing.T) { diff --git a/src/pkg/os/user/Makefile b/src/pkg/os/user/Makefile new file mode 100644 index 000000000..731f7999a --- /dev/null +++ b/src/pkg/os/user/Makefile @@ -0,0 +1,26 @@ +# Copyright 2011 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. + +include ../../../Make.inc + +TARG=os/user +GOFILES=\ + user.go\ + +ifneq ($(GOARCH),arm) +CGOFILES_linux=\ + lookup_unix.go +CGOFILES_freebsd=\ + lookup_unix.go +CGOFILES_darwin=\ + lookup_unix.go +endif + +ifneq ($(CGOFILES_$(GOOS)),) +CGOFILES+=$(CGOFILES_$(GOOS)) +else +GOFILES+=lookup_stubs.go +endif + +include ../../../Make.pkg diff --git a/src/pkg/os/user/lookup_stubs.go b/src/pkg/os/user/lookup_stubs.go new file mode 100644 index 000000000..2f08f70fd --- /dev/null +++ b/src/pkg/os/user/lookup_stubs.go @@ -0,0 +1,19 @@ +// Copyright 2011 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 user + +import ( + "fmt" + "os" + "runtime" +) + +func Lookup(username string) (*User, os.Error) { + return nil, fmt.Errorf("user: Lookup not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) +} + +func LookupId(int) (*User, os.Error) { + return nil, fmt.Errorf("user: LookupId not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) +} diff --git a/src/pkg/os/user/lookup_unix.go b/src/pkg/os/user/lookup_unix.go new file mode 100644 index 000000000..678de802b --- /dev/null +++ b/src/pkg/os/user/lookup_unix.go @@ -0,0 +1,104 @@ +// Copyright 2011 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 user + +import ( + "fmt" + "os" + "runtime" + "strings" + "unsafe" +) + +/* +#include <unistd.h> +#include <sys/types.h> +#include <pwd.h> +#include <stdlib.h> + +static int mygetpwuid_r(int uid, struct passwd *pwd, + char *buf, size_t buflen, struct passwd **result) { + return getpwuid_r(uid, pwd, buf, buflen, result); +} +*/ +import "C" + +// Lookup looks up a user by username. If the user cannot be found, +// the returned error is of type UnknownUserError. +func Lookup(username string) (*User, os.Error) { + return lookup(-1, username, true) +} + +// LookupId looks up a user by userid. If the user cannot be found, +// the returned error is of type UnknownUserIdError. +func LookupId(uid int) (*User, os.Error) { + return lookup(uid, "", false) +} + +func lookup(uid int, username string, lookupByName bool) (*User, os.Error) { + var pwd C.struct_passwd + var result *C.struct_passwd + + var bufSize C.long + if runtime.GOOS == "freebsd" { + // FreeBSD doesn't have _SC_GETPW_R_SIZE_MAX + // and just returns -1. So just use the same + // size that Linux returns + bufSize = 1024 + } else { + bufSize = C.sysconf(C._SC_GETPW_R_SIZE_MAX) + if bufSize <= 0 || bufSize > 1<<20 { + return nil, fmt.Errorf("user: unreasonable _SC_GETPW_R_SIZE_MAX of %d", bufSize) + } + } + buf := C.malloc(C.size_t(bufSize)) + defer C.free(buf) + var rv C.int + if lookupByName { + nameC := C.CString(username) + defer C.free(unsafe.Pointer(nameC)) + rv = C.getpwnam_r(nameC, + &pwd, + (*C.char)(buf), + C.size_t(bufSize), + &result) + if rv != 0 { + return nil, fmt.Errorf("user: lookup username %s: %s", username, os.Errno(rv)) + } + if result == nil { + return nil, UnknownUserError(username) + } + } else { + // mygetpwuid_r is a wrapper around getpwuid_r to + // to avoid using uid_t because C.uid_t(uid) for + // unknown reasons doesn't work on linux. + rv = C.mygetpwuid_r(C.int(uid), + &pwd, + (*C.char)(buf), + C.size_t(bufSize), + &result) + if rv != 0 { + return nil, fmt.Errorf("user: lookup userid %d: %s", uid, os.Errno(rv)) + } + if result == nil { + return nil, UnknownUserIdError(uid) + } + } + u := &User{ + Uid: int(pwd.pw_uid), + Gid: int(pwd.pw_gid), + Username: C.GoString(pwd.pw_name), + Name: C.GoString(pwd.pw_gecos), + HomeDir: C.GoString(pwd.pw_dir), + } + // The pw_gecos field isn't quite standardized. Some docs + // say: "It is expected to be a comma separated list of + // personal data where the first item is the full name of the + // user." + if i := strings.Index(u.Name, ","); i >= 0 { + u.Name = u.Name[:i] + } + return u, nil +} diff --git a/src/pkg/os/user/user.go b/src/pkg/os/user/user.go new file mode 100644 index 000000000..dd009211d --- /dev/null +++ b/src/pkg/os/user/user.go @@ -0,0 +1,35 @@ +// Copyright 2011 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 user allows user account lookups by name or id. +package user + +import ( + "strconv" +) + +// User represents a user account. +type User struct { + Uid int // user id + Gid int // primary group id + Username string + Name string + HomeDir string +} + +// UnknownUserIdError is returned by LookupId when +// a user cannot be found. +type UnknownUserIdError int + +func (e UnknownUserIdError) String() string { + return "user: unknown userid " + strconv.Itoa(int(e)) +} + +// UnknownUserError is returned by Lookup when +// a user cannot be found. +type UnknownUserError string + +func (e UnknownUserError) String() string { + return "user: unknown user " + string(e) +} diff --git a/src/pkg/os/user/user_test.go b/src/pkg/os/user/user_test.go new file mode 100644 index 000000000..2c142bf18 --- /dev/null +++ b/src/pkg/os/user/user_test.go @@ -0,0 +1,61 @@ +// Copyright 2011 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 user + +import ( + "os" + "reflect" + "runtime" + "syscall" + "testing" +) + +func skip(t *testing.T) bool { + if runtime.GOARCH == "arm" { + t.Logf("user: cgo not implemented on arm; skipping tests") + return true + } + + if runtime.GOOS == "linux" || runtime.GOOS == "freebsd" || runtime.GOOS == "darwin" { + return false + } + + t.Logf("user: Lookup not implemented on %s; skipping test", runtime.GOOS) + return true +} + +func TestLookup(t *testing.T) { + if skip(t) { + return + } + + // Test LookupId on the current user + uid := syscall.Getuid() + u, err := LookupId(uid) + if err != nil { + t.Fatalf("LookupId: %v", err) + } + if e, g := uid, u.Uid; e != g { + t.Errorf("expected Uid of %d; got %d", e, g) + } + fi, err := os.Stat(u.HomeDir) + if err != nil || !fi.IsDirectory() { + t.Errorf("expected a valid HomeDir; stat(%q): err=%v, IsDirectory=%v", err, fi.IsDirectory()) + } + if u.Username == "" { + t.Fatalf("didn't get a username") + } + + // Test Lookup by username, using the username from LookupId + un, err := Lookup(u.Username) + if err != nil { + t.Fatalf("Lookup: %v", err) + } + if !reflect.DeepEqual(u, un) { + t.Errorf("Lookup by userid vs. name didn't match\n"+ + "LookupId(%d): %#v\n"+ + "Lookup(%q): %#v\n",uid, u, u.Username, un) + } +} |