Imported Upstream version 1.4.16
[platform/upstream/m4.git] / tests / link.c
1 /* Emulate link on platforms that lack it, namely native Windows platforms.
2
3    Copyright (C) 2009-2011 Free Software Foundation, Inc.
4
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 3, or (at your option)
8    any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software Foundation,
17    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
18
19 #include <config.h>
20
21 #include <unistd.h>
22
23 #include <errno.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <sys/stat.h>
27
28 #if !HAVE_LINK
29 # if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
30
31 #  define WIN32_LEAN_AND_MEAN
32 #  include <windows.h>
33
34 /* CreateHardLink was introduced only in Windows 2000.  */
35 typedef BOOL (WINAPI * CreateHardLinkFuncType) (LPCTSTR lpFileName,
36                                                 LPCTSTR lpExistingFileName,
37                                                 LPSECURITY_ATTRIBUTES lpSecurityAttributes);
38 static CreateHardLinkFuncType CreateHardLinkFunc = NULL;
39 static BOOL initialized = FALSE;
40
41 static void
42 initialize (void)
43 {
44   HMODULE kernel32 = GetModuleHandle ("kernel32.dll");
45   if (kernel32 != NULL)
46     {
47       CreateHardLinkFunc =
48         (CreateHardLinkFuncType) GetProcAddress (kernel32, "CreateHardLinkA");
49     }
50   initialized = TRUE;
51 }
52
53 int
54 link (const char *file1, const char *file2)
55 {
56   char *dir;
57   size_t len1 = strlen (file1);
58   size_t len2 = strlen (file2);
59   if (!initialized)
60     initialize ();
61   if (CreateHardLinkFunc == NULL)
62     {
63       /* System does not support hard links.  */
64       errno = EPERM;
65       return -1;
66     }
67   /* Reject trailing slashes on non-directories; mingw does not
68      support hard-linking directories.  */
69   if ((len1 && (file1[len1 - 1] == '/' || file1[len1 - 1] == '\\'))
70       || (len2 && (file2[len2 - 1] == '/' || file2[len2 - 1] == '\\')))
71     {
72       struct stat st;
73       if (stat (file1, &st) == 0 && S_ISDIR (st.st_mode))
74         errno = EPERM;
75       else
76         errno = ENOTDIR;
77       return -1;
78     }
79   /* CreateHardLink("b/.","a",NULL) creates file "b", so we must check
80      that dirname(file2) exists.  */
81   dir = strdup (file2);
82   if (!dir)
83     return -1;
84   {
85     struct stat st;
86     char *p = strchr (dir, '\0');
87     while (dir < p && (*--p != '/' && *p != '\\'));
88     *p = '\0';
89     if (p != dir && stat (dir, &st) == -1)
90       {
91         int saved_errno = errno;
92         free (dir);
93         errno = saved_errno;
94         return -1;
95       }
96     free (dir);
97   }
98   /* Now create the link.  */
99   if (CreateHardLinkFunc (file2, file1, NULL) == 0)
100     {
101       /* It is not documented which errors CreateHardLink() can produce.
102        * The following conversions are based on tests on a Windows XP SP2
103        * system. */
104       DWORD err = GetLastError ();
105       switch (err)
106         {
107         case ERROR_ACCESS_DENIED:
108           errno = EACCES;
109           break;
110
111         case ERROR_INVALID_FUNCTION:    /* fs does not support hard links */
112           errno = EPERM;
113           break;
114
115         case ERROR_NOT_SAME_DEVICE:
116           errno = EXDEV;
117           break;
118
119         case ERROR_PATH_NOT_FOUND:
120         case ERROR_FILE_NOT_FOUND:
121           errno = ENOENT;
122           break;
123
124         case ERROR_INVALID_PARAMETER:
125           errno = ENAMETOOLONG;
126           break;
127
128         case ERROR_TOO_MANY_LINKS:
129           errno = EMLINK;
130           break;
131
132         case ERROR_ALREADY_EXISTS:
133           errno = EEXIST;
134           break;
135
136         default:
137           errno = EIO;
138         }
139       return -1;
140     }
141
142   return 0;
143 }
144
145 # else /* !Windows */
146
147 #  error "This platform lacks a link function, and Gnulib doesn't provide a replacement. This is a bug in Gnulib."
148
149 # endif /* !Windows */
150 #else /* HAVE_LINK */
151
152 # undef link
153
154 /* Create a hard link from FILE1 to FILE2, working around platform bugs.  */
155 int
156 rpl_link (char const *file1, char const *file2)
157 {
158   /* Reject trailing slashes on non-directories.  */
159   size_t len1 = strlen (file1);
160   size_t len2 = strlen (file2);
161   if ((len1 && file1[len1 - 1] == '/')
162       || (len2 && file2[len2 - 1] == '/'))
163     {
164       /* Let link() decide whether hard-linking directories is legal.
165          If stat() fails, then link() should fail for the same reason
166          (although on Solaris 9, link("file/","oops") mistakenly
167          succeeds); if stat() succeeds, require a directory.  */
168       struct stat st;
169       if (stat (file1, &st))
170         return -1;
171       if (!S_ISDIR (st.st_mode))
172         {
173           errno = ENOTDIR;
174           return -1;
175         }
176     }
177   else
178     {
179       /* Fix Cygwin 1.5.x bug where link("a","b/.") creates file "b".  */
180       char *dir = strdup (file2);
181       struct stat st;
182       char *p;
183       if (!dir)
184         return -1;
185       /* We already know file2 does not end in slash.  Strip off the
186          basename, then check that the dirname exists.  */
187       p = strrchr (dir, '/');
188       if (p)
189         {
190           *p = '\0';
191           if (stat (dir, &st) == -1)
192             {
193               int saved_errno = errno;
194               free (dir);
195               errno = saved_errno;
196               return -1;
197             }
198         }
199       free (dir);
200     }
201   return link (file1, file2);
202 }
203 #endif /* HAVE_LINK */