storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/safe/safe.go (about)

     1  /*
     2   * MinIO Cloud Storage (C) 2015-2016 MinIO, Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  // NOTE - Rename() not guaranteed to be safe on all filesystems which are not fully POSIX compatible
    18  
    19  package safe
    20  
    21  import (
    22  	"errors"
    23  	"io/ioutil"
    24  	"os"
    25  	"path/filepath"
    26  )
    27  
    28  // File represents safe file descriptor.
    29  type File struct {
    30  	name    string
    31  	tmpfile *os.File
    32  	closed  bool
    33  	aborted bool
    34  }
    35  
    36  // Write writes len(b) bytes to the temporary File.  In case of error, the temporary file is removed.
    37  func (file *File) Write(b []byte) (n int, err error) {
    38  	if file.closed {
    39  		err = errors.New("write on closed file")
    40  		return
    41  	}
    42  	if file.aborted {
    43  		err = errors.New("write on aborted file")
    44  		return
    45  	}
    46  
    47  	defer func() {
    48  		if err != nil {
    49  			os.Remove(file.tmpfile.Name())
    50  			file.aborted = true
    51  		}
    52  	}()
    53  
    54  	n, err = file.tmpfile.Write(b)
    55  	return
    56  }
    57  
    58  // Close closes the temporary File and renames to the named file.  In case of error, the temporary file is removed.
    59  func (file *File) Close() (err error) {
    60  	defer func() {
    61  		if err != nil {
    62  			os.Remove(file.tmpfile.Name())
    63  			file.aborted = true
    64  		}
    65  	}()
    66  
    67  	if file.closed {
    68  		err = errors.New("close on closed file")
    69  		return
    70  	}
    71  	if file.aborted {
    72  		err = errors.New("close on aborted file")
    73  		return
    74  	}
    75  
    76  	if err = file.tmpfile.Close(); err != nil {
    77  		return
    78  	}
    79  
    80  	err = os.Rename(file.tmpfile.Name(), file.name)
    81  
    82  	file.closed = true
    83  	return
    84  }
    85  
    86  // Abort aborts the temporary File by closing and removing the temporary file.
    87  func (file *File) Abort() (err error) {
    88  	if file.closed {
    89  		err = errors.New("abort on closed file")
    90  		return
    91  	}
    92  	if file.aborted {
    93  		err = errors.New("abort on aborted file")
    94  		return
    95  	}
    96  
    97  	file.tmpfile.Close()
    98  	err = os.Remove(file.tmpfile.Name())
    99  	file.aborted = true
   100  	return
   101  }
   102  
   103  // CreateFile creates the named file safely from unique temporary file.
   104  // The temporary file is renamed to the named file upon successful close
   105  // to safeguard intermediate state in the named file.  The temporary file
   106  // is created in the name of the named file with suffixed unique number
   107  // and prefixed "$tmpfile" string.  While creating the temporary file,
   108  // missing parent directories are also created.  The temporary file is
   109  // removed if case of any intermediate failure.  Not removed temporary
   110  // files can be cleaned up by identifying them using "$tmpfile" prefix
   111  // string.
   112  func CreateFile(name string) (*File, error) {
   113  	// ioutil.TempFile() fails if parent directory is missing.
   114  	// Create parent directory to avoid such error.
   115  	dname := filepath.Dir(name)
   116  	if err := os.MkdirAll(dname, 0700); err != nil {
   117  		return nil, err
   118  	}
   119  
   120  	fname := filepath.Base(name)
   121  	tmpfile, err := ioutil.TempFile(dname, "$tmpfile."+fname+".")
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  
   126  	if err = os.Chmod(tmpfile.Name(), 0600); err != nil {
   127  		if rerr := os.Remove(tmpfile.Name()); rerr != nil {
   128  			err = rerr
   129  		}
   130  		return nil, err
   131  	}
   132  
   133  	return &File{name: name, tmpfile: tmpfile}, nil
   134  }