github.com/searKing/golang/go@v1.2.117/sync/filelock/filelock_osfile.go (about)

     1  // Copyright 2023 The searKing Author. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package filelock
     6  
     7  import (
     8  	"io"
     9  	"io/fs"
    10  	"os"
    11  
    12  	os_ "github.com/searKing/golang/go/os"
    13  )
    14  
    15  // OpenFile is like os.OpenFile, but returns a locked file.
    16  // If flag includes os.O_WRONLY or os.O_RDWR, the file is write-locked;
    17  // otherwise, it is read-locked.
    18  func OpenFile(name string, flag int, perm fs.FileMode) (*LockedFile[*os.File], error) {
    19  	file, err := openFile(name, flag, perm)
    20  	if err != nil {
    21  		return nil, err
    22  	}
    23  	return NewLockedFile(file)
    24  }
    25  
    26  // Open is like os.Open, but returns a read-locked file.
    27  func Open(name string) (*LockedFile[*os.File], error) {
    28  	return OpenFile(name, os.O_RDONLY, 0)
    29  }
    30  
    31  // Create is like os.Create, but returns a write-locked file.
    32  func Create(name string) (*LockedFile[*os.File], error) {
    33  	return OpenFile(name, os_.DefaultFlagCreateTruncate, os_.DefaultPermissionFile)
    34  }
    35  
    36  // Edit creates the named file with mode 0666 (before umask),
    37  // but does not truncate existing contents.
    38  //
    39  // If Edit succeeds, methods on the returned File can be used for I/O.
    40  // The associated file descriptor has mode O_RDWR and the file is write-locked.
    41  func Edit(name string) (*LockedFile[*os.File], error) {
    42  	return OpenFile(name, os_.DefaultFlagCreateIfNotExist, os_.DefaultPermissionFile)
    43  }
    44  
    45  // Read opens the named file with a read-lock and returns its contents.
    46  func Read(name string) ([]byte, error) {
    47  	f, err := Open(name)
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  	defer f.Close()
    52  
    53  	return io.ReadAll(f.File)
    54  }
    55  
    56  // Write opens the named file (creating it with the given permissions if needed),
    57  // then write-locks it and overwrites it with the given content.
    58  func Write(name string, content io.Reader, perm fs.FileMode) (err error) {
    59  	f, err := OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
    60  	if err != nil {
    61  		return err
    62  	}
    63  
    64  	_, err = io.Copy(f.File, content)
    65  	if closeErr := f.Close(); err == nil {
    66  		err = closeErr
    67  	}
    68  	return err
    69  }
    70  
    71  // Transform invokes t with the result of reading the named file, with its lock
    72  // still held.
    73  //
    74  // If t returns a nil error, Transform then writes the returned contents back to
    75  // the file, making a best effort to preserve existing contents on error.
    76  //
    77  // t must not modify the slice passed to it.
    78  func Transform(name string, t func([]byte) ([]byte, error)) (err error) {
    79  	f, err := Edit(name)
    80  	if err != nil {
    81  		return err
    82  	}
    83  	defer f.Close()
    84  
    85  	old, err := io.ReadAll(f.File)
    86  	if err != nil {
    87  		return err
    88  	}
    89  
    90  	new, err := t(old)
    91  	if err != nil {
    92  		return err
    93  	}
    94  
    95  	if len(new) > len(old) {
    96  		// The overall file size is increasing, so write the tail first: if we're
    97  		// about to run out of space on the disk, we would rather detect that
    98  		// failure before we have overwritten the original contents.
    99  		if _, err := f.File.WriteAt(new[len(old):], int64(len(old))); err != nil {
   100  			// Make a best effort to remove the incomplete tail.
   101  			f.File.Truncate(int64(len(old)))
   102  			return err
   103  		}
   104  	}
   105  
   106  	// We're about to overwrite the old contents. In case of failure, make a best
   107  	// effort to roll back before we close the file.
   108  	defer func() {
   109  		if err != nil {
   110  			if _, err := f.File.WriteAt(old, 0); err == nil {
   111  				f.File.Truncate(int64(len(old)))
   112  			}
   113  		}
   114  	}()
   115  
   116  	if len(new) >= len(old) {
   117  		if _, err := f.File.WriteAt(new[:len(old)], 0); err != nil {
   118  			return err
   119  		}
   120  	} else {
   121  		if _, err := f.File.WriteAt(new, 0); err != nil {
   122  			return err
   123  		}
   124  		// The overall file size is decreasing, so shrink the file to its final size
   125  		// after writing. We do this after writing (instead of before) so that if
   126  		// the write fails, enough filesystem space will likely still be reserved
   127  		// to contain the previous contents.
   128  		if err := f.File.Truncate(int64(len(new))); err != nil {
   129  			return err
   130  		}
   131  	}
   132  
   133  	return nil
   134  }