github.com/mgoltzsche/ctnr@v0.7.1-alpha/pkg/fs/fsnodeattrs.go (about) 1 package fs 2 3 import ( 4 "fmt" 5 "net/url" 6 "os" 7 "path/filepath" 8 "regexp" 9 "sort" 10 "strconv" 11 "strings" 12 "time" 13 14 "github.com/mgoltzsche/ctnr/pkg/idutils" 15 "github.com/pkg/errors" 16 ) 17 18 const ( 19 AttrsHash AttrSet = 1 20 AttrsMtime AttrSet = 2 21 AttrsAtime AttrSet = 4 22 AttrsAll = AttrsHash | AttrsMtime | AttrsAtime 23 AttrsCompare = AttrsHash | AttrsMtime 24 TimeFormat = time.RFC3339 25 ) 26 27 type AttrSet uint 28 29 var ( 30 TypeFile NodeType = "file" 31 TypeDir NodeType = "dir" 32 TypeOverlay NodeType = "overlay" 33 TypeSymlink NodeType = "symlink" 34 TypeFifo NodeType = "fifo" 35 TypeDevice NodeType = "dev" 36 TypeWhiteout NodeType = "whiteout" 37 nameRegex = regexp.MustCompile("^[a-zA-Z0-9\\-_\\.,]+$") 38 nilWriter = hashingNilWriter("noop writer") 39 _ Source = &NodeAttrs{} 40 ) 41 42 type NodeType string 43 44 type FileAttrs struct { 45 Mode os.FileMode 46 idutils.UserIds 47 Xattrs map[string]string 48 FileTimes 49 Size int64 50 Symlink string 51 } 52 53 func (a *FileAttrs) AttrString(attrs AttrSet) string { 54 s := "" 55 if !a.UserIds.IsZero() { 56 s = "usr=" + a.UserIds.String() 57 } 58 perm := a.Mode.Perm() 59 if perm != 0 && a.Mode&os.ModeSymlink == 0 { 60 s += " mode=" + strconv.FormatUint(uint64(perm), 8) 61 } 62 if a.Size > 0 { 63 s += " size=" + strconv.Itoa(int(a.Size)) 64 } 65 if a.Symlink != "" { 66 s += " link=" + encodePath(a.Symlink) 67 } 68 if len(a.Xattrs) > 0 { 69 xa := make([]string, 0, len(a.Xattrs)) 70 for k, v := range a.Xattrs { 71 xa = append(xa, fmt.Sprintf("xattr.%s=%s", url.QueryEscape(k), url.QueryEscape(v))) 72 } 73 sort.Strings(xa) 74 s += " " + strings.Join(xa, " ") 75 } 76 if attrs&AttrsMtime != 0 { 77 if !a.Mtime.IsZero() { 78 s += " mtime=" + fmt.Sprintf("%d", a.Mtime.Unix()) 79 } 80 } 81 if attrs&AttrsAtime != 0 { 82 if !a.Atime.IsZero() { 83 s += " atime=" + fmt.Sprintf("%d", a.Atime.Unix()) 84 } 85 } 86 return strings.TrimLeft(s, " ") 87 } 88 89 func (a *FileAttrs) Equal(o *FileAttrs) bool { 90 return a.Mode.Perm() == o.Mode.Perm() && a.Uid == o.Uid && a.Gid == o.Gid && 91 a.Mtime.Unix() == o.Mtime.Unix() && a.Size == o.Size && a.Symlink == o.Symlink && 92 xattrsEqual(a.Xattrs, o.Xattrs) 93 } 94 95 func xattrsEqual(a, b map[string]string) bool { 96 if len(a) != len(b) { 97 return false 98 } 99 if a != nil { 100 for k, v := range a { 101 if bv, ok := b[k]; !ok || v != bv { 102 return false 103 } 104 } 105 } 106 return true 107 } 108 109 type FileTimes struct { 110 Atime time.Time 111 Mtime time.Time 112 } 113 114 type NodeAttrs struct { 115 NodeInfo 116 DerivedAttrs 117 } 118 119 type DerivedAttrs struct { 120 Hash string 121 URL string 122 HTTPInfo string 123 } 124 125 func (s *DerivedAttrs) Equal(o *DerivedAttrs) bool { 126 return s.Hash == o.Hash && s.URL == o.URL && s.HTTPInfo == o.HTTPInfo 127 } 128 129 type NodeInfo struct { 130 NodeType NodeType 131 FileAttrs 132 } 133 134 func (s NodeInfo) Equal(o NodeInfo) bool { 135 return s.NodeType == o.NodeType && s.FileAttrs.Equal(&o.FileAttrs) 136 } 137 138 func (a *NodeInfo) AttrString(attrs AttrSet) string { 139 return "type=" + string(a.NodeType) + " " + a.FileAttrs.AttrString(attrs) 140 } 141 142 type DeviceAttrs struct { 143 FileAttrs 144 Devmajor int64 145 Devminor int64 146 } 147 148 func (s *NodeAttrs) Attrs() NodeInfo { 149 return s.NodeInfo 150 } 151 func (s *NodeAttrs) DeriveAttrs() (DerivedAttrs, error) { 152 return s.DerivedAttrs, nil 153 } 154 func (s *NodeAttrs) Write(path, name string, w Writer, written map[Source]string) (err error) { 155 if linkDest, ok := written[s]; ok { 156 err = w.LowerLink(path, linkDest, s) 157 } else { 158 written[s] = path 159 err = w.LowerNode(path, name, s) 160 } 161 return 162 } 163 164 func ParseNodeAttrs(s string) (r NodeAttrs, err error) { 165 for _, e := range strings.Split(s, " ") { 166 l := strings.SplitN(e, "=", 2) 167 if len(l) != 2 { 168 return r, errors.Errorf("parse file attrs: invalid attr %q in %q (missing '=')", e, s) 169 } 170 171 k := l[0] 172 v := l[1] 173 switch k { 174 case "type": 175 r.NodeType = NodeType(v) 176 case "mode": 177 var m uint64 178 m, err = strconv.ParseUint(v, 8, 32) 179 if err != nil { 180 return r, errors.Errorf("parse file attrs: invalid mode %q", v) 181 } 182 r.Mode = os.FileMode(m) 183 case "usr": 184 r.UserIds, err = idutils.ParseUser(v).ToIds() 185 if err != nil { 186 return r, errors.Wrap(err, "parse file attrs") 187 } 188 case "size": 189 r.Size, err = strconv.ParseInt(v, 10, 64) 190 if err != nil { 191 return r, errors.Errorf("parse file attrs: invalid size %q", v) 192 } 193 case "link": 194 r.Symlink, err = decodePath(v) 195 if err != nil { 196 return r, errors.Errorf("parse file attrs: invalid symlink %q", v) 197 } 198 case "hash": 199 r.Hash = v 200 case "url": 201 r.URL = v 202 case "http": 203 if r.HTTPInfo, err = url.QueryUnescape(v); err != nil { 204 return r, errors.Wrap(err, "parse file attrs: invalid http attr") 205 } 206 case "mtime": 207 tme, err := strconv.ParseInt(v, 10, 64) 208 if err != nil { 209 return r, errors.Wrap(err, "parse file attrs: invalid mtime") 210 } 211 r.Mtime = time.Unix(tme, 0) 212 case "atime": 213 tme, err := strconv.ParseInt(v, 10, 64) 214 if err != nil { 215 return r, errors.Wrap(err, "parse file attrs: invalid atime") 216 } 217 r.Atime = time.Unix(tme, 0) 218 default: 219 if strings.HasPrefix(k, "xattr.") { 220 k, err = url.QueryUnescape(k[6:]) 221 if err != nil { 222 return r, errors.Errorf("parse file attrs: invalid xattr key %q", k) 223 } 224 v, err = url.QueryUnescape(v) 225 if err != nil { 226 return r, errors.Errorf("parse file attrs: invalid xattr value %q", v) 227 } 228 if r.Xattrs == nil { 229 r.Xattrs = map[string]string{} 230 } 231 r.Xattrs[k] = v 232 } 233 } 234 } 235 if r.Mtime.IsZero() { 236 r.Mtime = time.Now() 237 } 238 if r.Atime.IsZero() { 239 r.Atime = r.Mtime 240 } 241 return 242 } 243 244 func parseUnixTime(s string) (t time.Time, err error) { 245 dotPos := strings.Index(s, ".") 246 if dotPos == -1 && dotPos+1 < len(s) { 247 return t, errors.Errorf("parse unix time %q", s) 248 } 249 sec, err := strconv.ParseInt(s[:dotPos], 10, 64) 250 if err != nil { 251 return t, errors.Errorf("parse unix second from %q", s) 252 } 253 nsec, err := strconv.ParseInt(s[dotPos+1:], 10, 64) 254 if err != nil { 255 return t, errors.Errorf("parse nanosecond from %q", s) 256 } 257 return time.Unix(sec, nsec), nil 258 } 259 260 func (a *NodeAttrs) AttrString(attrs AttrSet) string { 261 s := a.NodeInfo.AttrString(attrs) 262 if attrs&AttrsHash != 0 { 263 if a.Hash != "" { 264 s += " hash=" + a.Hash 265 } 266 if a.URL != "" { 267 if strings.Contains(a.URL, " ") { 268 panic("attrs string: provided URL contains space: " + a.URL) 269 } 270 s += " url=" + a.URL 271 if a.HTTPInfo != "" { 272 s += " http=" + url.QueryEscape(a.HTTPInfo) 273 } 274 } 275 } 276 return s 277 } 278 279 func (a *NodeAttrs) String() string { 280 return "NodeAttrs{" + a.AttrString(AttrsAll) + "}" 281 } 282 283 func encodePath(p string) string { 284 l := strings.Split(p, string(filepath.Separator)) 285 for i, s := range l { 286 l[i] = url.PathEscape(s) 287 } 288 return strings.Join(l, string(filepath.Separator)) 289 } 290 291 func decodePath(p string) (r string, err error) { 292 l := strings.Split(p, string(filepath.Separator)) 293 for i, s := range l { 294 if l[i], err = url.PathUnescape(s); err != nil { 295 return "", errors.Wrap(err, "decode path") 296 } 297 } 298 return strings.Join(l, string(filepath.Separator)), nil 299 } 300 301 type FileInfo struct { 302 name string 303 *FileAttrs 304 } 305 306 func NewFileInfo(name string, attrs *FileAttrs) *FileInfo { 307 return &FileInfo{name, attrs} 308 } 309 310 var _ os.FileInfo = &FileInfo{} 311 312 func (fi *FileInfo) IsDir() bool { 313 return fi.Mode()&os.ModeDir != 0 314 } 315 func (fi *FileInfo) ModTime() time.Time { 316 return fi.Mtime 317 } 318 func (fi *FileInfo) Mode() os.FileMode { 319 return fi.FileAttrs.Mode 320 } 321 func (fi *FileInfo) Name() string { 322 return fi.name 323 } 324 func (fi *FileInfo) Size() int64 { 325 return fi.FileAttrs.Size 326 } 327 func (fi *FileInfo) Sys() interface{} { 328 return fi 329 }