...

Source file src/k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/files/files.go

Documentation: k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/files

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     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  package files
    18  
    19  import (
    20  	"fmt"
    21  	"os"
    22  	"path/filepath"
    23  
    24  	utilfs "k8s.io/kubernetes/pkg/util/filesystem"
    25  )
    26  
    27  const (
    28  	defaultPerm = 0755
    29  	tmpTag      = "tmp_" // additional prefix to prevent accidental collisions
    30  )
    31  
    32  // FileExists returns true if a regular file exists at `path`, false if `path` does not exist, otherwise an error
    33  func FileExists(fs utilfs.Filesystem, path string) (bool, error) {
    34  	if info, err := fs.Stat(path); err == nil {
    35  		if info.Mode().IsRegular() {
    36  			return true, nil
    37  		}
    38  		return false, fmt.Errorf("expected regular file at %q, but mode is %q", path, info.Mode().String())
    39  	} else if os.IsNotExist(err) {
    40  		return false, nil
    41  	} else {
    42  		return false, err
    43  	}
    44  }
    45  
    46  // EnsureFile ensures that a regular file exists at `path`, and if it must create the file any
    47  // necessary parent directories will also be created and the new file will be empty.
    48  func EnsureFile(fs utilfs.Filesystem, path string) error {
    49  	// if file exists, don't change it, but do report any unexpected errors
    50  	if ok, err := FileExists(fs, path); ok || err != nil {
    51  		return err
    52  	} // Assert: file does not exist
    53  
    54  	// create any necessary parents
    55  	err := fs.MkdirAll(filepath.Dir(path), defaultPerm)
    56  	if err != nil {
    57  		return err
    58  	}
    59  
    60  	// create the file
    61  	file, err := fs.Create(path)
    62  	if err != nil {
    63  		return err
    64  	}
    65  	// close the file, since we don't intend to use it yet
    66  	return file.Close()
    67  }
    68  
    69  // WriteTmpFile creates a temporary file at `path`, writes `data` into it, and fsyncs the file
    70  // Expects the parent directory to exist.
    71  func WriteTmpFile(fs utilfs.Filesystem, path string, data []byte) (tmpPath string, retErr error) {
    72  	dir := filepath.Dir(path)
    73  	prefix := tmpTag + filepath.Base(path)
    74  
    75  	// create the tmp file
    76  	tmpFile, err := fs.TempFile(dir, prefix)
    77  	if err != nil {
    78  		return "", err
    79  	}
    80  	defer func() {
    81  		// close the file, return the close error only if there haven't been any other errors
    82  		if err := tmpFile.Close(); retErr == nil {
    83  			retErr = err
    84  		}
    85  		// if there was an error writing, syncing, or closing, delete the temporary file and return the error
    86  		if retErr != nil {
    87  			if err := fs.Remove(tmpPath); err != nil {
    88  				retErr = fmt.Errorf("attempted to remove temporary file %q after error %v, but failed due to error: %v", tmpPath, retErr, err)
    89  			}
    90  			tmpPath = ""
    91  		}
    92  	}()
    93  
    94  	// Name() will be an absolute path when using utilfs.DefaultFS, because os.CreateTemp passes
    95  	// an absolute path to os.Open, and we ensure similar behavior in utilfs.FakeFS for testing.
    96  	tmpPath = tmpFile.Name()
    97  
    98  	// write data
    99  	if _, err := tmpFile.Write(data); err != nil {
   100  		return tmpPath, err
   101  	}
   102  	// sync file, to ensure it's written in case a hard reset happens
   103  	return tmpPath, tmpFile.Sync()
   104  }
   105  
   106  // ReplaceFile replaces the contents of the file at `path` with `data` by writing to a tmp file in the same
   107  // dir as `path` and renaming the tmp file over `path`. The file does not have to exist to use ReplaceFile,
   108  // but the parent directory must exist.
   109  // Note ReplaceFile calls fsync.
   110  func ReplaceFile(fs utilfs.Filesystem, path string, data []byte) error {
   111  	// write data to a temporary file
   112  	tmpPath, err := WriteTmpFile(fs, path, data)
   113  	if err != nil {
   114  		return err
   115  	}
   116  	// rename over existing file
   117  	return fs.Rename(tmpPath, path)
   118  }
   119  
   120  // DirExists returns true if a directory exists at `path`, false if `path` does not exist, otherwise an error
   121  func DirExists(fs utilfs.Filesystem, path string) (bool, error) {
   122  	if info, err := fs.Stat(path); err == nil {
   123  		if info.IsDir() {
   124  			return true, nil
   125  		}
   126  		return false, fmt.Errorf("expected dir at %q, but mode is %q", path, info.Mode().String())
   127  	} else if os.IsNotExist(err) {
   128  		return false, nil
   129  	} else {
   130  		return false, err
   131  	}
   132  }
   133  
   134  // EnsureDir ensures that a directory exists at `path`, and if it must create the directory any
   135  // necessary parent directories will also be created and the new directory will be empty.
   136  func EnsureDir(fs utilfs.Filesystem, path string) error {
   137  	// if dir exists, don't change it, but do report any unexpected errors
   138  	if ok, err := DirExists(fs, path); ok || err != nil {
   139  		return err
   140  	} // Assert: dir does not exist
   141  
   142  	// create the dir
   143  	return fs.MkdirAll(path, defaultPerm)
   144  }
   145  
   146  // WriteTempDir creates a temporary dir at `path`, writes `files` into it, and fsyncs all the files
   147  // The keys of `files` represent file names. These names must not:
   148  // - be empty
   149  // - be a path that contains more than the base name of a file (e.g. foo/bar is invalid, as is /bar)
   150  // - match `.` or `..` exactly
   151  // - be longer than 255 characters
   152  // The above validation rules are based on atomic_writer.go, though in this case are more restrictive
   153  // because we only allow a flat hierarchy.
   154  func WriteTempDir(fs utilfs.Filesystem, path string, files map[string]string) (tmpPath string, retErr error) {
   155  	// validate the filename keys; for now we only allow a flat keyset
   156  	for name := range files {
   157  		// invalidate empty names
   158  		if name == "" {
   159  			return "", fmt.Errorf("invalid file key: must not be empty: %q", name)
   160  		}
   161  		// invalidate: foo/bar and /bar
   162  		if name != filepath.Base(name) {
   163  			return "", fmt.Errorf("invalid file key %q, only base names are allowed", name)
   164  		}
   165  		// invalidate `.` and `..`
   166  		if name == "." || name == ".." {
   167  			return "", fmt.Errorf("invalid file key, may not be '.' or '..'")
   168  		}
   169  		// invalidate length > 255 characters
   170  		if len(name) > 255 {
   171  			return "", fmt.Errorf("invalid file key %q, must be less than 255 characters", name)
   172  		}
   173  	}
   174  
   175  	// write the temp directory in parent dir and return path to the tmp directory
   176  	dir := filepath.Dir(path)
   177  	prefix := tmpTag + filepath.Base(path)
   178  
   179  	// create the tmp dir
   180  	var err error
   181  	tmpPath, err = fs.TempDir(dir, prefix)
   182  	if err != nil {
   183  		return "", err
   184  	}
   185  	// be sure to clean up if there was an error
   186  	defer func() {
   187  		if retErr != nil {
   188  			if err := fs.RemoveAll(tmpPath); err != nil {
   189  				retErr = fmt.Errorf("attempted to remove temporary directory %q after error %v, but failed due to error: %v", tmpPath, retErr, err)
   190  			}
   191  		}
   192  	}()
   193  	// write data
   194  	for name, data := range files {
   195  		// create the file
   196  		file, err := fs.Create(filepath.Join(tmpPath, name))
   197  		if err != nil {
   198  			return tmpPath, err
   199  		}
   200  		// be sure to close the file when we're done
   201  		defer func() {
   202  			// close the file when we're done, don't overwrite primary retErr if close fails
   203  			if err := file.Close(); retErr == nil {
   204  				retErr = err
   205  			}
   206  		}()
   207  		// write the file
   208  		if _, err := file.Write([]byte(data)); err != nil {
   209  			return tmpPath, err
   210  		}
   211  		// sync the file, to ensure it's written in case a hard reset happens
   212  		if err := file.Sync(); err != nil {
   213  			return tmpPath, err
   214  		}
   215  	}
   216  	return tmpPath, nil
   217  }
   218  
   219  // ReplaceDir replaces the contents of the dir at `path` with `files` by writing to a tmp dir in the same
   220  // dir as `path` and renaming the tmp dir over `path`. The dir does not have to exist to use ReplaceDir.
   221  func ReplaceDir(fs utilfs.Filesystem, path string, files map[string]string) error {
   222  	// write data to a temporary directory
   223  	tmpPath, err := WriteTempDir(fs, path, files)
   224  	if err != nil {
   225  		return err
   226  	}
   227  	// rename over target directory
   228  	return fs.Rename(tmpPath, path)
   229  }
   230  

View as plain text