master xplshn/aruu / cmd / posix / ln.c
  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}