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  }