github.com/insomniacslk/u-root@v0.0.0-20200717035308-96b791510d76/cmds/core/ln/ln.go (about)

     1  // Copyright 2016-2017 the u-root Authors. All rights reserved
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Ln makes links to files.
     6  //
     7  // Synopsis:
     8  //     ln [-svfTiLPrt] TARGET LINK
     9  //
    10  // Options:
    11  //     -s: make symbolic links instead of hard links
    12  //     -v: print name of each linked file
    13  //     -f: remove destination files
    14  //     -T: treat linkname operand as a non-dir always
    15  //     -i: prompt if the user wants overwrite
    16  //     -L: dereference targets if are symbolic links
    17  //     -P: make hard links directly to symbolic links
    18  //     -r: create symlinks relative to link location
    19  //     -t: specify the directory to put the links
    20  //
    21  // Author:
    22  //     Manoel Vilela <manoel_vilela@engineer.com>
    23  package main
    24  
    25  import (
    26  	"bufio"
    27  	"flag"
    28  	"fmt"
    29  	"log"
    30  	"os"
    31  	"path/filepath"
    32  	"strings"
    33  )
    34  
    35  type config struct {
    36  	symlink  bool
    37  	verbose  bool
    38  	force    bool
    39  	nondir   bool
    40  	prompt   bool
    41  	logical  bool
    42  	physical bool
    43  	relative bool
    44  	dirtgt   string
    45  }
    46  
    47  // promptOverwrite ask for overwrite destination
    48  func promptOverwrite(fname string) bool {
    49  	fmt.Printf("ln: overwrite '%v'? ", fname)
    50  	answer, err := bufio.NewReader(os.Stdin).ReadString('\n')
    51  	return err == nil && strings.ToLower(answer)[0] == 'y'
    52  }
    53  
    54  // exists verify if a fname exists
    55  // the IsExists don't works fine, sorry for messy
    56  func exists(fname string) bool {
    57  	_, err := os.Lstat(fname)
    58  	return !os.IsNotExist(err)
    59  }
    60  
    61  // evalArgs based of the four type of uses describe at ln
    62  // get the targets and linknames operands
    63  // attention: linkName can be "", which latter will be inferred (see inferLinkName function)
    64  func (conf *config) evalArgs(args []string) (targets []string, linkName string) {
    65  	if conf.dirtgt != "" || len(args) <= 1 {
    66  		return args, ""
    67  	}
    68  
    69  	targets = args[:len(args)-1]
    70  	lastArg := args[len(args)-1]
    71  
    72  	if lf, err := os.Stat(lastArg); !conf.nondir && err == nil && lf.IsDir() {
    73  		conf.dirtgt = lastArg
    74  	} else {
    75  		linkName = lastArg
    76  	}
    77  
    78  	return targets, linkName
    79  }
    80  
    81  // relLink get the relative link path between
    82  // a target and linkName fpath
    83  // between a linkName operand and the target
    84  // HMM, i don't have sure if that works well...
    85  func relLink(target, linkName string) (string, error) {
    86  	base := filepath.Dir(linkName)
    87  	if newTarget, err := filepath.Rel(base, target); err == nil {
    88  		return newTarget, nil
    89  	} else if absLink, err := filepath.Abs(linkName); err == nil {
    90  		return filepath.Rel(absLink, target)
    91  	}
    92  
    93  	return "", nil
    94  }
    95  
    96  // inferLinkname infers the linkName if don't passed ("")
    97  // otherwhise preserves the linkName
    98  // e.g.:
    99  // $ ln -s -v /usr/bin/cp
   100  // cp -> /usr/bin/cp
   101  func inferLinkName(target, linkName string) string {
   102  	if linkName == "" {
   103  		linkName = filepath.Base(target)
   104  	}
   105  	return linkName
   106  }
   107  
   108  // dereferTarget treat symlinks according the flags -P and -L
   109  // if conf.logical or conf.physical follow the links
   110  // if conf.physical create a hard link instead symbolink
   111  func (conf config) dereferTarget(target string, linkFunc *func(string, string) error) (string, error) {
   112  	if conf.logical || conf.physical {
   113  		if newTarget, err := filepath.EvalSymlinks(target); err != nil {
   114  			return "", err
   115  		} else if newTarget != target {
   116  			target = newTarget
   117  			if conf.physical {
   118  				*linkFunc = os.Symlink
   119  			}
   120  		}
   121  	}
   122  	return target, nil
   123  }
   124  
   125  // ln is a general procedure for controlling the
   126  // flow of links creation, handling the flags and other stuffs.
   127  func (conf config) ln(args []string) error {
   128  	var remove bool
   129  
   130  	linkFunc := os.Link
   131  	if conf.symlink {
   132  		linkFunc = os.Symlink
   133  	}
   134  
   135  	originalPath, err := os.Getwd()
   136  	if err != nil {
   137  		return err
   138  	}
   139  
   140  	targets, linkName := conf.evalArgs(args)
   141  	for _, target := range targets {
   142  		linkFunc := linkFunc // back-overwrite possibility
   143  
   144  		// dereference symlinks
   145  		t, err := conf.dereferTarget(target, &linkFunc)
   146  		if err != nil {
   147  			return err
   148  		}
   149  		target = t
   150  
   151  		linkName := inferLinkName(target, linkName)
   152  		if conf.dirtgt != "" {
   153  			linkName = filepath.Join(conf.dirtgt, linkName)
   154  		}
   155  
   156  		if exists(linkName) {
   157  			if conf.prompt && !conf.force {
   158  				remove = promptOverwrite(linkName)
   159  				if !remove {
   160  					continue
   161  				}
   162  			}
   163  
   164  			if conf.force || remove {
   165  				if err := os.Remove(linkName); err != nil {
   166  					return err
   167  				}
   168  			}
   169  		}
   170  
   171  		if conf.relative && !conf.symlink {
   172  			return fmt.Errorf("cannot do -r without -s")
   173  		}
   174  
   175  		// make relative paths with symlinks
   176  		if conf.relative {
   177  			relTarget, err := relLink(target, linkName)
   178  			if err != nil {
   179  				return err
   180  			}
   181  			target = relTarget
   182  
   183  			if dir := filepath.Dir(linkName); dir != "" {
   184  				linkName = filepath.Base(linkName)
   185  				if err := os.Chdir(dir); err != nil {
   186  					return err
   187  				}
   188  			}
   189  
   190  		}
   191  
   192  		if err := linkFunc(target, linkName); err != nil {
   193  			return err
   194  		}
   195  
   196  		if conf.relative {
   197  			if err := os.Chdir(originalPath); err != nil {
   198  				return err
   199  			}
   200  		}
   201  
   202  		if conf.verbose {
   203  			fmt.Printf("%q -> %q\n", linkName, target)
   204  		}
   205  	}
   206  
   207  	return nil
   208  }
   209  
   210  func main() {
   211  	var conf config
   212  	flag.BoolVar(&conf.symlink, "s", false, "make symbolic links instead of hard links")
   213  	flag.BoolVar(&conf.verbose, "v", false, "print name of each linked file")
   214  	flag.BoolVar(&conf.force, "f", false, "remove destination files")
   215  	flag.BoolVar(&conf.nondir, "T", false, "treat linkname operand as a non-dir always")
   216  	flag.BoolVar(&conf.prompt, "i", false, "prompt if the user wants overwrite")
   217  	flag.BoolVar(&conf.logical, "L", false, "dereference targets if are symbolic links")
   218  	flag.BoolVar(&conf.physical, "P", false, "make hard links directly to symbolic links")
   219  	flag.BoolVar(&conf.relative, "r", false, "create symlinks relative to link location")
   220  	flag.StringVar(&conf.dirtgt, "t", "", "specify the directory to put the links")
   221  	flag.Parse()
   222  
   223  	args := flag.Args()
   224  	if len(args) == 0 {
   225  		log.Printf("ln: missing file operand")
   226  		flag.Usage()
   227  	}
   228  
   229  	if err := conf.ln(args); err != nil {
   230  		log.Fatalf("ln: link creation failed: %v", err)
   231  	}
   232  }