github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/cmds/core/elvish/util/claim.go (about) 1 package util 2 3 import ( 4 "errors" 5 "io/ioutil" 6 "os" 7 "strconv" 8 "strings" 9 ) 10 11 // ErrClaimFileBadPattern is thrown when the pattern argument passed to 12 // ClaimFile does not contain exactly one asterisk. 13 var ErrClaimFileBadPattern = errors.New("ClaimFile: pattern must contain exactly one asterisk") 14 15 // ClaimFile takes a directory and a pattern string containing exactly one 16 // asterisk (e.g. "a*.log"). It opens a file in that directory, with a filename 17 // matching the template, with "*" replaced by a number. That number is one plus 18 // the largest of all existing files matching the template. If no such file 19 // exists, "*" is replaced by 1. The file is opened for read and write, with 20 // permission 0666 (before umask). 21 // 22 // For example, if the directory /tmp/elvish contains a1.log, a2.log and a9.log, 23 // calling ClaimFile("/tmp/elvish", "a*.log") will open a10.log. If the 24 // directory has no files matching the pattern, this same call will open a1.log. 25 // 26 // This function is useful for automatically determining unique names for log 27 // files. Unique filenames can also be derived by embedding the PID, but using 28 // this function preserves the chronical order of the files. 29 // 30 // This function is concurrency-safe: it always opens a new, unclaimed file and 31 // is not subject to race condition. 32 func ClaimFile(dir, pattern string) (*os.File, error) { 33 if strings.Count(pattern, "*") != 1 { 34 return nil, ErrClaimFileBadPattern 35 } 36 asterisk := strings.IndexByte(pattern, '*') 37 prefix, suffix := pattern[:asterisk], pattern[asterisk+1:] 38 files, err := ioutil.ReadDir(dir) 39 if err != nil { 40 return nil, err 41 } 42 max := 0 43 for _, file := range files { 44 name := file.Name() 45 if len(name) > len(prefix)+len(suffix) && strings.HasPrefix(name, prefix) && strings.HasSuffix(name, suffix) { 46 core := name[len(prefix) : len(name)-len(suffix)] 47 if coreNum, err := strconv.Atoi(core); err == nil { 48 if max < coreNum { 49 max = coreNum 50 } 51 } 52 } 53 } 54 55 for i := max + 1; ; i++ { 56 name := prefix + strconv.Itoa(i) + suffix 57 f, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE, 0666) 58 if err == nil { 59 return f, nil 60 } 61 if !os.IsExist(err) { 62 return nil, err 63 } 64 } 65 }