github.com/stackdocker/rkt@v0.10.1-0.20151109095037-1aa827478248/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/coreos/rkt/pkg/uid"
    26  
    27  	"github.com/coreos/rkt/Godeps/_workspace/src/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 *uid.UidRange) error {
    64  	cleanSrc := filepath.Clean(src)
    65  
    66  	dirs := make(map[string][]syscall.Timespec)
    67  	copyWalker := func(path string, info os.FileInfo, err error) error {
    68  		if err != nil {
    69  			return err
    70  		}
    71  		rootLess := path[len(cleanSrc):]
    72  		target := filepath.Join(dest, rootLess)
    73  		mode := info.Mode()
    74  		switch {
    75  		case mode.IsDir():
    76  			err := os.Mkdir(target, mode.Perm())
    77  			if err != nil {
    78  				return err
    79  			}
    80  
    81  			dir, err := os.Open(target)
    82  			if err != nil {
    83  				return err
    84  			}
    85  			if err := dir.Chmod(mode); err != nil {
    86  				dir.Close()
    87  				return err
    88  			}
    89  			dir.Close()
    90  		case mode.IsRegular():
    91  			if err := CopyRegularFile(path, target); err != nil {
    92  				return err
    93  			}
    94  		case mode&os.ModeSymlink == os.ModeSymlink:
    95  			if err := CopySymlink(path, target); err != nil {
    96  				return err
    97  			}
    98  		case mode&os.ModeCharDevice == os.ModeCharDevice:
    99  			stat := syscall.Stat_t{}
   100  			if err := syscall.Stat(path, &stat); err != nil {
   101  				return err
   102  			}
   103  
   104  			dev := device.Makedev(device.Major(stat.Rdev), device.Minor(stat.Rdev))
   105  			mode := uint32(mode) | syscall.S_IFCHR
   106  			if err := syscall.Mknod(target, mode, int(dev)); err != nil {
   107  				return err
   108  			}
   109  		case mode&os.ModeDevice == os.ModeDevice:
   110  			stat := syscall.Stat_t{}
   111  			if err := syscall.Stat(path, &stat); err != nil {
   112  				return err
   113  			}
   114  
   115  			dev := device.Makedev(device.Major(stat.Rdev), device.Minor(stat.Rdev))
   116  			mode := uint32(mode) | syscall.S_IFBLK
   117  			if err := syscall.Mknod(target, mode, int(dev)); err != nil {
   118  				return err
   119  			}
   120  		case mode&os.ModeNamedPipe == os.ModeNamedPipe:
   121  			if err := syscall.Mkfifo(target, uint32(mode)); err != nil {
   122  				return err
   123  			}
   124  		default:
   125  			return fmt.Errorf("unsupported mode: %v", mode)
   126  		}
   127  
   128  		var srcUid = info.Sys().(*syscall.Stat_t).Uid
   129  		var srcGid = info.Sys().(*syscall.Stat_t).Gid
   130  
   131  		shiftedUid, shiftedGid, err := uidRange.ShiftRange(srcUid, srcGid)
   132  		if err != nil {
   133  			return err
   134  		}
   135  
   136  		if err := os.Lchown(target, int(shiftedUid), int(shiftedGid)); err != nil {
   137  			return err
   138  		}
   139  
   140  		// lchown(2) says that, depending on the linux kernel version, it
   141  		// can change the file's mode also if executed as root. So call
   142  		// os.Chmod after it.
   143  		if mode&os.ModeSymlink != os.ModeSymlink {
   144  			if err := os.Chmod(target, mode); err != nil {
   145  				return err
   146  			}
   147  		}
   148  
   149  		ts, err := pathToTimespec(path)
   150  		if err != nil {
   151  			return err
   152  		}
   153  
   154  		if mode.IsDir() {
   155  			dirs[target] = ts
   156  		}
   157  		if mode&os.ModeSymlink != os.ModeSymlink {
   158  			if err := syscall.UtimesNano(target, ts); err != nil {
   159  				return err
   160  			}
   161  		} else {
   162  			if err := LUtimesNano(target, ts); err != nil {
   163  				return err
   164  			}
   165  		}
   166  
   167  		return nil
   168  	}
   169  
   170  	if err := filepath.Walk(cleanSrc, copyWalker); err != nil {
   171  		return err
   172  	}
   173  
   174  	// Restore dirs atime and mtime. This has to be done after copying
   175  	// as a file copying will change its parent directory's times.
   176  	for dirPath, ts := range dirs {
   177  		if err := syscall.UtimesNano(dirPath, ts); err != nil {
   178  			return err
   179  		}
   180  	}
   181  
   182  	return nil
   183  }
   184  
   185  func pathToTimespec(name string) ([]syscall.Timespec, error) {
   186  	fi, err := os.Lstat(name)
   187  	if err != nil {
   188  		return nil, err
   189  	}
   190  	mtime := fi.ModTime()
   191  	stat := fi.Sys().(*syscall.Stat_t)
   192  	atime := time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
   193  	return []syscall.Timespec{TimeToTimespec(atime), TimeToTimespec(mtime)}, nil
   194  }
   195  
   196  // TODO(sgotti) use UTIMES_OMIT on linux if Time.IsZero ?
   197  func TimeToTimespec(time time.Time) (ts syscall.Timespec) {
   198  	nsec := int64(0)
   199  	if !time.IsZero() {
   200  		nsec = time.UnixNano()
   201  	}
   202  	return syscall.NsecToTimespec(nsec)
   203  }