// Copyright 2016 The etcd Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // +build linux package fileutil import ( "io" "os" "syscall" ) // This used to call syscall.Flock() but that call fails with EBADF on NFS. // An alternative is lockf() which works on NFS but that call lets a process lock // the same file twice. Instead, use Linux's non-standard open file descriptor // locks which will block if the process already holds the file lock. // // constants from /usr/include/bits/fcntl-linux.h const ( F_OFD_GETLK = 37 F_OFD_SETLK = 37 F_OFD_SETLKW = 38 ) var ( wrlck = syscall.Flock_t{ Type: syscall.F_WRLCK, Whence: int16(io.SeekStart), Start: 0, Len: 0, } linuxTryLockFile = flockTryLockFile linuxLockFile = flockLockFile ) func init() { // use open file descriptor locks if the system supports it getlk := syscall.Flock_t{Type: syscall.F_RDLCK} if err := syscall.FcntlFlock(0, F_OFD_GETLK, &getlk); err == nil { linuxTryLockFile = ofdTryLockFile linuxLockFile = ofdLockFile } } func TryLockFile(path string, flag int, perm os.FileMode) (*LockedFile, error) { return linuxTryLockFile(path, flag, perm) } func ofdTryLockFile(path string, flag int, perm os.FileMode) (*LockedFile, error) { f, err := os.OpenFile(path, flag, perm) if err != nil { return nil, err } flock := wrlck if err = syscall.FcntlFlock(f.Fd(), F_OFD_SETLK, &flock); err != nil { f.Close() if err == syscall.EWOULDBLOCK { err = ErrLocked } return nil, err } return &LockedFile{f}, nil } func LockFile(path string, flag int, perm os.FileMode) (*LockedFile, error) { return linuxLockFile(path, flag, perm) } func ofdLockFile(path string, flag int, perm os.FileMode) (*LockedFile, error) { f, err := os.OpenFile(path, flag, perm) if err != nil { return nil, err } flock := wrlck err = syscall.FcntlFlock(f.Fd(), F_OFD_SETLKW, &flock) if err != nil { f.Close() return nil, err } return &LockedFile{f}, err }