/* Create a hard link relative to open directories. Copyright (C) 2009-2024 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* written by Eric Blake */ #include #include #include #include #include #include #include #include #include "areadlink.h" #include "dirname.h" #include "eloop-threshold.h" #include "filenamecat.h" #include "openat-priv.h" #if !HAVE_LINKAT || LINKAT_SYMLINK_NOTSUP /* Create a link. If FILE1 is a symlink, either create a hardlink to that symlink, or fake it by creating an identical symlink. */ # if LINK_FOLLOWS_SYMLINKS == 0 # define link_immediate link # else static int link_immediate (char const *file1, char const *file2) { char *target = areadlink (file1); if (target) { /* A symlink cannot be modified in-place. Therefore, creating an identical symlink behaves like a hard link to a symlink, except for incorrect st_ino and st_nlink. However, we must be careful of EXDEV. */ struct stat st1; struct stat st2; char *dir = mdir_name (file2); if (!dir) { free (target); errno = ENOMEM; return -1; } if (lstat (file1, &st1) == 0 && stat (dir, &st2) == 0) { if (st1.st_dev == st2.st_dev) { int result = symlink (target, file2); free (target); free (dir); return result; } free (target); free (dir); errno = EXDEV; return -1; } free (target); free (dir); } if (errno == ENOMEM) return -1; return link (file1, file2); } # endif /* LINK_FOLLOWS_SYMLINKS == 0 */ /* Create a link. If FILE1 is a symlink, create a hardlink to the canonicalized file. */ # if 0 < LINK_FOLLOWS_SYMLINKS # define link_follow link # else static int link_follow (char const *file1, char const *file2) { char *name = (char *) file1; char *target; int result; int i = __eloop_threshold (); /* Using realpath or canonicalize_file_name is too heavy-handed: we don't need an absolute name, and we don't need to resolve intermediate symlinks, just the basename of each iteration. */ while (i-- && (target = areadlink (name))) { if (IS_ABSOLUTE_FILE_NAME (target)) { if (name != file1) free (name); name = target; } else { char *dir = mdir_name (name); if (name != file1) free (name); if (!dir) { free (target); errno = ENOMEM; return -1; } name = mfile_name_concat (dir, target, NULL); free (dir); free (target); if (!name) { errno = ENOMEM; return -1; } } } if (i < 0) { target = NULL; errno = ELOOP; } if (!target && errno != EINVAL) { if (name != file1) free (name); return -1; } result = link (name, file2); if (name != file1) free (name); return result; } # endif /* 0 < LINK_FOLLOWS_SYMLINKS */ /* On Solaris, link() doesn't follow symlinks by default, but does so as soon as a library or executable takes part in the program that has been compiled with "c99" or "cc -xc99=all" or "cc ... /usr/lib/values-xpg4.o ...". */ # if LINK_FOLLOWS_SYMLINKS == -1 /* Reduce the penalty of link_immediate and link_follow by incorporating the knowledge that link()'s behaviour depends on the __xpg4 variable. */ extern int __xpg4; static int solaris_optimized_link_immediate (char const *file1, char const *file2) { if (__xpg4 == 0) return link (file1, file2); return link_immediate (file1, file2); } static int solaris_optimized_link_follow (char const *file1, char const *file2) { if (__xpg4 != 0) return link (file1, file2); return link_follow (file1, file2); } # define link_immediate solaris_optimized_link_immediate # define link_follow solaris_optimized_link_follow # endif #endif /* !HAVE_LINKAT || LINKAT_SYMLINK_NOTSUP */ #if !HAVE_LINKAT /* Create a link to FILE1, in the directory open on descriptor FD1, to FILE2, in the directory open on descriptor FD2. If FILE1 is a symlink, FLAG controls whether to dereference FILE1 first. If possible, do it without changing the working directory. Otherwise, resort to using save_cwd/fchdir, then rename/restore_cwd. If either the save_cwd or the restore_cwd fails, then give a diagnostic and exit nonzero. */ int linkat (int fd1, char const *file1, int fd2, char const *file2, int flag) { if (flag & ~AT_SYMLINK_FOLLOW) { errno = EINVAL; return -1; } return at_func2 (fd1, file1, fd2, file2, flag ? link_follow : link_immediate); } #else /* HAVE_LINKAT */ # undef linkat /* Create a link. If FILE1 is a symlink, create a hardlink to the canonicalized file. */ static int linkat_follow (int fd1, char const *file1, int fd2, char const *file2) { char *name = (char *) file1; char *target; int result; int i = __eloop_threshold (); /* There is no realpathat. */ while (i-- && (target = areadlinkat (fd1, name))) { if (IS_ABSOLUTE_FILE_NAME (target)) { if (name != file1) free (name); name = target; } else { char *dir = mdir_name (name); if (name != file1) free (name); if (!dir) { free (target); errno = ENOMEM; return -1; } name = mfile_name_concat (dir, target, NULL); free (dir); free (target); if (!name) { errno = ENOMEM; return -1; } } } if (i < 0) { target = NULL; errno = ELOOP; } if (!target && errno != EINVAL) { if (name != file1) free (name); return -1; } result = linkat (fd1, name, fd2, file2, 0); if (name != file1) free (name); return result; } /* Like linkat, but guarantee that AT_SYMLINK_FOLLOW works even on older Linux kernels. */ int rpl_linkat (int fd1, char const *file1, int fd2, char const *file2, int flag) { if (flag & ~AT_SYMLINK_FOLLOW) { errno = EINVAL; return -1; } # if LINKAT_TRAILING_SLASH_BUG /* Reject trailing slashes on non-directories. */ { size_t len1 = strlen (file1); size_t len2 = strlen (file2); if ((len1 && file1[len1 - 1] == '/') || (len2 && file2[len2 - 1] == '/')) { /* Let linkat() decide whether hard-linking directories is legal. If fstatat() fails, then linkat() should fail for the same reason; if fstatat() succeeds, require a directory. */ struct stat st; if (fstatat (fd1, file1, &st, flag ? 0 : AT_SYMLINK_NOFOLLOW)) return -1; if (!S_ISDIR (st.st_mode)) { errno = ENOTDIR; return -1; } } } # endif if (!flag) { int result = linkat (fd1, file1, fd2, file2, flag); # if LINKAT_SYMLINK_NOTSUP /* OS X 10.10 has linkat() but it doesn't support hardlinks to symlinks. Fallback to our emulation in that case. */ if (result == -1 && (errno == ENOTSUP || errno == EOPNOTSUPP)) return at_func2 (fd1, file1, fd2, file2, link_immediate); # endif return result; } /* Cache the information on whether the system call really works. */ { static int have_follow_really; /* 0 = unknown, 1 = yes, -1 = no */ if (0 <= have_follow_really) { int result = linkat (fd1, file1, fd2, file2, flag); if (!(result == -1 && errno == EINVAL)) { have_follow_really = 1; return result; } have_follow_really = -1; } } return linkat_follow (fd1, file1, fd2, file2); } #endif /* HAVE_LINKAT */