github.com/git-amp/amp-sdk-go@v0.7.5/stdlib/utils/files.go (about) 1 package utils 2 3 import ( 4 "fmt" 5 "io" 6 "os" 7 "path" 8 "regexp" 9 "strconv" 10 "strings" 11 "time" 12 13 "github.com/git-amp/amp-sdk-go/stdlib/bufs" 14 "github.com/git-amp/amp-sdk-go/stdlib/errors" 15 ) 16 17 var ( 18 // DefaultDirPerms expresses the default mode of dir creation. 19 DefaultDirPerms = os.FileMode(0755) 20 ) 21 22 // EnsureDirAndMaxPerms ensures that the given path exists, that it's a directory, 23 // and that it has permissions that are no more permissive than the given ones. 24 // 25 // - If the path does not exist, it is created 26 // - If the path exists, but is not a directory, an error is returned 27 // - If the path exists, and is a directory, but has the wrong perms, it is chmod'ed 28 func EnsureDirAndMaxPerms(path string, perms os.FileMode) error { 29 stat, err := os.Stat(path) 30 if err != nil && !os.IsNotExist(err) { 31 // Regular error 32 return err 33 } else if os.IsNotExist(err) { 34 // Dir doesn't exist, create it with desired perms 35 return os.MkdirAll(path, perms) 36 } else if !stat.IsDir() { 37 // Path exists, but it's a file, so don't clobber 38 return errors.Errorf("%v already exists and is not a directory", path) 39 } else if stat.Mode() != perms { 40 // Dir exists, but wrong perms, so chmod 41 return os.Chmod(path, (stat.Mode() & perms)) 42 } 43 return nil 44 } 45 46 // WriteFileWithMaxPerms writes `data` to `path` and ensures that 47 // the file has permissions that are no more permissive than the given ones. 48 func WriteFileWithMaxPerms(path string, data []byte, perms os.FileMode) error { 49 f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, perms) 50 if err != nil { 51 return err 52 } 53 defer f.Close() 54 err = EnsureFileMaxPerms(f, perms) 55 if err != nil { 56 return err 57 } 58 _, err = f.Write(data) 59 return err 60 } 61 62 // CopyFileWithMaxPerms copies the file at `srcPath` to `dstPath` 63 // and ensures that it has permissions that are no more permissive than the given ones. 64 func CopyFileWithMaxPerms(srcPath, dstPath string, perms os.FileMode) error { 65 src, err := os.Open(srcPath) 66 if err != nil { 67 return errors.Wrap(err, "could not open source file") 68 } 69 defer src.Close() 70 71 dst, err := os.OpenFile(dstPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, perms) 72 if err != nil { 73 return errors.Wrap(err, "could not open destination file") 74 } 75 defer dst.Close() 76 77 err = EnsureFileMaxPerms(dst, perms) 78 if err != nil { 79 return errors.Wrap(err, "could not set file permissions") 80 } 81 82 _, err = io.Copy(dst, src) 83 return errors.Wrap(err, "could not copy file contents") 84 } 85 86 // EnsureFileMaxPerms ensures that the given file has permissions 87 // that are no more permissive than the given ones. 88 func EnsureFileMaxPerms(file *os.File, perms os.FileMode) error { 89 stat, err := file.Stat() 90 if err != nil { 91 return err 92 } 93 if stat.Mode() == perms { 94 return nil 95 } 96 return file.Chmod(stat.Mode() & perms) 97 } 98 99 // EnsureFilepathMaxPerms ensures that the file at the given filepath 100 // has permissions that are no more permissive than the given ones. 101 func EnsureFilepathMaxPerms(filepath string, perms os.FileMode) error { 102 dst, err := os.OpenFile(filepath, os.O_RDWR, perms) 103 if err != nil { 104 return err 105 } 106 defer dst.Close() 107 108 return EnsureFileMaxPerms(dst, perms) 109 } 110 111 type FileSize uint64 112 113 var fsregex = regexp.MustCompile(`(\d+\.?\d*)(tb|gb|mb|kb|b)?`) 114 115 const ( 116 KB FileSize = 1024 117 MB FileSize = 1000000 118 GB FileSize = 1000 * MB 119 TB FileSize = 1000 * GB 120 ) 121 122 func ParseFileSize(s string) (FileSize, error) { 123 var fs FileSize 124 err := fs.UnmarshalText([]byte(s)) 125 return fs, err 126 } 127 128 func (s FileSize) MarshalText() ([]byte, error) { 129 if s > TB { 130 return []byte(fmt.Sprintf("%.2ftb", float64(s)/float64(TB))), nil 131 } else if s > GB { 132 return []byte(fmt.Sprintf("%.2fgb", float64(s)/float64(GB))), nil 133 } else if s > MB { 134 return []byte(fmt.Sprintf("%.2fmb", float64(s)/float64(MB))), nil 135 } else if s > KB { 136 return []byte(fmt.Sprintf("%.2fkb", float64(s)/float64(KB))), nil 137 } 138 return []byte(fmt.Sprintf("%db", s)), nil 139 } 140 141 func (s *FileSize) UnmarshalText(bs []byte) error { 142 matches := fsregex.FindAllStringSubmatch(strings.ToLower(string(bs)), -1) 143 if len(matches) != 1 { 144 return errors.Errorf(`bad filesize: "%v"`, string(bs)) 145 } else if len(matches[0]) != 3 { 146 return errors.Errorf(`bad filesize: "%v"`, string(bs)) 147 } 148 var ( 149 num = matches[0][1] 150 unit = matches[0][2] 151 ) 152 bytes, err := strconv.ParseFloat(num, 64) 153 if err != nil { 154 return err 155 } 156 157 switch unit { 158 case "", "b": 159 case "kb": 160 bytes *= float64(KB) 161 case "mb": 162 bytes *= float64(MB) 163 case "gb": 164 bytes *= float64(GB) 165 case "tb": 166 bytes *= float64(TB) 167 default: 168 return errors.Errorf(`bad filesize unit: "%v"`, unit) 169 } 170 *s = FileSize(bytes) 171 return nil 172 } 173 174 func (s FileSize) String() string { 175 str, _ := s.MarshalText() 176 return string(str) 177 } 178 179 // ExpandAndCheckPath parses/expands the given path and then verifies it's existence or non-existence, 180 // depending on autoCreate and returning the the expanded path. 181 // 182 // If autoCreate == true, an error is returned if the dir didn't exist and failed to be created. 183 // 184 // If autoCreate == false, an error is returned if the dir doesn't exist. 185 func ExpandAndCheckPath( 186 pathname string, 187 autoCreate bool, 188 ) (string, error) { 189 190 var err error 191 if err != nil { 192 err = errors.Errorf("error expanding '%s'", pathname) 193 } else { 194 _, err = os.Stat(pathname) 195 if err != nil && os.IsNotExist(err) { 196 if autoCreate { 197 err = os.MkdirAll(pathname, DefaultDirPerms) 198 } else { 199 err = errors.Errorf("path '%s' does not exist", pathname) 200 } 201 } 202 } 203 204 if err != nil { 205 return "", err 206 } 207 208 return pathname, nil 209 } 210 211 // CreateNewDir creates the specified dir (and returns an error if the dir already exists) 212 // 213 // If dirPath is absolute then basePath is ignored. 214 // Returns the effective pathname. 215 func CreateNewDir(basePath, dirPath string) (string, error) { 216 var pathname string 217 218 if path.IsAbs(dirPath) { 219 pathname = dirPath 220 } else { 221 pathname = path.Join(basePath, dirPath) 222 } 223 224 if _, err := os.Stat(pathname); !os.IsNotExist(err) { 225 return "", errors.Errorf("for safety, the path '%s' must not already exist", pathname) 226 } 227 228 if err := os.MkdirAll(pathname, DefaultDirPerms); err != nil { 229 return "", err 230 } 231 232 return pathname, nil 233 } 234 235 // GetExePath returns the pathname of the dir containing the host exe 236 func GetExePath() (string, error) { 237 hostExe, err := os.Executable() 238 if err != nil { 239 return "", err 240 } 241 hostDir := path.Dir(hostExe) 242 return hostDir, nil 243 } 244 245 var remapCharset = map[rune]rune{ 246 ' ': '-', 247 '.': '-', 248 '?': '-', 249 '\\': '+', 250 '/': '+', 251 '&': '+', 252 } 253 254 // MakeFSFriendly makes a given string safe to use for a file system. 255 // If suffix is given, the hex encoding of those bytes are appended after a space. 256 func MakeFSFriendly(name string, suffix []byte) string { 257 258 var b strings.Builder 259 for _, r := range name { 260 if replace, ok := remapCharset[r]; ok { 261 if replace != 0 { 262 b.WriteRune(replace) 263 } 264 } else { 265 b.WriteRune(r) 266 } 267 } 268 269 if len(suffix) > 0 { 270 b.WriteString(" ") 271 b.WriteString(bufs.Base32Encoding.EncodeToString(suffix)) 272 } 273 274 friendlyName := b.String() 275 return friendlyName 276 } 277 278 // CreateTemp creates a temporary file with the given file mode flags using the given name pattern. 279 // The name pattern should contain a '*' to where a random alphanumeric should go. 280 func CreateTemp(dir, pattern string, fileFlags int) (ofile *os.File, err error) { 281 if dir == "" { 282 dir = os.TempDir() 283 } 284 285 var prefix, suffix string 286 if pos := strings.LastIndexByte(pattern, '*'); pos != -1 { 287 prefix, suffix = pattern[:pos], pattern[pos+1:] 288 } else { 289 prefix = pattern 290 } 291 prefix = path.Join(dir, prefix) 292 293 for { 294 msec := time.Now().UnixMicro() 295 name := prefix + strconv.FormatInt(int64(msec), 32) + suffix 296 ofile, err = os.OpenFile(name, os.O_CREATE|fileFlags, 0600) 297 if os.IsExist(err) { 298 continue 299 } 300 break 301 } 302 return 303 }