// flock acquires an advisory lock on a file descriptor. func flock(f *os.File, exclusive bool, timeout time.Duration) error { var t time.Time for { // If we're beyond our timeout then return an error. // This can only occur after we've attempted a flock once. if t.IsZero() { t = time.Now() } else if timeout > 0 && time.Since(t) > timeout { return ErrTimeout } var lock syscall.Flock_t lock.Start = 0 lock.Len = 0 lock.Pid = 0 lock.Whence = 0 lock.Pid = 0 if exclusive { lock.Type = syscall.F_WRLCK } else { lock.Type = syscall.F_RDLCK } err := syscall.FcntlFlock(f.Fd(), syscall.F_SETLK, &lock) if err == nil { return nil } else if err != syscall.EAGAIN { return err } // Wait for a bit and try again. time.Sleep(50 * time.Millisecond) } }
// TryLock acquires exclusivity on the lock without blocking func (l *lock) TryLock() error { var lock syscall.Flock_t lock.Start = 0 lock.Len = 0 lock.Pid = 0 lock.Type = syscall.F_WRLCK lock.Whence = 0 lock.Pid = 0 err := syscall.FcntlFlock(uintptr(l.fd), syscall.F_SETLK, &lock) if err != nil && err == syscall.EAGAIN { return ErrLocked } return err }
// Lock acquires exclusivity on the lock without blocking func (l *lock) Lock() error { var lock syscall.Flock_t lock.Start = 0 lock.Len = 0 lock.Type = syscall.F_WRLCK lock.Whence = 0 lock.Pid = 0 return syscall.FcntlFlock(uintptr(l.fd), syscall.F_SETLK, &lock) }
func TryLockFile(path string, flag int, perm os.FileMode) (*LockedFile, error) { var lock syscall.Flock_t lock.Start = 0 lock.Len = 0 lock.Pid = 0 lock.Type = syscall.F_WRLCK lock.Whence = 0 lock.Pid = 0 f, err := os.OpenFile(path, flag, perm) if err != nil { return nil, err } if err := syscall.FcntlFlock(f.Fd(), syscall.F_SETLK, &lock); err != nil { f.Close() if err == syscall.EAGAIN { err = ErrLocked } return nil, err } return &LockedFile{f}, nil }
// TestFcntlFlock tests whether the file locking structure matches // the calling convention of each kernel. // On some Linux systems, glibc uses another set of values for the // commands and translates them to the correct value that the kernel // expects just before the actual fcntl syscall. As Go uses raw // syscalls directly, it must use the real value, not the glibc value. // Thus this test also verifies that the Flock_t structure can be // roundtripped with F_SETLK and F_GETLK. func TestFcntlFlock(t *testing.T) { if runtime.GOOS == "darwin" && (runtime.GOARCH == "arm" || runtime.GOARCH == "arm64") { t.Skip("skipping; no child processes allowed on iOS") } flock := syscall.Flock_t{ Type: syscall.F_WRLCK, Start: 31415, Len: 271828, Whence: 1, } if os.Getenv("GO_WANT_HELPER_PROCESS") == "" { // parent name := filepath.Join(os.TempDir(), "TestFcntlFlock") fd, err := syscall.Open(name, syscall.O_CREAT|syscall.O_RDWR|syscall.O_CLOEXEC, 0) if err != nil { t.Fatalf("Open failed: %v", err) } defer syscall.Unlink(name) defer syscall.Close(fd) if err := syscall.Ftruncate(fd, 1<<20); err != nil { t.Fatalf("Ftruncate(1<<20) failed: %v", err) } if err := syscall.FcntlFlock(uintptr(fd), syscall.F_SETLK, &flock); err != nil { t.Fatalf("FcntlFlock(F_SETLK) failed: %v", err) } cmd := exec.Command(os.Args[0], "-test.run=^TestFcntlFlock$") cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1") cmd.ExtraFiles = []*os.File{os.NewFile(uintptr(fd), name)} out, err := cmd.CombinedOutput() if len(out) > 0 || err != nil { t.Fatalf("child process: %q, %v", out, err) } } else { // child got := flock // make sure the child lock is conflicting with the parent lock got.Start-- got.Len++ if err := syscall.FcntlFlock(3, syscall.F_GETLK, &got); err != nil { t.Fatalf("FcntlFlock(F_GETLK) failed: %v", err) } flock.Pid = int32(syscall.Getppid()) // Linux kernel always set Whence to 0 flock.Whence = 0 if got.Type == flock.Type && got.Start == flock.Start && got.Len == flock.Len && got.Pid == flock.Pid && got.Whence == flock.Whence { os.Exit(0) } t.Fatalf("FcntlFlock got %v, want %v", got, flock) } }