files.go 7.5 KB

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