github.com/rkt/rkt@v1.30.1-0.20200224141603-171c416fac02/pkg/fileutil/fileutil.go (about)

     1  // Copyright 2015 The rkt Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package fileutil
    16  
    17  import (
    18  	"fmt"
    19  	"io"
    20  	"os"
    21  	"path/filepath"
    22  	"syscall"
    23  	"time"
    24  
    25  	"github.com/rkt/rkt/pkg/user"
    26  
    27  	"github.com/appc/spec/pkg/device"
    28  )
    29  
    30  func CopyRegularFile(src, dest string) (err error) {
    31  	srcFile, err := os.Open(src)
    32  	if err != nil {
    33  		return err
    34  	}
    35  	defer srcFile.Close()
    36  	destFile, err := os.Create(dest)
    37  	if err != nil {
    38  		return err
    39  	}
    40  	defer func() {
    41  		e := destFile.Close()
    42  		if err == nil {
    43  			err = e
    44  		}
    45  	}()
    46  	if _, err := io.Copy(destFile, srcFile); err != nil {
    47  		return err
    48  	}
    49  	return nil
    50  }
    51  
    52  func CopySymlink(src, dest string) error {
    53  	symTarget, err := os.Readlink(src)
    54  	if err != nil {
    55  		return err
    56  	}
    57  	if err := os.Symlink(symTarget, dest); err != nil {
    58  		return err
    59  	}
    60  	return nil
    61  }
    62  
    63  func CopyTree(src, dest string, uidRange *user.UidRange) error {
    64  	cleanSrc := filepath.Clean(src)
    65  	dirs := make(map[string][]syscall.Timespec)
    66  	copyWalker := func(path string, info os.FileInfo, err error) error {
    67  		if err != nil {
    68  			return err
    69  		}
    70  		rootLess := path[len(cleanSrc):]
    71  		if cleanSrc == "." {
    72  			rootLess = path
    73  		}
    74  		target := filepath.Join(dest, rootLess)
    75  		mode := info.Mode()
    76  		switch {
    77  		case mode.IsDir():
    78  			err := os.Mkdir(target, mode.Perm())
    79  			if err != nil {
    80  				return err
    81  			}
    82  
    83  			dir, err := os.Open(target)
    84  			if err != nil {
    85  				return err
    86  			}
    87  			if err := dir.Chmod(mode); err != nil {
    88  				dir.Close()
    89  				return err
    90  			}
    91  			dir.Close()
    92  		case mode.IsRegular():
    93  			if err := CopyRegularFile(path, target); err != nil {
    94  				return err
    95  			}
    96  		case mode&os.ModeSymlink == os.ModeSymlink:
    97  			if err := CopySymlink(path, target); err != nil {
    98  				return err
    99  			}
   100  		case mode&os.ModeCharDevice == os.ModeCharDevice:
   101  			stat := syscall.Stat_t{}
   102  			if err := syscall.Stat(path, &stat); err != nil {
   103  				return err
   104  			}
   105  
   106  			dev := device.Makedev(device.Major(stat.Rdev), device.Minor(stat.Rdev))
   107  			mode := uint32(mode) | syscall.S_IFCHR
   108  			if err := syscall.Mknod(target, mode, int(dev)); err != nil {
   109  				return err
   110  			}
   111  		case mode&os.ModeDevice == os.ModeDevice:
   112  			stat := syscall.Stat_t{}
   113  			if err := syscall.Stat(path, &stat); err != nil {
   114  				return err
   115  			}
   116  
   117  			dev := device.Makedev(device.Major(stat.Rdev), device.Minor(stat.Rdev))
   118  			mode := uint32(mode) | syscall.S_IFBLK
   119  			if err := syscall.Mknod(target, mode, int(dev)); err != nil {
   120  				return err
   121  			}
   122  		case mode&os.ModeNamedPipe == os.ModeNamedPipe:
   123  			if err := syscall.Mkfifo(target, uint32(mode)); err != nil {
   124  				return err
   125  			}
   126  		default:
   127  			return fmt.Errorf("unsupported mode: %v", mode)
   128  		}
   129  
   130  		var srcUid = info.Sys().(*syscall.Stat_t).Uid
   131  		var srcGid = info.Sys().(*syscall.Stat_t).Gid
   132  
   133  		shiftedUid, shiftedGid, err := uidRange.ShiftRange(srcUid, srcGid)
   134  		if err != nil {
   135  			return err
   136  		}
   137  
   138  		if err := os.Lchown(target, int(shiftedUid), int(shiftedGid)); err != nil {
   139  			return err
   140  		}
   141  
   142  		// lchown(2) says that, depending on the linux kernel version, it
   143  		// can change the file's mode also if executed as root. So call
   144  		// os.Chmod after it.
   145  		if mode&os.ModeSymlink != os.ModeSymlink {
   146  			if err := os.Chmod(target, mode); err != nil {
   147  				return err
   148  			}
   149  		}
   150  
   151  		ts, err := pathToTimespec(path)
   152  		if err != nil {
   153  			return err
   154  		}
   155  
   156  		if mode.IsDir() {
   157  			dirs[target] = ts
   158  		}
   159  		if mode&os.ModeSymlink != os.ModeSymlink {
   160  			if err := syscall.UtimesNano(target, ts); err != nil {
   161  				return err
   162  			}
   163  		} else {
   164  			if err := LUtimesNano(target, ts); err != nil {
   165  				return err
   166  			}
   167  		}
   168  
   169  		return nil
   170  	}
   171  
   172  	if err := filepath.Walk(cleanSrc, copyWalker); err != nil {
   173  		return err
   174  	}
   175  
   176  	// Restore dirs atime and mtime. This has to be done after copying
   177  	// as a file copying will change its parent directory's times.
   178  	for dirPath, ts := range dirs {
   179  		if err := syscall.UtimesNano(dirPath, ts); err != nil {
   180  			return err
   181  		}
   182  	}
   183  
   184  	return nil
   185  }
   186  
   187  func pathToTimespec(name string) ([]syscall.Timespec, error) {
   188  	fi, err := os.Lstat(name)
   189  	if err != nil {
   190  		return nil, err
   191  	}
   192  	mtime := fi.ModTime()
   193  	stat := fi.Sys().(*syscall.Stat_t)
   194  	atime := time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
   195  	return []syscall.Timespec{TimeToTimespec(atime), TimeToTimespec(mtime)}, nil
   196  }
   197  
   198  // TODO(sgotti) use UTIMES_OMIT on linux if Time.IsZero ?
   199  func TimeToTimespec(time time.Time) (ts syscall.Timespec) {
   200  	nsec := int64(0)
   201  	if !time.IsZero() {
   202  		nsec = time.UnixNano()
   203  	}
   204  	return syscall.NsecToTimespec(nsec)
   205  }
   206  
   207  // DirSize takes a path and returns its size in bytes
   208  func DirSize(path string) (int64, error) {
   209  	seenInode := make(map[uint64]struct{})
   210  
   211  	if _, err := os.Stat(path); err == nil {
   212  		var sz int64
   213  		err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
   214  			if hasHardLinks(info) {
   215  				ino := getInode(info)
   216  				if _, ok := seenInode[ino]; !ok {
   217  					seenInode[ino] = struct{}{}
   218  					sz += info.Size()
   219  				}
   220  			} else {
   221  				sz += info.Size()
   222  			}
   223  			return err
   224  		})
   225  		return sz, err
   226  	}
   227  
   228  	return 0, nil
   229  }
   230  
   231  // IsExecutable checks if the given path points to an executable file by
   232  // checking the executable bit. Inspired by os.exec.LookPath()
   233  func IsExecutable(path string) bool {
   234  	d, err := os.Stat(path)
   235  	if err == nil {
   236  		m := d.Mode()
   237  		return !m.IsDir() && m&0111 != 0
   238  	}
   239  	return false
   240  }
   241  
   242  // IsDeviceNode checks if the given path points to a block or char device.
   243  // It doesn't follow symlinks.
   244  func IsDeviceNode(path string) bool {
   245  	d, err := os.Lstat(path)
   246  	if err == nil {
   247  		m := d.Mode()
   248  		return m&os.ModeDevice == os.ModeDevice
   249  	}
   250  	return false
   251  }