Use a better method of identifying files on w32
[pspp] / src / data / file-name.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 1997-9, 2000, 2006, 2007, 2009, 2010, 2011 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include "data/file-name.h"
20
21 #include <ctype.h>
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <sys/stat.h>
27 #include <unistd.h>
28
29 #include "data/settings.h"
30 #include "libpspp/hash-functions.h"
31 #include "libpspp/message.h"
32 #include "libpspp/str.h"
33 #include "libpspp/version.h"
34
35 #include "gl/dirname.h"
36 #include "gl/dosname.h"
37 #include "gl/intprops.h"
38 #include "gl/minmax.h"
39 #include "gl/relocatable.h"
40 #include "gl/xalloc.h"
41 #include "gl/xmalloca.h"
42
43 #include "gettext.h"
44 #define _(msgid) gettext (msgid)
45
46 #if defined _WIN32 || defined __WIN32__
47 #define WIN32_LEAN_AND_MEAN  /* avoid including junk */
48 #include <windows.h>
49 #endif
50 \f
51 /* Functions for performing operations on file names. */
52
53 /* Searches for a configuration file with name NAME in the directories given in
54    PATH, which is terminated by a null pointer.  Returns the full name of the
55    first file found, which the caller is responsible for freeing with free(),
56    or NULL if none is found. */
57 char *
58 fn_search_path (const char *base_name, char **path)
59 {
60   size_t i;
61
62   if (fn_is_absolute (base_name))
63     return xstrdup (base_name);
64
65   for (i = 0; path[i] != NULL; i++)
66     {
67       const char *dir = path[i];
68       char *file;
69
70       if (!strcmp (dir, "") || !strcmp (dir, "."))
71         file = xstrdup (base_name);
72       else if (ISSLASH (dir[strlen (dir) - 1]))
73         file = xasprintf ("%s%s", dir, base_name);
74       else
75         file = xasprintf ("%s/%s", dir, base_name);
76
77       if (fn_exists (file))
78         return file;
79       free (file);
80     }
81
82   return NULL;
83 }
84
85 /* Returns the extension part of FILE_NAME as a malloc()'d string.
86    If FILE_NAME does not have an extension, returns an empty
87    string. */
88 char *
89 fn_extension (const char *file_name)
90 {
91   const char *extension = strrchr (file_name, '.');
92   if (extension == NULL)
93     extension = "";
94   return xstrdup (extension);
95 }
96 \f
97 /* Find out information about files. */
98
99 /* Returns true iff NAME specifies an absolute file name. */
100 bool
101 fn_is_absolute (const char *name)
102 {
103   return IS_ABSOLUTE_FILE_NAME (name);
104 }
105
106 /* Returns true if file with name NAME exists, and that file is not a
107    directory */
108 bool
109 fn_exists (const char *name)
110 {
111   struct stat temp;
112   if ( stat (name, &temp) != 0 )
113     return false;
114
115   return ! S_ISDIR (temp.st_mode);
116 }
117
118 \f
119 /* Basic file handling. */
120
121 #if HAVE_POPEN
122 /* Used for giving an error message on a set_safer security
123    violation. */
124 static FILE *
125 safety_violation (const char *fn)
126 {
127   msg (SE, _("Not opening pipe file `%s' because %s option set."), fn, "SAFER");
128   errno = EPERM;
129   return NULL;
130 }
131 #endif
132
133 /* File open routine that understands `-' as stdin/stdout and `|cmd'
134    as a pipe to command `cmd'.  Returns resultant FILE on success,
135    NULL on failure.  If NULL is returned then errno is set to a
136    sensible value.  */
137 FILE *
138 fn_open (const char *fn, const char *mode)
139 {
140   assert (mode[0] == 'r' || mode[0] == 'w' || mode[0] == 'a');
141
142   if (mode[0] == 'r')
143     {
144       if (!strcmp (fn, "stdin") || !strcmp (fn, "-"))
145         return stdin;
146     }
147   else
148     {
149       if (!strcmp (fn, "stdout") || !strcmp (fn, "-"))
150         return stdout;
151       if (!strcmp (fn, "stderr"))
152         return stderr;
153     }
154
155 #if HAVE_POPEN
156   if (fn[0] == '|')
157     {
158       if (settings_get_safer_mode ())
159         return safety_violation (fn);
160
161       return popen (&fn[1], mode[0] == 'r' ? "r" : "w");
162     }
163   else if (*fn && fn[strlen (fn) - 1] == '|')
164     {
165       char *s;
166       FILE *f;
167
168       if (settings_get_safer_mode ())
169         return safety_violation (fn);
170
171       s = xmalloca (strlen (fn));
172       memcpy (s, fn, strlen (fn) - 1);
173       s[strlen (fn) - 1] = 0;
174
175       f = popen (s, mode[0] == 'r' ? "r" : "w");
176
177       freea (s);
178
179       return f;
180     }
181   else
182 #endif
183     return fopen (fn, mode);
184 }
185
186 /* Counterpart to fn_open that closes file F with name FN; returns 0
187    on success, EOF on failure.  If EOF is returned, errno is set to a
188    sensible value. */
189 int
190 fn_close (const char *fn, FILE *f)
191 {
192   if (fileno (f) == STDIN_FILENO
193       || fileno (f) == STDOUT_FILENO
194       || fileno (f) == STDERR_FILENO)
195     return 0;
196 #if HAVE_POPEN
197   else if (fn[0] == '|' || (*fn && fn[strlen (fn) - 1] == '|'))
198     {
199       pclose (f);
200       return 0;
201     }
202 #endif
203   else
204     return fclose (f);
205 }
206
207
208 /* A file's identity:
209
210    - For a file that exists, this is its device and inode.
211
212    - For a file that does not exist, but which has a directory
213      name that exists, this is the device and inode of the
214      directory, plus the file's base name.
215
216    - For a file that does not exist and has a nonexistent
217      directory, this is the file name.
218
219    Windows doesn't have inode numbers, so we just use the name
220    there. */
221 struct file_identity
222 {
223   dev_t device;               /* Device number. */
224   ino_t inode;                /* Inode number. */
225   char *name;                 /* File name, where needed, otherwise NULL. */
226 };
227
228 /* Returns a pointer to a dynamically allocated structure whose
229    value can be used to tell whether two files are actually the
230    same file.  Returns a null pointer if no information about the
231    file is available, perhaps because it does not exist.  The
232    caller is responsible for freeing the structure with
233    fn_free_identity() when finished. */
234 struct file_identity *
235 fn_get_identity (const char *file_name)
236 {
237   struct file_identity *identity = xmalloc (sizeof *identity);
238
239 #if !(defined _WIN32 || defined __WIN32__)
240   struct stat s;
241   if (lstat (file_name, &s) == 0)
242     {
243       identity->device = s.st_dev;
244       identity->inode = s.st_ino;
245       identity->name = NULL;
246     }
247   else
248     {
249       char *dir = dir_name (file_name);
250       if (last_component (file_name) != NULL && stat (dir, &s) == 0)
251         {
252           identity->device = s.st_dev;
253           identity->inode = s.st_ino;
254           identity->name = base_name (file_name);
255         }
256       else
257         {
258           identity->device = 0;
259           identity->inode = 0;
260           identity->name = xstrdup (file_name);
261         }
262       free (dir);
263     }
264 #else /* Windows */
265   bool ok = false;
266   HANDLE h = CreateFile (file_name, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
267   if (h != INVALID_HANDLE_VALUE)
268   {
269     BY_HANDLE_FILE_INFORMATION fi;
270     ok = GetFileInformationByHandle (h, &fi);
271     if (ok)
272       {
273         identity->device = fi.dwVolumeSerialNumber;
274         identity->inode = fi.nFileIndexHigh << 16 | fi.nFileIndexLow;
275         identity->name = 0;
276       }
277     CloseHandle (h);
278   }
279
280   if (!ok)
281     {
282       identity->device = 0;
283       identity->inode = 0;
284
285       size_t bufsize;
286       size_t pathlen = 255;
287       char *cname = NULL;
288       do 
289       {
290         bufsize = pathlen;
291         cname = xrealloc (cname, bufsize);
292         pathlen = GetFullPathName (file_name, bufsize, cname, NULL);
293       }
294       while (pathlen > bufsize);
295       identity->name = xstrdup (cname);
296       free (cname);
297       str_lowercase (identity->name);
298     }
299 #endif /* Windows */
300
301   return identity;
302 }
303
304 /* Frees IDENTITY obtained from fn_get_identity(). */
305 void
306 fn_free_identity (struct file_identity *identity)
307 {
308   if (identity != NULL)
309     {
310       free (identity->name);
311       free (identity);
312     }
313 }
314
315 /* Compares A and B, returning a strcmp()-type result. */
316 int
317 fn_compare_file_identities (const struct file_identity *a,
318                             const struct file_identity *b)
319 {
320   if (a->device != b->device)
321     return a->device < b->device ? -1 : 1;
322   else if (a->inode != b->inode)
323     return a->inode < b->inode ? -1 : 1;
324   else if (a->name != NULL)
325     return b->name != NULL ? strcmp (a->name, b->name) : 1;
326   else
327     return b->name != NULL ? -1 : 0;
328 }
329
330 /* Returns a hash value for IDENTITY. */
331 unsigned int
332 fn_hash_identity (const struct file_identity *identity)
333 {
334   unsigned int hash = hash_int (identity->device, identity->inode);
335   if (identity->name != NULL)
336     hash = hash_string (identity->name, hash);
337   return hash;
338 }
339
340 #ifdef WIN32
341
342 /* Apparently windoze users like to see output dumped into their home directory,
343    not the current directory (!) */
344 const char *
345 default_output_path (void)
346 {
347   static char *path = NULL;
348
349   if ( path == NULL)
350     {
351       /* Windows NT defines HOMEDRIVE and HOMEPATH.  But give preference
352          to HOME, because the user can change HOME.  */
353
354       const char *home_dir = getenv ("HOME");
355       int i;
356
357       if (home_dir == NULL)
358         {
359           const char *home_drive = getenv ("HOMEDRIVE");
360           const char *home_path = getenv ("HOMEPATH");
361
362           if (home_drive != NULL && home_path != NULL)
363             home_dir = xasprintf ("%s%s",
364                                   home_drive, home_path);
365         }
366
367       if (home_dir == NULL)
368         home_dir = "c:/users/default"; /* poor default */
369
370       /* Copy home_dir into path.  Add a slash at the end but
371          only if there isn't already one there, because Windows
372          treats // specially. */
373       if (home_dir[0] == '\0'
374           || strchr ("/\\", home_dir[strlen (home_dir) - 1]) == NULL)
375         path = xasprintf ("%s%c", home_dir, '/');
376       else
377         path = xstrdup (home_dir);
378
379       for(i = 0; i < strlen (path); i++)
380         if (path[i] == '\\') path[i] = '/';
381     }
382
383   return path;
384 }
385
386 #else
387
388 /* ... whereas the rest of the world just likes it to be
389    put "here" for easy access. */
390 const char *
391 default_output_path (void)
392 {
393   static char current_dir[]  = "";
394
395   return current_dir;
396 }
397
398 #endif
399