|
|
@@ -1,124 +0,0 @@
|
|
|
-//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/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)
|
|
|
-}
|