github.com/IBM/fsgo@v0.0.0-20220920202152-e16fd2119d49/ioutil.go (about) 1 // Copyright 2022 IBM Inc. All rights reserved 2 // Copyright © 2014 Steve Francia <spf@spf13.com> 3 // 4 // SPDX-License-Identifier: Apache2.0 5 package fsgo 6 7 import ( 8 "bytes" 9 "io" 10 "os" 11 "path/filepath" 12 "sort" 13 "strconv" 14 "strings" 15 "sync" 16 "time" 17 ) 18 19 // byName implements sort.Interface. 20 type byName []os.FileInfo 21 22 func (f byName) Len() int { return len(f) } 23 func (f byName) Less(i, j int) bool { return f[i].Name() < f[j].Name() } 24 func (f byName) Swap(i, j int) { f[i], f[j] = f[j], f[i] } 25 26 // ReadDir reads the directory named by dirname and returns 27 // a list of sorted directory entries. 28 func (a FsGo) ReadDir(dirname string) ([]os.FileInfo, error) { 29 return ReadDir(a.Fs, dirname) 30 } 31 32 func ReadDir(fs Fs, dirname string) ([]os.FileInfo, error) { 33 f, err := fs.Open(dirname) 34 if err != nil { 35 return nil, err 36 } 37 list, err := f.Readdir(-1) 38 f.Close() 39 if err != nil { 40 return nil, err 41 } 42 sort.Sort(byName(list)) 43 return list, nil 44 } 45 46 // ReadFile reads the file named by filename and returns the contents. 47 // A successful call returns err == nil, not err == EOF. Because ReadFile 48 // reads the whole file, it does not treat an EOF from Read as an error 49 // to be reported. 50 func (a FsGo) ReadFile(filename string) ([]byte, error) { 51 return ReadFile(a.Fs, filename) 52 } 53 54 func ReadFile(fs Fs, filename string) ([]byte, error) { 55 f, err := fs.Open(filename) 56 if err != nil { 57 return nil, err 58 } 59 defer f.Close() 60 // It's a good but not certain bet that FileInfo will tell us exactly how much to 61 // read, so let's try it but be prepared for the answer to be wrong. 62 var n int64 63 64 if fi, err := f.Stat(); err == nil { 65 // Don't preallocate a huge buffer, just in case. 66 if size := fi.Size(); size < 1e9 { 67 n = size 68 } 69 } 70 // As initial capacity for readAll, use n + a little extra in case Size is zero, 71 // and to avoid another allocation after Read has filled the buffer. The readAll 72 // call will read into its allocated internal buffer cheaply. If the size was 73 // wrong, we'll either waste some space off the end or reallocate as needed, but 74 // in the overwhelmingly common case we'll get it just right. 75 return readAll(f, n+bytes.MinRead) 76 } 77 78 // readAll reads from r until an error or EOF and returns the data it read 79 // from the internal buffer allocated with a specified capacity. 80 func readAll(r io.Reader, capacity int64) (b []byte, err error) { 81 buf := bytes.NewBuffer(make([]byte, 0, capacity)) 82 // If the buffer overflows, we will get bytes.ErrTooLarge. 83 // Return that as an error. Any other panic remains. 84 defer func() { 85 e := recover() 86 if e == nil { 87 return 88 } 89 if panicErr, ok := e.(error); ok && panicErr == bytes.ErrTooLarge { 90 err = panicErr 91 } else { 92 panic(e) 93 } 94 }() 95 _, err = buf.ReadFrom(r) 96 return buf.Bytes(), err 97 } 98 99 // ReadAll reads from r until an error or EOF and returns the data it read. 100 // A successful call returns err == nil, not err == EOF. Because ReadAll is 101 // defined to read from src until EOF, it does not treat an EOF from Read 102 // as an error to be reported. 103 func ReadAll(r io.Reader) ([]byte, error) { 104 return readAll(r, bytes.MinRead) 105 } 106 107 // WriteFile writes data to a file named by filename. 108 // If the file does not exist, WriteFile creates it with permissions perm; 109 // otherwise WriteFile truncates it before writing. 110 func (a FsGo) WriteFile(filename string, data []byte, perm os.FileMode) error { 111 return WriteFile(a.Fs, filename, data, perm) 112 } 113 114 func WriteFile(fs Fs, filename string, data []byte, perm os.FileMode) error { 115 f, err := fs.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) 116 if err != nil { 117 return err 118 } 119 n, err := f.Write(data) 120 if err == nil && n < len(data) { 121 err = io.ErrShortWrite 122 } 123 if err1 := f.Close(); err == nil { 124 err = err1 125 } 126 return err 127 } 128 129 // Random number state. 130 // We generate random temporary file names so that there's a good 131 // chance the file doesn't exist yet - keeps the number of tries in 132 // TempFile to a minimum. 133 var randNum uint32 134 var randmu sync.Mutex 135 136 func reseed() uint32 { 137 return uint32(time.Now().UnixNano() + int64(os.Getpid())) 138 } 139 140 func nextRandom() string { 141 randmu.Lock() 142 r := randNum 143 if r == 0 { 144 r = reseed() 145 } 146 r = r*1664525 + 1013904223 // constants from Numerical Recipes 147 randNum = r 148 randmu.Unlock() 149 return strconv.Itoa(int(1e9 + r%1e9))[1:] 150 } 151 152 // TempFile creates a new temporary file in the directory dir, 153 // opens the file for reading and writing, and returns the resulting *os.File. 154 // The filename is generated by taking pattern and adding a random 155 // string to the end. If pattern includes a "*", the random string 156 // replaces the last "*". 157 // If dir is the empty string, TempFile uses the default directory 158 // for temporary files (see os.TempDir). 159 // Multiple programs calling TempFile simultaneously 160 // will not choose the same file. The caller can use f.Name() 161 // to find the pathname of the file. It is the caller's responsibility 162 // to remove the file when no longer needed. 163 func (a FsGo) TempFile(dir, pattern string) (f File, err error) { 164 return TempFile(a.Fs, dir, pattern) 165 } 166 167 func TempFile(fs Fs, dir, pattern string) (f File, err error) { 168 if dir == "" { 169 dir = os.TempDir() 170 } 171 172 var prefix, suffix string 173 if pos := strings.LastIndex(pattern, "*"); pos != -1 { 174 prefix, suffix = pattern[:pos], pattern[pos+1:] 175 } else { 176 prefix = pattern 177 } 178 179 nconflict := 0 180 for i := 0; i < 10000; i++ { 181 name := filepath.Join(dir, prefix+nextRandom()+suffix) 182 f, err = fs.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) 183 if os.IsExist(err) { 184 if nconflict++; nconflict > 10 { 185 randmu.Lock() 186 randNum = reseed() 187 randmu.Unlock() 188 } 189 continue 190 } 191 break 192 } 193 return 194 } 195 196 // TempDir creates a new temporary directory in the directory dir 197 // with a name beginning with prefix and returns the path of the 198 // new directory. If dir is the empty string, TempDir uses the 199 // default directory for temporary files (see os.TempDir). 200 // Multiple programs calling TempDir simultaneously 201 // will not choose the same directory. It is the caller's responsibility 202 // to remove the directory when no longer needed. 203 func (a FsGo) TempDir(dir, prefix string) (name string, err error) { 204 return TempDir(a.Fs, dir, prefix) 205 } 206 func TempDir(fs Fs, dir, prefix string) (name string, err error) { 207 if dir == "" { 208 dir = os.TempDir() 209 } 210 211 nconflict := 0 212 for i := 0; i < 10000; i++ { 213 try := filepath.Join(dir, prefix+nextRandom()) 214 err = fs.Mkdir(try, 0700) 215 if os.IsExist(err) { 216 if nconflict++; nconflict > 10 { 217 randmu.Lock() 218 randNum = reseed() 219 randmu.Unlock() 220 } 221 continue 222 } 223 if err == nil { 224 name = try 225 } 226 break 227 } 228 return 229 }