1 /*        $NetBSD: ln.c,v 1.40 2018/08/26 23:01:06 sevan Exp $        */
2 
3 /*-
4  * Copyright (c) 1987, 1993, 1994
5  *        The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. Neither the name of the University nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 #if 0
33 #ifndef lint
34 static char const copyright[] =
35 "@(#) Copyright (c) 1987, 1993, 1994\n\
36           The Regents of the University of California.  All rights reserved.\n";
37 #endif /* not lint */
38 
39 #ifndef lint
40 static char sccsid[] = "@(#)ln.c        8.2 (Berkeley) 3/31/94";
41 #endif /* not lint */
42 #endif
43 #include <sys/cdefs.h>
44 #ifdef __FBSDID
45 __FBSDID("$FreeBSD: head/bin/ln/ln.c 251261 2013-06-02 17:55:00Z eadler $");
46 #endif
47 __RCSID("$NetBSD: ln.c,v 1.40 2018/08/26 23:01:06 sevan Exp $");
48 
49 #include <sys/param.h>
50 #include <sys/stat.h>
51 
52 #include <err.h>
53 #include <errno.h>
54 #include <fcntl.h>
55 #include <libgen.h>
56 #include <limits.h>
57 #include <stdio.h>
58 #include <stdlib.h>
59 #include <string.h>
60 #include <unistd.h>
61 
62 static int          fflag;                        /* Unlink existing files. */
63 static int          Fflag;                        /* Remove empty directories also. */
64 static int          hflag;                        /* Check new name for symlink first. */
65 static int          iflag;                        /* Interactive mode. */
66 static int          Pflag;                        /* Create hard links to symlinks. */
67 static int          sflag;                        /* Symbolic, not hard, link. */
68 static int          vflag;                        /* Verbose output. */
69 static int          wflag;                        /* Warn if symlink target does not
70                                                    * exist, and -f is not enabled. */
71 static char         linkch;
72 
73 static int          linkit(const char *, const char *, int);
74 static __dead void  usage(void);
75 
76 int
main(int argc,char * argv[])77 main(int argc, char *argv[])
78 {
79           struct stat sb;
80           char *p, *targetdir;
81           int ch, exitval;
82 
83           /*
84            * Test for the special case where the utility is called as
85            * "link", for which the functionality provided is greatly
86            * simplified.
87            */
88           if ((p = strrchr(argv[0], '/')) == NULL)
89                     p = argv[0];
90           else
91                     ++p;
92           if (strcmp(p, "link") == 0) {
93                     while (getopt(argc, argv, "") != -1)
94                               usage();
95                     argc -= optind;
96                     argv += optind;
97                     if (argc != 2)
98                               usage();
99                     if (link(argv[0], argv[1]) == -1)
100                               err(EXIT_FAILURE, NULL);
101                     exit(EXIT_SUCCESS);
102           }
103 
104           while ((ch = getopt(argc, argv, "FLPfhinsvw")) != -1)
105                     switch (ch) {
106                     case 'F':
107                               Fflag = 1;
108                               break;
109                     case 'L':
110                               Pflag = 0;
111                               break;
112                     case 'P':
113                               Pflag = 1;
114                               break;
115                     case 'f':
116                               fflag = 1;
117                               iflag = 0;
118                               wflag = 0;
119                               break;
120                     case 'h':
121                     case 'n':
122                               hflag = 1;
123                               break;
124                     case 'i':
125                               iflag = 1;
126                               fflag = 0;
127                               break;
128                     case 's':
129                               sflag = 1;
130                               break;
131                     case 'v':
132                               vflag = 1;
133                               break;
134                     case 'w':
135                               wflag = 1;
136                               break;
137                     case '?':
138                     default:
139                               usage();
140                     }
141 
142           argv += optind;
143           argc -= optind;
144 
145           linkch = sflag ? '-' : '=';
146           if (sflag == 0)
147                     Fflag = 0;
148           if (Fflag == 1 && iflag == 0) {
149                     fflag = 1;
150                     wflag = 0;                    /* Implied when fflag != 0 */
151           }
152 
153           switch(argc) {
154           case 0:
155                     usage();
156                     /* NOTREACHED */
157           case 1:                                 /* ln source */
158                     exit(linkit(argv[0], ".", 1));
159           case 2:                                 /* ln source target */
160                     exit(linkit(argv[0], argv[1], 0));
161           default:
162                     ;
163           }
164                                                   /* ln source1 source2 directory */
165           targetdir = argv[argc - 1];
166           if (hflag && lstat(targetdir, &sb) == 0 && S_ISLNK(sb.st_mode)) {
167                     /*
168                      * We were asked not to follow symlinks, but found one at
169                      * the target--simulate "not a directory" error
170                      */
171                     errno = ENOTDIR;
172                     err(1, "%s", targetdir);
173           }
174           if (stat(targetdir, &sb))
175                     err(1, "%s", targetdir);
176           if (!S_ISDIR(sb.st_mode))
177                     usage();
178           for (exitval = 0; *argv != targetdir; ++argv)
179                     exitval |= linkit(*argv, targetdir, 1);
180           exit(exitval);
181 }
182 
183 /*
184  * Two pathnames refer to the same directory entry if the directories match
185  * and the final components' names match.
186  */
187 static int
samedirent(const char * path1,const char * path2)188 samedirent(const char *path1, const char *path2)
189 {
190           const char *file1, *file2;
191           char pathbuf[PATH_MAX];
192           struct stat sb1, sb2;
193 
194           if (strcmp(path1, path2) == 0)
195                     return 1;
196           file1 = strrchr(path1, '/');
197           if (file1 != NULL)
198                     file1++;
199           else
200                     file1 = path1;
201           file2 = strrchr(path2, '/');
202           if (file2 != NULL)
203                     file2++;
204           else
205                     file2 = path2;
206           if (strcmp(file1, file2) != 0)
207                     return 0;
208           if (file1 - path1 >= PATH_MAX || file2 - path2 >= PATH_MAX)
209                     return 0;
210           if (file1 == path1)
211                     memcpy(pathbuf, ".", 2);
212           else {
213                     memcpy(pathbuf, path1, file1 - path1);
214                     pathbuf[file1 - path1] = '\0';
215           }
216           if (stat(pathbuf, &sb1) != 0)
217                     return 0;
218           if (file2 == path2)
219                     memcpy(pathbuf, ".", 2);
220           else {
221                     memcpy(pathbuf, path2, file2 - path2);
222                     pathbuf[file2 - path2] = '\0';
223           }
224           if (stat(pathbuf, &sb2) != 0)
225                     return 0;
226           return sb1.st_dev == sb2.st_dev && sb1.st_ino == sb2.st_ino;
227 }
228 
229 static int
linkit(const char * source,const char * target,int isdir)230 linkit(const char *source, const char *target, int isdir)
231 {
232           struct stat sb;
233           const char *p;
234           int ch, exists, first;
235           char path[PATH_MAX];
236           char wbuf[PATH_MAX];
237           char bbuf[PATH_MAX];
238 
239           if (!sflag) {
240                     /* If source doesn't exist, quit now. */
241                     if ((Pflag ? lstat : stat)(source, &sb)) {
242                               warn("%s", source);
243                               return (1);
244                     }
245                     /* Only symbolic links to directories. */
246                     if (S_ISDIR(sb.st_mode)) {
247                               errno = EISDIR;
248                               warn("%s", source);
249                               return (1);
250                     }
251           }
252 
253           /*
254            * If the target is a directory (and not a symlink if hflag),
255            * append the source's name.
256            */
257           if (isdir ||
258               (lstat(target, &sb) == 0 && S_ISDIR(sb.st_mode)) ||
259               (!hflag && stat(target, &sb) == 0 && S_ISDIR(sb.st_mode))) {
260                     if (strlcpy(bbuf, source, sizeof(bbuf)) >= sizeof(bbuf) ||
261                         (p = basename(bbuf)) == NULL ||
262                         snprintf(path, sizeof(path), "%s/%s", target, p) >=
263                         (ssize_t)sizeof(path)) {
264                               errno = ENAMETOOLONG;
265                               warn("%s", source);
266                               return (1);
267                     }
268                     target = path;
269           }
270 
271           /*
272            * If the link source doesn't exist, and a symbolic link was
273            * requested, and -w was specified, give a warning.
274            */
275           if (sflag && wflag) {
276                     if (*source == '/') {
277                               /* Absolute link source. */
278                               if (stat(source, &sb) != 0)
279                                          warn("warning: %s inaccessible", source);
280                     } else {
281                               /*
282                                * Relative symlink source.  Try to construct the
283                                * absolute path of the source, by appending `source'
284                                * to the parent directory of the target.
285                                */
286                               strlcpy(bbuf, target, sizeof(bbuf));
287                               p = dirname(bbuf);
288                               if (p != NULL) {
289                                         (void)snprintf(wbuf, sizeof(wbuf), "%s/%s",
290                                                             p, source);
291                                         if (stat(wbuf, &sb) != 0)
292                                                   warn("warning: %s", source);
293                               }
294                     }
295           }
296 
297           /*
298            * If the file exists, first check it is not the same directory entry.
299            */
300           exists = !lstat(target, &sb);
301           if (exists) {
302                     if (!sflag && samedirent(source, target)) {
303                               warnx("%s and %s are the same directory entry",
304                                   source, target);
305                               return (1);
306                     }
307           }
308           /*
309            * Then unlink it forcibly if -f was specified
310            * and interactively if -i was specified.
311            */
312           if (fflag && exists) {
313                     if (Fflag && S_ISDIR(sb.st_mode)) {
314                               if (rmdir(target)) {
315                                         warn("%s", target);
316                                         return (1);
317                               }
318                     } else if (unlink(target)) {
319                               warn("%s", target);
320                               return (1);
321                     }
322           } else if (iflag && exists) {
323                     fflush(stdout);
324                     fprintf(stderr, "replace %s? ", target);
325 
326                     first = ch = getchar();
327                     while(ch != '\n' && ch != EOF)
328                               ch = getchar();
329                     if (first != 'y' && first != 'Y') {
330                               fprintf(stderr, "not replaced\n");
331                               return (1);
332                     }
333 
334                     if (Fflag && S_ISDIR(sb.st_mode)) {
335                               if (rmdir(target)) {
336                                         warn("%s", target);
337                                         return (1);
338                               }
339                     } else if (unlink(target)) {
340                               warn("%s", target);
341                               return (1);
342                     }
343           }
344 
345           /* Attempt the link. */
346           if (sflag ? symlink(source, target) :
347               linkat(AT_FDCWD, source, AT_FDCWD, target,
348               Pflag ? 0 : AT_SYMLINK_FOLLOW)) {
349                     warn("%s", target);
350                     return (1);
351           }
352           if (vflag)
353                     (void)printf("%s %c> %s\n", target, linkch, source);
354           return (0);
355 }
356 
357 static __dead void
usage(void)358 usage(void)
359 {
360           (void)fprintf(stderr,
361               "usage: %s [-L | -P | -s [-F]] [-f | -iw] [-hnv] source_file [target_file]\n"
362               "       %s [-L | -P | -s [-F]] [-f | -iw] [-hnv] source_file ... target_dir\n"
363               "       link source_file target_file\n", getprogname(), getprogname());
364           exit(1);
365 }
366