...

Source file src/github.com/containerd/continuity/fs/path.go

Documentation: github.com/containerd/continuity/fs

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package fs
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"errors"
    23  	"io"
    24  	"os"
    25  	"path/filepath"
    26  )
    27  
    28  var errTooManyLinks = errors.New("too many links")
    29  
    30  type currentPath struct {
    31  	path     string
    32  	f        os.FileInfo
    33  	fullPath string
    34  }
    35  
    36  func pathChange(lower, upper *currentPath) (ChangeKind, string) {
    37  	if lower == nil {
    38  		if upper == nil {
    39  			panic("cannot compare nil paths")
    40  		}
    41  		return ChangeKindAdd, upper.path
    42  	}
    43  	if upper == nil {
    44  		return ChangeKindDelete, lower.path
    45  	}
    46  
    47  	switch i := directoryCompare(lower.path, upper.path); {
    48  	case i < 0:
    49  		// File in lower that is not in upper
    50  		return ChangeKindDelete, lower.path
    51  	case i > 0:
    52  		// File in upper that is not in lower
    53  		return ChangeKindAdd, upper.path
    54  	default:
    55  		return ChangeKindModify, upper.path
    56  	}
    57  }
    58  
    59  func directoryCompare(a, b string) int {
    60  	l := len(a)
    61  	if len(b) < l {
    62  		l = len(b)
    63  	}
    64  	for i := 0; i < l; i++ {
    65  		c1, c2 := a[i], b[i]
    66  		if c1 == filepath.Separator {
    67  			c1 = byte(0)
    68  		}
    69  		if c2 == filepath.Separator {
    70  			c2 = byte(0)
    71  		}
    72  		if c1 < c2 {
    73  			return -1
    74  		}
    75  		if c1 > c2 {
    76  			return +1
    77  		}
    78  	}
    79  	if len(a) < len(b) {
    80  		return -1
    81  	}
    82  	if len(a) > len(b) {
    83  		return +1
    84  	}
    85  	return 0
    86  }
    87  
    88  func sameFile(f1, f2 *currentPath) (bool, error) {
    89  	if os.SameFile(f1.f, f2.f) {
    90  		return true, nil
    91  	}
    92  
    93  	equalStat, err := compareSysStat(f1.f.Sys(), f2.f.Sys())
    94  	if err != nil || !equalStat {
    95  		return equalStat, err
    96  	}
    97  
    98  	if eq, err := compareCapabilities(f1.fullPath, f2.fullPath); err != nil || !eq {
    99  		return eq, err
   100  	}
   101  
   102  	// If not a directory also check size, modtime, and content
   103  	if !f1.f.IsDir() {
   104  		if f1.f.Size() != f2.f.Size() {
   105  			return false, nil
   106  		}
   107  		t1 := f1.f.ModTime()
   108  		t2 := f2.f.ModTime()
   109  
   110  		if t1.Unix() != t2.Unix() {
   111  			return false, nil
   112  		}
   113  
   114  		// If the timestamp may have been truncated in both of the
   115  		// files, check content of file to determine difference
   116  		if t1.Nanosecond() == 0 && t2.Nanosecond() == 0 {
   117  			if (f1.f.Mode() & os.ModeSymlink) == os.ModeSymlink {
   118  				return compareSymlinkTarget(f1.fullPath, f2.fullPath)
   119  			}
   120  			if f1.f.Size() == 0 { // if file sizes are zero length, the files are the same by definition
   121  				return true, nil
   122  			}
   123  			return compareFileContent(f1.fullPath, f2.fullPath)
   124  		} else if t1.Nanosecond() != t2.Nanosecond() {
   125  			return false, nil
   126  		}
   127  	}
   128  
   129  	return true, nil
   130  }
   131  
   132  func compareSymlinkTarget(p1, p2 string) (bool, error) {
   133  	t1, err := os.Readlink(p1)
   134  	if err != nil {
   135  		return false, err
   136  	}
   137  	t2, err := os.Readlink(p2)
   138  	if err != nil {
   139  		return false, err
   140  	}
   141  	return t1 == t2, nil
   142  }
   143  
   144  const compareChuckSize = 32 * 1024
   145  
   146  // compareFileContent compares the content of 2 same sized files
   147  // by comparing each byte.
   148  func compareFileContent(p1, p2 string) (bool, error) {
   149  	f1, err := os.Open(p1)
   150  	if err != nil {
   151  		return false, err
   152  	}
   153  	defer f1.Close()
   154  	f2, err := os.Open(p2)
   155  	if err != nil {
   156  		return false, err
   157  	}
   158  	defer f2.Close()
   159  
   160  	b1 := make([]byte, compareChuckSize)
   161  	b2 := make([]byte, compareChuckSize)
   162  	for {
   163  		n1, err1 := f1.Read(b1)
   164  		if err1 != nil && err1 != io.EOF {
   165  			return false, err1
   166  		}
   167  		n2, err2 := f2.Read(b2)
   168  		if err2 != nil && err2 != io.EOF {
   169  			return false, err2
   170  		}
   171  		if n1 != n2 || !bytes.Equal(b1[:n1], b2[:n2]) {
   172  			return false, nil
   173  		}
   174  		if err1 == io.EOF && err2 == io.EOF {
   175  			return true, nil
   176  		}
   177  	}
   178  }
   179  
   180  func pathWalk(ctx context.Context, root string, pathC chan<- *currentPath) error {
   181  	return filepath.Walk(root, func(path string, f os.FileInfo, err error) error {
   182  		if err != nil {
   183  			return err
   184  		}
   185  
   186  		// Rebase path
   187  		path, err = filepath.Rel(root, path)
   188  		if err != nil {
   189  			return err
   190  		}
   191  
   192  		path = filepath.Join(string(os.PathSeparator), path)
   193  
   194  		// Skip root
   195  		if path == string(os.PathSeparator) {
   196  			return nil
   197  		}
   198  
   199  		p := &currentPath{
   200  			path:     path,
   201  			f:        f,
   202  			fullPath: filepath.Join(root, path),
   203  		}
   204  
   205  		select {
   206  		case <-ctx.Done():
   207  			return ctx.Err()
   208  		case pathC <- p:
   209  			return nil
   210  		}
   211  	})
   212  }
   213  
   214  func nextPath(ctx context.Context, pathC <-chan *currentPath) (*currentPath, error) {
   215  	select {
   216  	case <-ctx.Done():
   217  		return nil, ctx.Err()
   218  	case p := <-pathC:
   219  		return p, nil
   220  	}
   221  }
   222  
   223  // RootPath joins a path with a root, evaluating and bounding any
   224  // symlink to the root directory.
   225  func RootPath(root, path string) (string, error) {
   226  	if path == "" {
   227  		return root, nil
   228  	}
   229  	var linksWalked int // to protect against cycles
   230  	for {
   231  		i := linksWalked
   232  		newpath, err := walkLinks(root, path, &linksWalked)
   233  		if err != nil {
   234  			return "", err
   235  		}
   236  		path = newpath
   237  		if i == linksWalked {
   238  			newpath = filepath.Join("/", newpath)
   239  			if path == newpath {
   240  				return filepath.Join(root, newpath), nil
   241  			}
   242  			path = newpath
   243  		}
   244  	}
   245  }
   246  
   247  func walkLink(root, path string, linksWalked *int) (newpath string, islink bool, err error) {
   248  	if *linksWalked > 255 {
   249  		return "", false, errTooManyLinks
   250  	}
   251  
   252  	path = filepath.Join("/", path)
   253  	if path == "/" {
   254  		return path, false, nil
   255  	}
   256  	realPath := filepath.Join(root, path)
   257  
   258  	fi, err := os.Lstat(realPath)
   259  	if err != nil {
   260  		// If path does not yet exist, treat as non-symlink
   261  		if os.IsNotExist(err) {
   262  			return path, false, nil
   263  		}
   264  		return "", false, err
   265  	}
   266  	if fi.Mode()&os.ModeSymlink == 0 {
   267  		return path, false, nil
   268  	}
   269  	newpath, err = os.Readlink(realPath)
   270  	if err != nil {
   271  		return "", false, err
   272  	}
   273  	*linksWalked++
   274  	return newpath, true, nil
   275  }
   276  
   277  func walkLinks(root, path string, linksWalked *int) (string, error) {
   278  	switch dir, file := filepath.Split(path); {
   279  	case dir == "":
   280  		newpath, _, err := walkLink(root, file, linksWalked)
   281  		return newpath, err
   282  	case file == "":
   283  		if os.IsPathSeparator(dir[len(dir)-1]) {
   284  			if dir == "/" {
   285  				return dir, nil
   286  			}
   287  			return walkLinks(root, dir[:len(dir)-1], linksWalked)
   288  		}
   289  		newpath, _, err := walkLink(root, dir, linksWalked)
   290  		return newpath, err
   291  	default:
   292  		newdir, err := walkLinks(root, dir, linksWalked)
   293  		if err != nil {
   294  			return "", err
   295  		}
   296  		newpath, islink, err := walkLink(root, filepath.Join(newdir, file), linksWalked)
   297  		if err != nil {
   298  			return "", err
   299  		}
   300  		if !islink {
   301  			return newpath, nil
   302  		}
   303  		if filepath.IsAbs(newpath) {
   304  			return newpath, nil
   305  		}
   306  		return filepath.Join(newdir, newpath), nil
   307  	}
   308  }
   309  

View as plain text