| /* Emulate link on platforms that lack it, namely native Windows platforms. |
| |
| Copyright (C) 2009-2020 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 2, 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 <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> |
| |
| /* Avoid warnings from gcc -Wcast-function-type. */ |
| # define GetProcAddress \ |
| (void *) GetProcAddress |
| |
| /* CreateHardLink was introduced only in Windows 2000. */ |
| typedef BOOL (WINAPI * CreateHardLinkFuncType) (LPCTSTR lpFileName, |
| LPCTSTR 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; |
| } |
| |
| int |
| link (const char *file1, const char *file2) |
| { |
| char *dir; |
| size_t len1 = strlen (file1); |
| size_t len2 = strlen (file2); |
| if (!initialized) |
| initialize (); |
| if (CreateHardLinkFunc == NULL) |
| { |
| /* System does not support hard links. */ |
| errno = EPERM; |
| return -1; |
| } |
| /* Reject trailing slashes on non-directories; mingw does not |
| support hard-linking directories. */ |
| if ((len1 && (file1[len1 - 1] == '/' || file1[len1 - 1] == '\\')) |
| || (len2 && (file2[len2 - 1] == '/' || file2[len2 - 1] == '\\'))) |
| { |
| struct stat st; |
| if (stat (file1, &st) == 0 && S_ISDIR (st.st_mode)) |
| errno = EPERM; |
| else |
| errno = ENOTDIR; |
| 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) == -1) |
| { |
| int saved_errno = errno; |
| free (dir); |
| errno = saved_errno; |
| 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)) |
| { |
| 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) == -1) |
| { |
| int saved_errno = errno; |
| free (dir); |
| errno = saved_errno; |
| return -1; |
| } |
| } |
| free (dir); |
| } |
| return link (file1, file2); |
| } |
| #endif /* HAVE_LINK */ |