github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/snap/squashfs/stat.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2017 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package squashfs 21 22 import ( 23 "fmt" 24 "os" 25 "path/filepath" 26 "strconv" 27 "strings" 28 "time" 29 ) 30 31 type SnapFileOwner struct { 32 UID uint32 33 GID uint32 34 } 35 36 type stat struct { 37 path string 38 size int64 39 mode os.FileMode 40 mtime time.Time 41 user string 42 group string 43 } 44 45 func (s stat) Name() string { return filepath.Base(s.path) } 46 func (s stat) Size() int64 { return s.size } 47 func (s stat) Mode() os.FileMode { return s.mode } 48 func (s stat) ModTime() time.Time { return s.mtime } 49 func (s stat) IsDir() bool { return s.mode.IsDir() } 50 func (s stat) Sys() interface{} { return nil } 51 func (s stat) Path() string { return s.path } // not path of os.FileInfo 52 53 const minLen = len("drwxrwxr-x u/g 53595 2017-12-08 11:19 .") 54 55 func fromRaw(raw []byte) (*stat, error) { 56 if len(raw) < minLen { 57 return nil, errBadLine(raw) 58 } 59 60 st := &stat{} 61 62 parsers := []func([]byte) (int, error){ 63 // first, the file mode, e.g. "-rwxr-xr-x" 64 st.parseMode, 65 // next, user/group info 66 st.parseOwner, 67 // next'll come the size or the node type 68 st.parseSize, 69 // and then the time 70 st.parseTimeUTC, 71 // and finally the path 72 st.parsePath, 73 } 74 p := 0 75 for _, parser := range parsers { 76 n, err := parser(raw[p:]) 77 if err != nil { 78 return nil, err 79 } 80 p += n 81 if p < len(raw) && raw[p] != ' ' { 82 return nil, errBadLine(raw) 83 } 84 p++ 85 } 86 87 if st.mode&os.ModeSymlink != 0 { 88 // the symlink *could* be from a file called "foo -> bar" to 89 // another called "baz -> quux" in which case the following 90 // would be wrong, but so be it. 91 92 idx := strings.Index(st.path, " -> ") 93 if idx < 0 { 94 return nil, errBadPath(raw) 95 } 96 st.path = st.path[:idx] 97 } 98 99 return st, nil 100 } 101 102 type statError struct { 103 part string 104 raw []byte 105 } 106 107 func (e statError) Error() string { 108 return fmt.Sprintf("cannot parse %s: %q", e.part, e.raw) 109 } 110 111 func errBadLine(raw []byte) statError { 112 return statError{ 113 part: "line", 114 raw: raw, 115 } 116 } 117 118 func errBadMode(raw []byte) statError { 119 return statError{ 120 part: "mode", 121 raw: raw, 122 } 123 } 124 125 func errBadOwner(raw []byte) statError { 126 return statError{ 127 part: "owner", 128 raw: raw, 129 } 130 } 131 132 func errBadNode(raw []byte) statError { 133 return statError{ 134 part: "node", 135 raw: raw, 136 } 137 } 138 139 func errBadSize(raw []byte) statError { 140 return statError{ 141 part: "size", 142 raw: raw, 143 } 144 } 145 146 func errBadTime(raw []byte) statError { 147 return statError{ 148 part: "time", 149 raw: raw, 150 } 151 } 152 153 func errBadPath(raw []byte) statError { 154 return statError{ 155 part: "path", 156 raw: raw, 157 } 158 } 159 160 func (st *stat) parseTimeUTC(raw []byte) (int, error) { 161 const timelen = 16 162 t, err := time.Parse("2006-01-02 15:04", string(raw[:timelen])) 163 if err != nil { 164 return 0, errBadTime(raw) 165 } 166 167 st.mtime = t 168 169 return timelen, nil 170 } 171 172 func (st *stat) parseMode(raw []byte) (int, error) { 173 switch raw[0] { 174 case '-': 175 // 0 176 case 'd': 177 st.mode |= os.ModeDir 178 case 's': 179 st.mode |= os.ModeSocket 180 case 'c': 181 st.mode |= os.ModeCharDevice 182 case 'b': 183 st.mode |= os.ModeDevice 184 case 'p': 185 st.mode |= os.ModeNamedPipe 186 case 'l': 187 st.mode |= os.ModeSymlink 188 default: 189 return 0, errBadMode(raw) 190 } 191 192 for i := 0; i < 3; i++ { 193 m, err := modeFromTriplet(raw[1+3*i:4+3*i], uint(2-i)) 194 if err != nil { 195 return 0, err 196 } 197 st.mode |= m 198 } 199 200 // always this length (1+3*3==10) 201 return 10, nil 202 } 203 204 func (st *stat) parseOwner(raw []byte) (int, error) { 205 var p, ui, uj, gi, gj int 206 207 // first check it's sane (at least two non-space chars) 208 if raw[0] == ' ' || raw[1] == ' ' { 209 return 0, errBadLine(raw) 210 } 211 212 ui = 0 213 // from useradd(8): Usernames may only be up to 32 characters long. 214 // from groupadd(8): Groupnames may only be up to 32 characters long. 215 // +1 for the separator, +1 for the ending space 216 maxL := 66 217 if len(raw) < maxL { 218 maxL = len(raw) 219 } 220 out: 221 for p = ui; p < maxL; p++ { 222 switch raw[p] { 223 case '/': 224 uj = p 225 gi = p + 1 226 case ' ': 227 gj = p 228 break out 229 } 230 } 231 232 if uj == 0 || gj == 0 || gi == gj { 233 return 0, errBadOwner(raw) 234 } 235 st.user, st.group = string(raw[ui:uj]), string(raw[gi:gj]) 236 237 return p, nil 238 } 239 240 func modeFromTriplet(trip []byte, shift uint) (os.FileMode, error) { 241 var mode os.FileMode 242 high := false 243 if len(trip) != 3 { 244 panic("bad triplet length") 245 } 246 switch trip[0] { 247 case '-': 248 // 0 249 case 'r': 250 mode |= 4 251 default: 252 return 0, errBadMode(trip) 253 } 254 switch trip[1] { 255 case '-': 256 // 0 257 case 'w': 258 mode |= 2 259 default: 260 return 0, errBadMode(trip) 261 } 262 switch trip[2] { 263 case '-': 264 // 0 265 case 'x': 266 mode |= 1 267 case 'S', 'T': 268 high = true 269 case 's', 't': 270 mode |= 1 271 high = true 272 default: 273 return 0, errBadMode(trip) 274 } 275 276 mode <<= 3 * shift 277 if high { 278 mode |= (01000 << shift) 279 } 280 return mode, nil 281 } 282 283 func (st *stat) parseSize(raw []byte) (int, error) { 284 // the "size" column, for regular files, is the file size in bytes: 285 // -rwxr-xr-x user/group 53595 2017-12-08 11:19 ./yadda 286 // ^^^^^ like this 287 // for devices, though, it's the major, minor of the node: 288 // crw-rw---- root/audio 14, 3 2017-12-05 10:29 ./dev/dsp 289 // ^^^^^^ like so 290 // (for other things it is a size, although what the size is 291 // _of_ is left as an exercise for the reader) 292 isNode := st.mode&(os.ModeDevice|os.ModeCharDevice) != 0 293 p := 0 294 maxP := len(raw) - len("2006-01-02 15:04 .") 295 296 for raw[p] == ' ' { 297 if p >= maxP { 298 return 0, errBadLine(raw) 299 } 300 p++ 301 } 302 303 ni := p 304 305 for raw[p] >= '0' && raw[p] <= '9' { 306 if p >= maxP { 307 return 0, errBadLine(raw) 308 } 309 p++ 310 } 311 312 if p == ni { 313 if isNode { 314 return 0, errBadNode(raw) 315 } 316 return 0, errBadSize(raw) 317 } 318 319 if isNode { 320 if raw[p] != ',' { 321 return 0, errBadNode(raw) 322 } 323 324 p++ 325 326 // drop the space before the minor mode 327 for raw[p] == ' ' { 328 p++ 329 } 330 // drop the minor mode 331 for raw[p] >= '0' && raw[p] <= '9' { 332 p++ 333 } 334 335 if raw[p] != ' ' { 336 return 0, errBadNode(raw) 337 } 338 } else { 339 if raw[p] != ' ' { 340 return 0, errBadSize(raw) 341 } 342 // note that, much as it makes very little sense, the arch- 343 // dependent st_size is never an unsigned 64 bit quantity. 344 // It's one of unsigned long, long long, or just off_t. 345 // 346 // Also note os.FileInfo's Size needs to return an int64, and 347 // squashfs's inode->data (where it stores sizes for regular 348 // files) is a long long. 349 sz, err := strconv.ParseInt(string(raw[ni:p]), 10, 64) 350 if err != nil { 351 return 0, errBadSize(raw) 352 } 353 st.size = sz 354 } 355 356 return p, nil 357 } 358 359 func (st *stat) parsePath(raw []byte) (int, error) { 360 if raw[0] != '.' { 361 return 0, errBadPath(raw) 362 } 363 if len(raw[1:]) == 0 { 364 st.path = "/" 365 } else { 366 st.path = string(raw[1:]) 367 } 368 369 return len(raw), nil 370 }