github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/cmds/core/chmod/chmod.go (about) 1 // Copyright 2016-2020 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // chmod changes mode bits (e.g. permissions) of a file. 6 // 7 // Synopsis: 8 // chmod MODE FILE... 9 // 10 // Desription: 11 // MODE is a three character octal value or a string like a=rwx 12 package main 13 14 import ( 15 "flag" 16 "fmt" 17 "log" 18 "os" 19 "path/filepath" 20 "regexp" 21 "strconv" 22 "strings" 23 ) 24 25 const special = 99999 26 27 var ( 28 recursive bool 29 reference string 30 ) 31 32 func init() { 33 flag.BoolVar(&recursive, 34 "R", 35 false, 36 "do changes recursively") 37 38 flag.BoolVar(&recursive, 39 "recursive", 40 false, 41 "do changes recursively") 42 43 flag.StringVar(&reference, 44 "reference", 45 "", 46 "use mode from reference file") 47 } 48 49 func changeMode(path string, mode os.FileMode, octval uint64, mask uint64) (err error) { 50 // A special value for mask means the mode is fully described 51 if mask == special { 52 return os.Chmod(path, mode) 53 } 54 55 var info os.FileInfo 56 info, err = os.Stat(path) 57 if err != nil { 58 log.Printf("%v", err) 59 return 60 } 61 62 mode = info.Mode() & os.FileMode(mask) 63 mode = mode | os.FileMode(octval) 64 65 return os.Chmod(path, mode) 66 } 67 68 func calculateMode(modeString string) (mode os.FileMode, octval uint64, mask uint64) { 69 var err error 70 octval, err = strconv.ParseUint(modeString, 8, 32) 71 if err == nil { 72 if octval > 0777 { 73 log.Fatalf("Invalid octal value %0o. Value should be less than or equal to 0777.", octval) 74 } 75 // a fully described octal mode was supplied, signal that with a special value for mask 76 mask = special 77 mode = os.FileMode(octval) 78 return 79 } 80 81 reMode := regexp.MustCompile("^([ugoa]+)([-+=])(.*)") 82 m := reMode.FindStringSubmatch(modeString) 83 // Test for mode strings with invalid characters. 84 // This can't be done in the first regexp: if the match for m[3] is restricted to [rwx]*, 85 // `a=9` and `a=` would be indistinguishable: m[3] would be empty. 86 // `a=` is a valid (but destructive) operation. Do not turn a typo into that. 87 reMode = regexp.MustCompile("^[rwx]*$") 88 if len(m) < 3 || !reMode.MatchString(m[3]) { 89 log.Fatalf("Unable to decode mode %q. Please use an octal value or a valid mode string.", modeString) 90 } 91 92 // m[3] is [rwx]{0,3} 93 var octvalDigit uint64 94 if strings.Contains(m[3], "r") { 95 octvalDigit += 4 96 } 97 if strings.Contains(m[3], "w") { 98 octvalDigit += 2 99 } 100 if strings.Contains(m[3], "x") { 101 octvalDigit++ 102 } 103 104 // m[2] is [-+=] 105 var operator = m[2] 106 107 // Use a mask so that we do not overwrite permissions for a user/group that was not specified 108 mask = 0777 109 110 // For "-", invert octvalDigit before applying the mask 111 if operator == "-" { 112 octvalDigit = 7 - octvalDigit 113 } 114 115 // m[1] is [ugoa]+ 116 if strings.Contains(m[1], "o") || strings.Contains(m[1], "a") { 117 octval += octvalDigit 118 mask = mask & 0770 119 } 120 if strings.Contains(m[1], "g") || strings.Contains(m[1], "a") { 121 octval += octvalDigit << 3 122 mask = mask & 0707 123 } 124 if strings.Contains(m[1], "u") || strings.Contains(m[1], "a") { 125 octval += octvalDigit << 6 126 mask = mask & 0077 127 } 128 129 // For "+" the mask is superfluous, reset it 130 if operator == "+" { 131 mask = 0777 132 } 133 134 // The mode is fully described, signal that with a special value for mask 135 if operator == "=" && strings.Contains(m[1], "a") { 136 mask = special 137 mode = os.FileMode(octval) 138 } 139 return 140 } 141 142 func main() { 143 flag.Parse() 144 if len(flag.Args()) < 1 { 145 fmt.Fprintf(os.Stderr, "Usage of %s: [mode] filepath\n", os.Args[0]) 146 flag.PrintDefaults() 147 os.Exit(1) 148 } 149 150 if len(flag.Args()) < 2 && reference == "" { 151 fmt.Fprintf(os.Stderr, "Usage of %s: [mode] filepath\n", os.Args[0]) 152 flag.PrintDefaults() 153 os.Exit(1) 154 } 155 156 var mode os.FileMode 157 var octval, mask uint64 158 var fileList []string 159 160 if reference != "" { 161 fi, err := os.Stat(reference) 162 if err != nil { 163 log.Fatalf("bad reference file: %v", err) 164 165 } 166 mask = special 167 mode = fi.Mode() 168 fileList = flag.Args() 169 } else { 170 mode, octval, mask = calculateMode(flag.Args()[0]) 171 fileList = flag.Args()[1:] 172 } 173 174 var exitError bool 175 for _, name := range fileList { 176 if recursive { 177 err := filepath.Walk(name, func(path string, 178 info os.FileInfo, 179 err error) error { 180 return changeMode(path, mode, octval, mask) 181 }) 182 if err != nil { 183 log.Printf("%v", err) 184 exitError = true 185 } 186 } else { 187 if err := changeMode(name, mode, octval, mask); err != nil { 188 log.Printf("%v", err) 189 exitError = true 190 } 191 } 192 } 193 if exitError { 194 os.Exit(1) 195 } 196 }