summaryrefslogtreecommitdiffstats
path: root/lib/link.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/link.c')
-rw-r--r--lib/link.c237
1 files changed, 237 insertions, 0 deletions
diff --git a/lib/link.c b/lib/link.c
new file mode 100644
index 0000000..2c81e2f
--- /dev/null
+++ b/lib/link.c
@@ -0,0 +1,237 @@
+/* Emulate link on platforms that lack it, namely native Windows platforms.
+
+ Copyright (C) 2009-2023 Free Software Foundation, Inc.
+
+ This file is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ This file 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+#include <unistd.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#if !HAVE_LINK
+# if defined _WIN32 && ! defined __CYGWIN__
+
+# define WIN32_LEAN_AND_MEAN
+# include <windows.h>
+
+/* Don't assume that UNICODE is not defined. */
+# undef GetModuleHandle
+# define GetModuleHandle GetModuleHandleA
+# undef CreateHardLink
+# define CreateHardLink CreateHardLinkA
+
+# if !(_WIN32_WINNT >= _WIN32_WINNT_WINXP)
+
+/* Avoid warnings from gcc -Wcast-function-type. */
+# define GetProcAddress \
+ (void *) GetProcAddress
+
+/* CreateHardLink was introduced only in Windows 2000. */
+typedef BOOL (WINAPI * CreateHardLinkFuncType) (LPCSTR lpFileName,
+ LPCSTR lpExistingFileName,
+ LPSECURITY_ATTRIBUTES lpSecurityAttributes);
+static CreateHardLinkFuncType CreateHardLinkFunc = NULL;
+static BOOL initialized = FALSE;
+
+static void
+initialize (void)
+{
+ HMODULE kernel32 = GetModuleHandle ("kernel32.dll");
+ if (kernel32 != NULL)
+ {
+ CreateHardLinkFunc =
+ (CreateHardLinkFuncType) GetProcAddress (kernel32, "CreateHardLinkA");
+ }
+ initialized = TRUE;
+}
+
+# else
+
+# define CreateHardLinkFunc CreateHardLink
+
+# endif
+
+int
+link (const char *file1, const char *file2)
+{
+ char *dir;
+ size_t len1 = strlen (file1);
+ size_t len2 = strlen (file2);
+
+# if !(_WIN32_WINNT >= _WIN32_WINNT_WINXP)
+ if (!initialized)
+ initialize ();
+# endif
+
+ if (CreateHardLinkFunc == NULL)
+ {
+ /* System does not support hard links. */
+ errno = EPERM;
+ return -1;
+ }
+ /* Reject trailing slashes on non-directories; native Windows does not
+ support hard-linking directories. */
+ if ((len1 && (file1[len1 - 1] == '/' || file1[len1 - 1] == '\\'))
+ || (len2 && (file2[len2 - 1] == '/' || file2[len2 - 1] == '\\')))
+ {
+ /* If stat() fails, then link() should fail for the same reason. */
+ struct stat st;
+ if (stat (file1, &st))
+ {
+ if (errno == EOVERFLOW)
+ /* It's surely a file, not a directory (see stat-w32.c). */
+ errno = ENOTDIR;
+ return -1;
+ }
+ if (!S_ISDIR (st.st_mode))
+ errno = ENOTDIR;
+ else
+ errno = EPERM;
+ return -1;
+ }
+ /* CreateHardLink("b/.","a",NULL) creates file "b", so we must check
+ that dirname(file2) exists. */
+ dir = strdup (file2);
+ if (!dir)
+ return -1;
+ {
+ struct stat st;
+ char *p = strchr (dir, '\0');
+ while (dir < p && (*--p != '/' && *p != '\\'));
+ *p = '\0';
+ if (p != dir && stat (dir, &st) != 0 && errno != EOVERFLOW)
+ {
+ free (dir);
+ return -1;
+ }
+ free (dir);
+ }
+ /* Now create the link. */
+ if (CreateHardLinkFunc (file2, file1, NULL) == 0)
+ {
+ /* It is not documented which errors CreateHardLink() can produce.
+ * The following conversions are based on tests on a Windows XP SP2
+ * system. */
+ DWORD err = GetLastError ();
+ switch (err)
+ {
+ case ERROR_ACCESS_DENIED:
+ errno = EACCES;
+ break;
+
+ case ERROR_INVALID_FUNCTION: /* fs does not support hard links */
+ errno = EPERM;
+ break;
+
+ case ERROR_NOT_SAME_DEVICE:
+ errno = EXDEV;
+ break;
+
+ case ERROR_PATH_NOT_FOUND:
+ case ERROR_FILE_NOT_FOUND:
+ errno = ENOENT;
+ break;
+
+ case ERROR_INVALID_PARAMETER:
+ errno = ENAMETOOLONG;
+ break;
+
+ case ERROR_TOO_MANY_LINKS:
+ errno = EMLINK;
+ break;
+
+ case ERROR_ALREADY_EXISTS:
+ errno = EEXIST;
+ break;
+
+ default:
+ errno = EIO;
+ }
+ return -1;
+ }
+
+ return 0;
+}
+
+# else /* !Windows */
+
+# error "This platform lacks a link function, and Gnulib doesn't provide a replacement. This is a bug in Gnulib."
+
+# endif /* !Windows */
+#else /* HAVE_LINK */
+
+# undef link
+
+/* Create a hard link from FILE1 to FILE2, working around platform bugs. */
+int
+rpl_link (char const *file1, char const *file2)
+{
+ size_t len1;
+ size_t len2;
+ struct stat st;
+
+ /* Don't allow IRIX to dereference dangling file2 symlink. */
+ if (lstat (file2, &st) == 0 || errno == EOVERFLOW)
+ {
+ errno = EEXIST;
+ return -1;
+ }
+
+ /* Reject trailing slashes on non-directories. */
+ len1 = strlen (file1);
+ len2 = strlen (file2);
+ if ((len1 && file1[len1 - 1] == '/')
+ || (len2 && file2[len2 - 1] == '/'))
+ {
+ /* Let link() decide whether hard-linking directories is legal.
+ If stat() fails, then link() should fail for the same reason
+ (although on Solaris 9, link("file/","oops") mistakenly
+ succeeds); if stat() succeeds, require a directory. */
+ if (stat (file1, &st))
+ return -1;
+ if (!S_ISDIR (st.st_mode))
+ {
+ errno = ENOTDIR;
+ return -1;
+ }
+ }
+ else
+ {
+ /* Fix Cygwin 1.5.x bug where link("a","b/.") creates file "b". */
+ char *dir = strdup (file2);
+ char *p;
+ if (!dir)
+ return -1;
+ /* We already know file2 does not end in slash. Strip off the
+ basename, then check that the dirname exists. */
+ p = strrchr (dir, '/');
+ if (p)
+ {
+ *p = '\0';
+ if (stat (dir, &st) != 0 && errno != EOVERFLOW)
+ {
+ free (dir);
+ return -1;
+ }
+ }
+ free (dir);
+ }
+ return link (file1, file2);
+}
+#endif /* HAVE_LINK */