github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/snap/snapdir/snapdir.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2015 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 snapdir
    21  
    22  import (
    23  	"fmt"
    24  	"io"
    25  	"io/ioutil"
    26  	"os"
    27  	"path/filepath"
    28  
    29  	"github.com/snapcore/snapd/osutil"
    30  	"github.com/snapcore/snapd/snap"
    31  	"github.com/snapcore/snapd/snap/internal"
    32  )
    33  
    34  func IsSnapDir(path string) bool {
    35  	if osutil.IsDirectory(path) {
    36  		if osutil.FileExists(filepath.Join(path, "meta", "snap.yaml")) {
    37  			return true
    38  		}
    39  	}
    40  	return false
    41  }
    42  
    43  // SnapDir is the snapdir based snap.
    44  type SnapDir struct {
    45  	path string
    46  }
    47  
    48  // Path returns the path of the backing container.
    49  func (s *SnapDir) Path() string {
    50  	return s.path
    51  }
    52  
    53  // New returns a new snap directory container.
    54  func New(path string) *SnapDir {
    55  	return &SnapDir{path: path}
    56  }
    57  
    58  func (s *SnapDir) Size() (size int64, err error) {
    59  	totalSize := int64(0)
    60  	f := func(_ string, info os.FileInfo, err error) error {
    61  		totalSize += info.Size()
    62  		return err
    63  	}
    64  	filepath.Walk(s.path, f)
    65  
    66  	return totalSize, nil
    67  }
    68  
    69  func (s *SnapDir) Install(targetPath, mountDir string, opts *snap.InstallOptions) (bool, error) {
    70  	// TODO:UC20: support MustNotCrossDevices somehow here
    71  
    72  	return false, os.Symlink(s.path, targetPath)
    73  }
    74  
    75  func (s *SnapDir) RandomAccessFile(file string) (interface {
    76  	io.ReaderAt
    77  	io.Closer
    78  	Size() int64
    79  }, error) {
    80  	f, err := os.Open(filepath.Join(s.path, file))
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  	return internal.NewSizedFile(f)
    85  }
    86  
    87  func (s *SnapDir) ReadFile(file string) (content []byte, err error) {
    88  	return ioutil.ReadFile(filepath.Join(s.path, file))
    89  }
    90  
    91  func littleWalk(dirPath string, dirHandle *os.File, dirstack *[]string, walkFn filepath.WalkFunc) error {
    92  	const numSt = 100
    93  
    94  	sts, err := dirHandle.Readdir(numSt)
    95  	if err != nil {
    96  		return err
    97  	}
    98  	for _, st := range sts {
    99  		path := filepath.Join(dirPath, st.Name())
   100  		if err := walkFn(path, st, nil); err != nil {
   101  			if st.IsDir() && err == filepath.SkipDir {
   102  				// caller wants to skip this directory
   103  				continue
   104  			}
   105  			return err
   106  		} else if st.IsDir() {
   107  			*dirstack = append(*dirstack, path)
   108  		}
   109  	}
   110  
   111  	return nil
   112  }
   113  
   114  // Walk (part of snap.Container) is like filepath.Walk, without the ordering guarantee.
   115  func (s *SnapDir) Walk(relative string, walkFn filepath.WalkFunc) error {
   116  	relative = filepath.Clean(relative)
   117  	if relative == "" || relative == "/" {
   118  		relative = "."
   119  	} else if relative[0] == '/' {
   120  		// I said relative, darn it :-)
   121  		relative = relative[1:]
   122  	}
   123  	root := filepath.Join(s.path, relative)
   124  	// we could just filepath.Walk(root, walkFn), but that doesn't scale
   125  	// well to insanely big directories as it reads the whole directory,
   126  	// in order to sort it. This Walk doesn't do that.
   127  	//
   128  	// Also the directory is always relative to the top of the container
   129  	// for us, which would make it a little more messy to get right.
   130  	f, err := os.Open(root)
   131  	if err != nil {
   132  		return walkFn(relative, nil, err)
   133  	}
   134  	defer func() {
   135  		if f != nil {
   136  			f.Close()
   137  		}
   138  	}()
   139  
   140  	st, err := f.Stat()
   141  	if err != nil {
   142  		return walkFn(relative, nil, err)
   143  	}
   144  
   145  	err = walkFn(relative, st, nil)
   146  	if err != nil {
   147  		return err
   148  	}
   149  	if !st.IsDir() {
   150  		return nil
   151  	}
   152  
   153  	var dirstack []string
   154  	for {
   155  		if err := littleWalk(relative, f, &dirstack, walkFn); err != nil {
   156  			if err != io.EOF {
   157  				err = walkFn(relative, nil, err)
   158  				if err != nil {
   159  					return err
   160  				}
   161  			}
   162  			if len(dirstack) == 0 {
   163  				// finished
   164  				break
   165  			}
   166  			f.Close()
   167  			f = nil
   168  			for f == nil && len(dirstack) > 0 {
   169  				relative = dirstack[0]
   170  				f, err = os.Open(filepath.Join(s.path, relative))
   171  				if err != nil {
   172  					err = walkFn(relative, nil, err)
   173  					if err != nil {
   174  						return err
   175  					}
   176  				}
   177  				dirstack = dirstack[1:]
   178  			}
   179  			if f == nil {
   180  				break
   181  			}
   182  			continue
   183  		}
   184  	}
   185  
   186  	return nil
   187  }
   188  
   189  func (s *SnapDir) ListDir(path string) ([]string, error) {
   190  	fileInfos, err := ioutil.ReadDir(filepath.Join(s.path, path))
   191  	if err != nil {
   192  		return nil, err
   193  	}
   194  
   195  	var fileNames []string
   196  	for _, fileInfo := range fileInfos {
   197  		fileNames = append(fileNames, fileInfo.Name())
   198  	}
   199  
   200  	return fileNames, nil
   201  }
   202  
   203  func (s *SnapDir) Unpack(src, dstDir string) error {
   204  	return fmt.Errorf("unpack is not supported with snaps of type snapdir")
   205  }