Module Name: src Committed By: christos Date: Thu Apr 20 21:50:50 UTC 2017
Modified Files: src/bin/ln: ln.1 ln.c Log Message: Replace ours with the FreeBSD version; it is more versatile and handles errors better (does not remove files if it is going to fail when -f). To generate a diff of this commit: cvs rdiff -u -r1.26 -r1.27 src/bin/ln/ln.1 cvs rdiff -u -r1.35 -r1.36 src/bin/ln/ln.c Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/bin/ln/ln.1 diff -u src/bin/ln/ln.1:1.26 src/bin/ln/ln.1:1.27 --- src/bin/ln/ln.1:1.26 Wed Aug 10 13:38:39 2016 +++ src/bin/ln/ln.1 Thu Apr 20 17:50:50 2017 @@ -1,5 +1,5 @@ -.\" $NetBSD: ln.1,v 1.26 2016/08/10 17:38:39 sevan Exp $ -.\" +.\" $NetBSD: ln.1,v 1.27 2017/04/20 21:50:50 christos Exp $ +.\"- .\" Copyright (c) 1980, 1990, 1993 .\" The Regents of the University of California. All rights reserved. .\" @@ -14,7 +14,7 @@ .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. -.\" 3. Neither the name of the University nor the names of its contributors +.\" 4. Neither the name of the University nor the names of its contributors .\" may be used to endorse or promote products derived from this software .\" without specific prior written permission. .\" @@ -31,41 +31,94 @@ .\" SUCH DAMAGE. .\" .\" @(#)ln.1 8.2 (Berkeley) 12/30/93 +.\" $FreeBSD: head/bin/ln/ln.1 244791 2012-12-28 22:06:33Z gjb $ .\" -.Dd August 10, 2016 +.Dd April 20, 2017 .Dt LN 1 .Os .Sh NAME -.Nm ln -.Nd make links +.Nm ln , +.Nm link +.Nd link files .Sh SYNOPSIS .Nm -.Op Fl fhinsv +.Op Fl L | Fl P | Fl s Op Fl F +.Op Fl f | iw +.Op Fl hnv .Ar source_file .Op Ar target_file .Nm -.Op Fl fhinsv -.Ar source_file ... target_dir +.Op Fl L | Fl P | Fl s Op Fl F +.Op Fl f | iw +.Op Fl hnv +.Ar source_file ... +.Ar target_dir +.Nm link +.Ar source_file Ar target_file .Sh DESCRIPTION The .Nm -utility creates a new directory entry (linked file) which has the -same modes as the original file. +utility creates a new directory entry (linked file) for the file name +specified by +.Ar target_file . +The +.Ar target_file +will be created with the same file modes as the +.Ar source_file . It is useful for maintaining multiple copies of a file in many places at once without using up storage for the .Dq copies ; instead, a link .Dq points to the original copy. -There are two types of links: hard links and symbolic links. +There are two types of links; hard links and symbolic links. How a link .Dq points -to a file is one of the differences between a hard or symbolic link. +to a file is one of the differences between a hard and symbolic link. .Pp The options are as follows: .Bl -tag -width flag +.It Fl F +If the target file already exists and is a directory, then remove it +so that the link may occur. +The +.Fl F +option should be used with either +.Fl f +or +.Fl i +options. +If none is specified, +.Fl f +is implied. +The +.Fl F +option is a no-op unless +.Fl s +option is specified. +.It Fl L +When creating a hard link to a symbolic link, +create a hard link to the target of the symbolic link. +This is the default. +This option cancels the +.Fl P +option. +.It Fl P +When creating a hard link to a symbolic link, +create a hard link to the symbolic link itself. +This option cancels the +.Fl L +option. .It Fl f -Unlink any already existing file, permitting the link to occur. +If the target file already exists, +then unlink it so that the link may occur. +(The +.Fl f +option overrides any previous +.Fl i +and +.Fl w +options.) .It Fl h If the .Ar target_file @@ -95,24 +148,26 @@ Same as .Fl h , for compatibility with other .Nm -implementations, namely GNU coreutils. +implementations. .It Fl s Create a symbolic link. .It Fl v Cause .Nm to be verbose, showing files as they are processed. +.It Fl w +Warn if the source of a symbolic link does not currently exist. .El .Pp -By default +By default, .Nm makes .Em hard links. A hard link to a file is indistinguishable from the original directory entry; -any changes to a file are effective independent of the name used to reference +any changes to a file are effectively independent of the name used to reference the file. -Hard links may not normally refer to directories and may not span file systems. +Directories may not be hardlinked, and hard links may not span file systems. .Pp A symbolic link contains the name of the file to which it is linked. @@ -132,7 +187,7 @@ Symbolic links may span file systems and Given one or two arguments, .Nm creates a link to an existing file -.Ar source_file . +.Ar source_file . If .Ar target_file is given, the link has that name; @@ -141,7 +196,7 @@ may also be a directory in which to plac otherwise it is placed in the current directory. If only the directory is specified, the link will be made to the last component of -.Ar source_file . +.Ar source_file . .Pp Given more than two arguments, .Nm @@ -149,6 +204,97 @@ makes links in .Ar target_dir to all the named source files. The links made will have the same name as the files being linked to. +.Pp +When the utility is called as +.Nm link , +exactly two arguments must be supplied, +neither of which may specify a directory. +No options may be supplied in this simple mode of operation, +which performs a +.Xr link 2 +operation using the two passed arguments. +.Sh EXAMPLES +Create a symbolic link named +.Pa /home/src +and point it to +.Pa /usr/src : +.Pp +.Dl # ln -s /usr/src /home/src +.Pp +Hard link +.Pa /usr/local/bin/fooprog +to file +.Pa /usr/local/bin/fooprog-1.0 : +.Pp +.Dl # ln /usr/local/bin/fooprog-1.0 /usr/local/bin/fooprog +.Pp +As an exercise, try the following commands: +.Bd -literal -offset indent +# ls -i /bin/[ +11553 /bin/[ +# ls -i /bin/test +11553 /bin/test +.Ed +.Pp +Note that both files have the same inode; that is, +.Pa /bin/[ +is essentially an alias for the +.Xr test 1 +command. +This hard link exists so +.Xr test 1 +may be invoked from shell scripts, for example, using the +.Li "if [ ]" +construct. +.Pp +In the next example, the second call to +.Nm +removes the original +.Pa foo +and creates a replacement pointing to +.Pa baz : +.Bd -literal -offset indent +# mkdir bar baz +# ln -s bar foo +# ln -shf baz foo +.Ed +.Pp +Without the +.Fl h +option, this would instead leave +.Pa foo +pointing to +.Pa bar +and inside +.Pa foo +create a new symlink +.Pa baz +pointing to itself. +This results from directory-walking. +.Pp +An easy rule to remember is that the argument order for +.Nm +is the same as for +.Xr cp 1 : +The first argument needs to exist, the second one is created. +.Sh COMPATIBILITY +The +.Fl h , +.Fl i , +.Fl n , +.Fl v +and +.Fl w +options are non-standard and their use in scripts is not recommended. +They are provided solely for compatibility with other +.Nm +implementations. +.Pp +The +.Fl F +option is a +.Fx +extension and should not be used in portable scripts. .Sh SEE ALSO .Xr link 2 , .Xr lstat 2 , @@ -162,12 +308,12 @@ The utility conforms to .St -p1003.2-92 . .Pp -The -.Fl v -option is an extension to -.St -p1003.2-92 . +The simplified +.Nm link +command conforms to +.St -susv2 . .Sh HISTORY -A +An .Nm -utility appeared in +command appeared in .At v1 . Index: src/bin/ln/ln.c diff -u src/bin/ln/ln.c:1.35 src/bin/ln/ln.c:1.36 --- src/bin/ln/ln.c:1.35 Mon Aug 29 10:38:30 2011 +++ src/bin/ln/ln.c Thu Apr 20 17:50:50 2017 @@ -1,6 +1,6 @@ -/* $NetBSD: ln.c,v 1.35 2011/08/29 14:38:30 joerg Exp $ */ +/* $NetBSD: ln.c,v 1.36 2017/04/20 21:50:50 christos Exp $ */ -/* +/*- * Copyright (c) 1987, 1993, 1994 * The Regents of the University of California. All rights reserved. * @@ -12,7 +12,7 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors + * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * @@ -29,59 +29,91 @@ * SUCH DAMAGE. */ -#include <sys/cdefs.h> +#if 0 #ifndef lint -__COPYRIGHT("@(#) Copyright (c) 1987, 1993, 1994\ - The Regents of the University of California. All rights reserved."); +static char const copyright[] = +"@(#) Copyright (c) 1987, 1993, 1994\n\ + The Regents of the University of California. All rights reserved.\n"; #endif /* not lint */ #ifndef lint -#if 0 static char sccsid[] = "@(#)ln.c 8.2 (Berkeley) 3/31/94"; -#else -__RCSID("$NetBSD: ln.c,v 1.35 2011/08/29 14:38:30 joerg Exp $"); -#endif #endif /* not lint */ +#endif +#include <sys/cdefs.h> +#ifdef __FBSDID +__FBSDID("$FreeBSD: head/bin/ln/ln.c 251261 2013-06-02 17:55:00Z eadler $"); +#endif +__RCSID("$NetBSD: ln.c,v 1.36 2017/04/20 21:50:50 christos Exp $"); #include <sys/param.h> #include <sys/stat.h> #include <err.h> #include <errno.h> -#include <locale.h> +#include <fcntl.h> +#include <libgen.h> +#include <limits.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> -static int fflag; /* Unlink existing files. */ -static int hflag; /* Check new name for symlink first. */ -static int iflag; /* Interactive mode. */ -static int sflag; /* Symbolic, not hard, link. */ -static int vflag; /* Verbose output */ - - /* System link call. */ -static int (*linkf)(const char *, const char *); -static char linkch; +static int fflag; /* Unlink existing files. */ +static int Fflag; /* Remove empty directories also. */ +static int hflag; /* Check new name for symlink first. */ +static int iflag; /* Interactive mode. */ +static int Pflag; /* Create hard links to symlinks. */ +static int sflag; /* Symbolic, not hard, link. */ +static int vflag; /* Verbose output. */ +static int wflag; /* Warn if symlink target does not + * exist, and -f is not enabled. */ +static char linkch; static int linkit(const char *, const char *, int); -__dead static void usage(void); +static void usage(void); int main(int argc, char *argv[]) { struct stat sb; + char *p, *targetdir; int ch, exitval; - char *sourcedir; - setprogname(argv[0]); - (void)setlocale(LC_ALL, ""); + /* + * Test for the special case where the utility is called as + * "link", for which the functionality provided is greatly + * simplified. + */ + if ((p = strrchr(argv[0], '/')) == NULL) + p = argv[0]; + else + ++p; + if (strcmp(p, "link") == 0) { + while (getopt(argc, argv, "") != -1) + usage(); + argc -= optind; + argv += optind; + if (argc != 2) + usage(); + exit(linkit(argv[0], argv[1], 0)); + } - while ((ch = getopt(argc, argv, "fhinsv")) != -1) + while ((ch = getopt(argc, argv, "FLPfhinsvw")) != -1) switch (ch) { + case 'F': + Fflag = 1; + break; + case 'L': + Pflag = 0; + break; + case 'P': + Pflag = 1; + break; case 'f': fflag = 1; iflag = 0; + wflag = 0; break; case 'h': case 'n': @@ -94,137 +126,238 @@ main(int argc, char *argv[]) case 's': sflag = 1; break; - case 'v': + case 'v': vflag = 1; break; + case 'w': + wflag = 1; + break; case '?': default: usage(); - /* NOTREACHED */ } argv += optind; argc -= optind; - if (sflag) { - linkf = symlink; - linkch = '-'; - } else { - linkf = link; - linkch = '='; + linkch = sflag ? '-' : '='; + if (sflag == 0) + Fflag = 0; + if (Fflag == 1 && iflag == 0) { + fflag = 1; + wflag = 0; /* Implied when fflag != 0 */ } switch(argc) { case 0: usage(); /* NOTREACHED */ - case 1: /* ln target */ + case 1: /* ln source */ exit(linkit(argv[0], ".", 1)); - /* NOTREACHED */ - case 2: /* ln target source */ + case 2: /* ln source target */ exit(linkit(argv[0], argv[1], 0)); - /* NOTREACHED */ + default: + ; } - - /* ln target1 target2 directory */ - sourcedir = argv[argc - 1]; - if (hflag && lstat(sourcedir, &sb) == 0 && S_ISLNK(sb.st_mode)) { - /* we were asked not to follow symlinks, but found one at - the target--simulate "not a directory" error */ + /* ln source1 source2 directory */ + targetdir = argv[argc - 1]; + if (hflag && lstat(targetdir, &sb) == 0 && S_ISLNK(sb.st_mode)) { + /* + * We were asked not to follow symlinks, but found one at + * the target--simulate "not a directory" error + */ errno = ENOTDIR; - err(EXIT_FAILURE, "%s", sourcedir); - /* NOTREACHED */ + err(1, "%s", targetdir); } - if (stat(sourcedir, &sb)) { - err(EXIT_FAILURE, "%s", sourcedir); - /* NOTREACHED */ - } - if (!S_ISDIR(sb.st_mode)) { + if (stat(targetdir, &sb)) + err(1, "%s", targetdir); + if (!S_ISDIR(sb.st_mode)) usage(); - /* NOTREACHED */ - } - for (exitval = 0; *argv != sourcedir; ++argv) - exitval |= linkit(*argv, sourcedir, 1); + for (exitval = 0; *argv != targetdir; ++argv) + exitval |= linkit(*argv, targetdir, 1); exit(exitval); - /* NOTREACHED */ } +/* + * Two pathnames refer to the same directory entry if the directories match + * and the final components' names match. + */ static int -linkit(const char *target, const char *source, int isdir) +samedirent(const char *path1, const char *path2) +{ + const char *file1, *file2; + char pathbuf[PATH_MAX]; + struct stat sb1, sb2; + + if (strcmp(path1, path2) == 0) + return 1; + file1 = strrchr(path1, '/'); + if (file1 != NULL) + file1++; + else + file1 = path1; + file2 = strrchr(path2, '/'); + if (file2 != NULL) + file2++; + else + file2 = path2; + if (strcmp(file1, file2) != 0) + return 0; + if (file1 - path1 >= PATH_MAX || file2 - path2 >= PATH_MAX) + return 0; + if (file1 == path1) + memcpy(pathbuf, ".", 2); + else { + memcpy(pathbuf, path1, file1 - path1); + pathbuf[file1 - path1] = '\0'; + } + if (stat(pathbuf, &sb1) != 0) + return 0; + if (file2 == path2) + memcpy(pathbuf, ".", 2); + else { + memcpy(pathbuf, path2, file2 - path2); + pathbuf[file2 - path2] = '\0'; + } + if (stat(pathbuf, &sb2) != 0) + return 0; + return sb1.st_dev == sb2.st_dev && sb1.st_ino == sb2.st_ino; +} + +static int +linkit(const char *source, const char *target, int isdir) { struct stat sb; const char *p; - char path[MAXPATHLEN]; int ch, exists, first; + char path[PATH_MAX]; + char wbuf[PATH_MAX]; + char bbuf[PATH_MAX]; if (!sflag) { - /* If target doesn't exist, quit now. */ - if (stat(target, &sb)) { - warn("%s", target); + /* If source doesn't exist, quit now. */ + if ((Pflag ? lstat : stat)(source, &sb)) { + warn("%s", source); + return (1); + } + /* Only symbolic links to directories. */ + if (S_ISDIR(sb.st_mode)) { + errno = EISDIR; + warn("%s", source); return (1); } } - /* If the source is a directory (and not a symlink if hflag), - append the target's name. */ + /* + * If the target is a directory (and not a symlink if hflag), + * append the source's name. + */ if (isdir || - (!lstat(source, &sb) && S_ISDIR(sb.st_mode)) || - (!hflag && !stat(source, &sb) && S_ISDIR(sb.st_mode))) { - if ((p = strrchr(target, '/')) == NULL) - p = target; - else - ++p; - (void)snprintf(path, sizeof(path), "%s/%s", source, p); - source = path; + (lstat(target, &sb) == 0 && S_ISDIR(sb.st_mode)) || + (!hflag && stat(target, &sb) == 0 && S_ISDIR(sb.st_mode))) { + if (strlcpy(bbuf, source, sizeof(bbuf)) >= sizeof(bbuf) || + (p = basename(bbuf)) == NULL || + snprintf(path, sizeof(path), "%s/%s", target, p) >= + (ssize_t)sizeof(path)) { + errno = ENAMETOOLONG; + warn("%s", source); + return (1); + } + target = path; } - exists = !lstat(source, &sb); + /* + * If the link source doesn't exist, and a symbolic link was + * requested, and -w was specified, give a warning. + */ + if (sflag && wflag) { + if (*source == '/') { + /* Absolute link source. */ + if (stat(source, &sb) != 0) + warn("warning: %s inaccessible", source); + } else { + /* + * Relative symlink source. Try to construct the + * absolute path of the source, by appending `source' + * to the parent directory of the target. + */ + strlcpy(bbuf, target, sizeof(bbuf)); + p = dirname(bbuf); + if (p != NULL) { + (void)snprintf(wbuf, sizeof(wbuf), "%s/%s", + p, source); + if (stat(wbuf, &sb) != 0) + warn("warning: %s", source); + } + } + } /* - * If the file exists, then unlink it forcibly if -f was specified + * If the file exists, first check it is not the same directory entry. + */ + exists = !lstat(target, &sb); + if (exists) { + if (!sflag && samedirent(source, target)) { + warnx("%s and %s are the same directory entry", + source, target); + return (1); + } + } + /* + * Then unlink it forcibly if -f was specified * and interactively if -i was specified. */ if (fflag && exists) { - if (unlink(source)) { - warn("%s", source); + if (Fflag && S_ISDIR(sb.st_mode)) { + if (rmdir(target)) { + warn("%s", target); + return (1); + } + } else if (unlink(target)) { + warn("%s", target); return (1); } } else if (iflag && exists) { fflush(stdout); - (void)fprintf(stderr, "replace %s? ", source); + fprintf(stderr, "replace %s? ", target); first = ch = getchar(); - while (ch != '\n' && ch != EOF) + while(ch != '\n' && ch != EOF) ch = getchar(); if (first != 'y' && first != 'Y') { - (void)fprintf(stderr, "not replaced\n"); + fprintf(stderr, "not replaced\n"); return (1); } - if (unlink(source)) { - warn("%s", source); + if (Fflag && S_ISDIR(sb.st_mode)) { + if (rmdir(target)) { + warn("%s", target); + return (1); + } + } else if (unlink(target)) { + warn("%s", target); return (1); } } /* Attempt the link. */ - if ((*linkf)(target, source)) { - warn("%s", source); + if (sflag ? symlink(source, target) : + linkat(AT_FDCWD, source, AT_FDCWD, target, + Pflag ? 0 : AT_SYMLINK_FOLLOW)) { + warn("%s", target); return (1); } if (vflag) - (void)printf("%s %c> %s\n", source, linkch, target); - + (void)printf("%s %c> %s\n", target, linkch, source); return (0); } static void usage(void) { - - (void)fprintf(stderr, - "usage:\t%s [-fhinsv] file1 file2\n\t%s [-fhinsv] file ... directory\n", - getprogname(), getprogname()); + (void)fprintf(stderr, "%s\n%s\n%s\n", + "usage: ln [-s [-F] | -L | -P] [-f | -i] [-hnv] source_file [target_file]", + " ln [-s [-F] | -L | -P] [-f | -i] [-hnv] source_file ... target_dir", + " link source_file target_file"); exit(1); - /* NOTREACHED */ }