1/* see LICENSE file for copyright and license details */
2
3#include <sys/stat.h>
4
5#include <errno.h>
6#include <fcntl.h>
7#include <libgen.h>
8#include <limits.h>
9#include <stdlib.h>
10#include <string.h>
11#include <unistd.h>
12
13#include "util.h"
14
15#if FEATURE_LN_RELATIVE
16#define MAXPATHPARTS (PATH_MAX / 2)
17
18/* splits an absolute, realpath()-canonicalized path in place on '/',
19 * returning pointers to each component; safe only against realpath()
20 * output, which has no trailing or doubled slashes to worry about */
21static int
22split_path(char *path, char **parts, int max)
23{
24 char *p = path;
25 int n = 0;
26
27 if (*p == '/')
28 p++;
29 while (*p && n < max) {
30 parts[n++] = p;
31 p = strchr(p, '/');
32 if (!p)
33 break;
34 *p++ = '\0';
35 }
36 return n;
37}
38
39/* the text a relative symlink at from_dir/name needs to store to reach
40 * to_path: the common prefix of both canonicalized paths, a ".." per
41 * remaining from_dir component, then whatever is left of to_path */
42static void
43relpath(const char *from_dir, const char *to_path, char *out, size_t outsz)
44{
45 char from_real[PATH_MAX], to_real[PATH_MAX], to_dir_buf[PATH_MAX], to_base_buf[PATH_MAX];
46 char *from_parts[MAXPATHPARTS], *to_parts[MAXPATHPARTS];
47 int nfrom, nto, ncommon, i;
48
49 if (!realpath(from_dir, from_real))
50 eprintf("realpath %s:", from_dir);
51
52 strlcpy(to_dir_buf, to_path, sizeof(to_dir_buf));
53 strlcpy(to_base_buf, to_path, sizeof(to_base_buf));
54 if (!realpath(dirname(to_dir_buf), to_real))
55 eprintf("realpath %s:", to_path);
56
57 nfrom = split_path(from_real, from_parts, MAXPATHPARTS);
58 nto = split_path(to_real, to_parts, MAXPATHPARTS);
59
60 ncommon = 0;
61 while (ncommon < nfrom && ncommon < nto &&
62 strcmp(from_parts[ncommon], to_parts[ncommon]) == 0)
63 ncommon++;
64
65 out[0] = '\0';
66 for (i = ncommon; i < nfrom; i++)
67 strlcat(out, "../", outsz);
68 for (i = ncommon; i < nto; i++) {
69 strlcat(out, to_parts[i], outsz);
70 strlcat(out, "/", outsz);
71 }
72 strlcat(out, basename(to_base_buf), outsz);
73}
74#endif
75
76static void
77usage(void)
78{
79#if FEATURE_LN_RELATIVE
80#if FEATURE_LN_NO_TARGET_DIR
81 eprintf(
82 "usage: %s [-f] [-L | -P | -r | -s] [-T] target [name]\n"
83 " %s [-f] [-L | -P | -r | -s] target ... dir\n",
84 argv0,
85 argv0
86 );
87#else
88 eprintf(
89 "usage: %s [-f] [-L | -P | -r | -s] target [name]\n"
90 " %s [-f] [-L | -P | -r | -s] target ... dir\n",
91 argv0,
92 argv0
93 );
94#endif
95#else
96#if FEATURE_LN_NO_TARGET_DIR
97 eprintf(
98 "usage: %s [-f] [-L | -P | -s] [-T] target [name]\n"
99 " %s [-f] [-L | -P | -s] target ... dir\n",
100 argv0,
101 argv0
102 );
103#else
104 eprintf(
105 "usage: %s [-f] [-L | -P | -s] target [name]\n"
106 " %s [-f] [-L | -P | -s] target ... dir\n",
107 argv0,
108 argv0
109 );
110#endif
111#endif
112}
113
114// ?man ln: make links between files
115// ?man arguments: target [name]
116// ?man create hard or symbolic links between files
117int
118main(int argc, char *argv[])
119{
120 char *targetdir = ".", *target = NULL;
121 int ret = 0, sflag = 0, fflag = 0, dirfd = AT_FDCWD, hastarget = 0, flags = AT_SYMLINK_FOLLOW;
122 struct stat st, tst;
123#if FEATURE_LN_RELATIVE
124 int rflag = 0;
125 char relbuf[PATH_MAX];
126#endif
127#if FEATURE_LN_NO_TARGET_DIR
128 int Tflag = 0;
129#endif
130
131 ARGBEGIN
132 {
133 // ?man -f: force creation of links by removing existing destination
134 // files
135 case 'f':
136 fflag = 1;
137 break;
138 // ?man -L: specify option flag
139 case 'L':
140 flags |= AT_SYMLINK_FOLLOW;
141 break;
142 // ?man -P: specify option flag
143 case 'P':
144 flags &= ~AT_SYMLINK_FOLLOW;
145 break;
146#if FEATURE_LN_RELATIVE
147 // ?man -r: with -s, store the target as a path relative to name's
148 // own directory instead of copying it as given
149 case 'r':
150 rflag = 1;
151 sflag = 1;
152 break;
153#endif
154 // ?man -s: make symbolic links instead of hard links
155 case 's':
156 sflag = 1;
157 break;
158#if FEATURE_LN_NO_TARGET_DIR
159 // ?man -T: treat name as a regular file, never as a directory to
160 // place the link into, even if a file of that name is currently
161 // a directory
162 case 'T':
163 Tflag = 1;
164 break;
165#endif
166 default:
167 usage();
168 }
169 ARGEND
170
171 if (!argc)
172 usage();
173
174 if (argc > 1) {
175#if FEATURE_LN_NO_TARGET_DIR
176 if (Tflag && argc != 2)
177 eprintf("%s: extra operand\n", argv[2]);
178 if (!Tflag && !stat(argv[argc - 1], &st) && S_ISDIR(st.st_mode)) {
179#else
180 if (!stat(argv[argc - 1], &st) && S_ISDIR(st.st_mode)) {
181#endif
182 if ((dirfd = open(argv[argc - 1], O_RDONLY)) < 0)
183 eprintf("open %s:", argv[argc - 1]);
184 targetdir = argv[argc - 1];
185 if (targetdir[strlen(targetdir) - 1] == '/')
186 targetdir[strlen(targetdir) - 1] = '\0';
187 } else if (argc == 2) {
188 hastarget = 1;
189 target = argv[argc - 1];
190 } else {
191 eprintf("%s: not a directory\n", argv[argc - 1]);
192 }
193 argv[argc - 1] = NULL;
194 argc--;
195 }
196
197 for (; *argv; argc--, argv++) {
198 if (!hastarget)
199 target = basename(*argv);
200
201 if (!sflag) {
202 if (stat(*argv, &st) < 0) {
203 weprintf("stat %s:", *argv);
204 ret = 1;
205 continue;
206 } else if (fstatat(dirfd, target, &tst, AT_SYMLINK_NOFOLLOW) < 0) {
207 if (errno != ENOENT) {
208 weprintf("fstatat %s %s:", targetdir, target);
209 ret = 1;
210 continue;
211 }
212 } else if (st.st_dev == tst.st_dev && st.st_ino == tst.st_ino) {
213 if (!fflag) {
214 weprintf(
215 "%s and %s/%s are the same "
216 "file\n",
217 *argv,
218 targetdir,
219 target
220 );
221 ret = 1;
222 }
223 continue;
224 }
225 }
226
227 if (fflag && unlinkat(dirfd, target, 0) < 0 && errno != ENOENT) {
228 weprintf("unlinkat %s %s:", targetdir, target);
229 ret = 1;
230 continue;
231 }
232
233#if FEATURE_LN_RELATIVE
234 if (rflag) {
235 /* targetdir only names the real destination directory when name
236 * was defaulted from target's own basename (the dir-argument
237 * form); an explicit name may itself carry a directory prefix
238 * (ln -r target dir/name), which then names it instead */
239 char dirbuf[PATH_MAX];
240 const char *fromdir = targetdir;
241
242 if (hastarget) {
243 strlcpy(dirbuf, target, sizeof(dirbuf));
244 fromdir = dirname(dirbuf);
245 }
246 relpath(fromdir, *argv, relbuf, sizeof(relbuf));
247 }
248#endif
249
250 if ((sflag ?
251#if FEATURE_LN_RELATIVE
252 symlinkat(rflag ? relbuf : *argv, dirfd, target)
253#else
254 symlinkat(*argv, dirfd, target)
255#endif
256 : linkat(AT_FDCWD, *argv, dirfd, target, flags))
257 < 0) {
258 weprintf("%s %s <- %s/%s:", sflag ? "symlinkat" : "linkat", *argv, targetdir, target);
259 ret = 1;
260 }
261 }
262
263 return ret;
264}