| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124 |
- //go:build darwin || dragonfly || freebsd || illumos || linux || netbsd || openbsd
- // The above platforms support flock()
- package fileutil
- import (
- "bytes"
- "fmt"
- "io"
- "os"
- "syscall"
- "github.com/opencost/opencost/core/pkg/log"
- )
- // WriteLockedFD uses the flock() syscall to safely write to an open file as
- // long as other users of the file are also using flock()-based access.
- //
- // WriteLocked will block until it gets lock access.
- //
- // The file will be truncated before writing and at the end of writing the
- // FD will be reset to position 0.
- //
- // For the reasons outlined best in https://lwn.net/Articles/586904/ this uses
- // flock() instead of fcntl(). The ability to lock byte ranges is not necessary
- // and flock() has better behavior.
- func WriteLockedFD(f *os.File, data []byte) (int, error) {
- // For the reasons outlined best in https://lwn.net/Articles/586904/ we're
- // going to use flock() instead of fcntl() because we want a whole-file lock.
- if err := syscall.Flock(int(f.Fd()), syscall.LOCK_EX); err != nil {
- return 0, fmt.Errorf("unexpected error flock()-ing with EX: %w", err)
- }
- defer func() {
- if err := syscall.Flock(int(f.Fd()), syscall.LOCK_UN); err != nil {
- log.Errorf("unexpected error flock()-ing FD %d with UN after writing: %s", f.Fd(), err)
- }
- }()
- if err := f.Truncate(0); err != nil {
- return 0, fmt.Errorf("truncating: %w", err)
- }
- if _, err := f.Seek(0, 0); err != nil {
- return 0, fmt.Errorf("seeking to 0 before write: %w", err)
- }
- defer func() {
- if _, err := f.Seek(0, 0); err != nil {
- log.Errorf("unexpected error seeking to 0 after write on FD %d: %s", f.Fd(), err)
- }
- }()
- n, err := f.Write(data)
- if err != nil {
- return n, fmt.Errorf("writing data: %w", err)
- }
- return n, nil
- }
- // WriteLocked opens the file and then calls WriteLockedFD.
- func WriteLocked(filename string, data []byte) (int, error) {
- file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0600)
- if err != nil {
- return 0, fmt.Errorf("opening %s: %w", filename, err)
- }
- defer file.Close()
- return WriteLockedFD(file, data)
- }
- // ReadLockedFD uses the flock() syscall to safely read from an open file as
- // long as other users of the file are also using flock()-based access.
- //
- // ReadLockedFD will block until it gets lock access.
- //
- // This will read the file in full, from 0, regardless of the current
- // position and then reset the position to 0.
- //
- // For the reasons outlined best in https://lwn.net/Articles/586904/ this uses
- // flock() instead of fcntl(). The ability to lock byte ranges is not necessary
- // and flock() has better behavior.
- func ReadLockedFD(f *os.File) ([]byte, error) {
- // For the reasons outlined best in https://lwn.net/Articles/586904/ we're
- // going to use flock() instead of fcntl() because we want a whole-file lock.
- if err := syscall.Flock(int(f.Fd()), syscall.LOCK_SH); err != nil {
- return nil, fmt.Errorf("unexpected error flock()-ing with SH: %w", err)
- }
- defer func() {
- if err := syscall.Flock(int(f.Fd()), syscall.LOCK_UN); err != nil {
- log.Errorf("unexpected error flock()-ing FD %d with UN reading: %s", f.Fd(), err)
- }
- }()
- if _, err := f.Seek(0, 0); err != nil {
- return nil, fmt.Errorf("seeking to 0 before read: %w", err)
- }
- defer func() {
- if _, err := f.Seek(0, 0); err != nil {
- log.Errorf("unexpected error seeking to 0 after read on FD %d: %s", f.Fd(), err)
- }
- }()
- buf := bytes.NewBuffer(nil)
- if _, err := io.Copy(buf, f); err != nil {
- if err := syscall.Flock(int(f.Fd()), syscall.LOCK_UN); err != nil {
- log.Errorf("unexpected error flock()-ing with UN after error reading: %s", err)
- }
- return nil, fmt.Errorf("copying data out of file: %w", err)
- }
- return buf.Bytes(), nil
- }
- // ReadLocked opens the given file and then calls ReadLockedFD.
- func ReadLocked(filename string) ([]byte, error) {
- file, err := os.OpenFile(filename, os.O_RDONLY, 0600)
- if err != nil {
- return nil, fmt.Errorf("opening %s: %w", filename, err)
- }
- defer file.Close()
- return ReadLockedFD(file)
- }
|