gitlab.com/apertussolutions/u-root@v7.0.0+incompatible/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 }