github.com/stulluk/snapd@v0.0.0-20210611110309-f6d5d5bd24b0/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  }