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

     1  // Copyright 2016 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  	"errors"
    19  	"os"
    20  	"path/filepath"
    21  )
    22  
    23  // The following code was taken from "src/path/filepath/symlink.go" from Go 1.7.4.
    24  // Modifications include:
    25  // - remove Windows specific code
    26  // - continue evaluatig paths, even if they don't exist
    27  
    28  // Original copyright notice:
    29  //
    30  // Copyright 2012 The Go Authors. All rights reserved.
    31  // Use of this source code is governed by a BSD-style
    32  // license that can be found in the LICENSE file.
    33  
    34  func isRoot(path string) bool {
    35  	return path == "/"
    36  }
    37  
    38  func walkLink(path string, linksWalked *int) (newpath string, islink bool, err error) {
    39  	if *linksWalked > 255 {
    40  		return "", false, errors.New("EvalSymlinks: too many links")
    41  	}
    42  	fi, err := os.Lstat(path)
    43  	if os.IsNotExist(err) {
    44  		return path, false, nil
    45  	}
    46  	if err != nil {
    47  		return "", false, err
    48  	}
    49  	if fi.Mode()&os.ModeSymlink == 0 {
    50  		return path, false, nil
    51  	}
    52  	newpath, err = os.Readlink(path)
    53  	if err != nil {
    54  		return "", false, err
    55  	}
    56  	*linksWalked++
    57  	return newpath, true, nil
    58  }
    59  
    60  func walkLinks(path string, linksWalked *int) (string, error) {
    61  	dir, file := filepath.Split(path)
    62  
    63  	switch {
    64  	case dir == "":
    65  		newpath, _, err := walkLink(file, linksWalked)
    66  		return newpath, err
    67  	case file == "":
    68  		if os.IsPathSeparator(dir[len(dir)-1]) {
    69  			if isRoot(dir) {
    70  				return dir, nil
    71  			}
    72  			return walkLinks(dir[:len(dir)-1], linksWalked)
    73  		}
    74  		newpath, _, err := walkLink(dir, linksWalked)
    75  		return newpath, err
    76  	default:
    77  		newdir, err := walkLinks(dir, linksWalked)
    78  		if err != nil {
    79  			return "", err
    80  		}
    81  		newpath, islink, err := walkLink(filepath.Join(newdir, file), linksWalked)
    82  		if err != nil {
    83  			return "", err
    84  		}
    85  		if !islink {
    86  			return newpath, nil
    87  		}
    88  		if filepath.IsAbs(newpath) || os.IsPathSeparator(newpath[0]) {
    89  			return newpath, nil
    90  		}
    91  		return filepath.Join(newdir, newpath), nil
    92  	}
    93  }
    94  
    95  func walkSymlinks(path string) (string, error) {
    96  	if path == "" {
    97  		return path, nil
    98  	}
    99  	var linksWalked int // to protect against cycles
   100  	for {
   101  		i := linksWalked
   102  		newpath, err := walkLinks(path, &linksWalked)
   103  		if err != nil {
   104  			return "", err
   105  		}
   106  		if i == linksWalked {
   107  			return filepath.Clean(newpath), nil
   108  		}
   109  		path = newpath
   110  	}
   111  }
   112  
   113  // EvalSymlinksAlways behaves like filepath.EvalSymlinks
   114  // but does not require that all path components must exist.
   115  //
   116  // While filepath.EvalSymlink behaves like `readlink -e`
   117  // this function behaves like `readlink -m`.
   118  //
   119  // Unlike `readlink` EvalSymlinksAlways might return a relative path.
   120  func EvalSymlinksAlways(path string) (string, error) {
   121  	return walkSymlinks(path)
   122  }