5e4080b11f22ffa463f4bc0264b68de2736abd25
[pspp-builds.git] / 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 directory part of FILE_NAME, as a malloc()'d
86    string. */
87 char *
88 fn_dir_name (const char *file_name)
89 {
90   return dir_name (file_name);
91 }
92
93 /* Returns the extension part of FILE_NAME as a malloc()'d string.
94    If FILE_NAME does not have an extension, returns an empty
95    string. */
96 char *
97 fn_extension (const char *file_name)
98 {
99   const char *extension = strrchr (file_name, '.');
100   if (extension == NULL)
101     extension = "";
102   return xstrdup (extension);
103 }
104 \f
105 /* Find out information about files. */
106
107 /* Returns true iff NAME specifies an absolute file name. */
108 bool
109 fn_is_absolute (const char *name)
110 {
111   return IS_ABSOLUTE_FILE_NAME (name);
112 }
113
114 /* Returns true if FILE_NAME is a virtual file that doesn't
115    really exist on disk, false if it's a real file name. */
116 bool
117 fn_is_special (const char *file_name)
118 {
119   if (!strcmp (file_name, "-") || !strcmp (file_name, "stdin")
120       || !strcmp (file_name, "stdout") || !strcmp (file_name, "stderr")
121 #ifdef HAVE_POPEN
122       || file_name[0] == '|'
123       || (*file_name && file_name[strlen (file_name) - 1] == '|')
124 #endif
125       )
126     return true;
127
128   return false;
129 }
130
131 /* Returns true if file with name NAME exists. */
132 bool
133 fn_exists (const char *name)
134 {
135   struct stat temp;
136   return stat (name, &temp) == 0;
137 }
138 \f
139 /* Environment variables. */
140
141 /* Simulates $VER and $ARCH environment variables. */
142 const char *
143 fn_getenv (const char *s)
144 {
145   if (!strcmp (s, "VER"))
146     return fn_getenv_default ("STAT_VER", bare_version);
147   else if (!strcmp (s, "ARCH"))
148     return fn_getenv_default ("STAT_ARCH", host_system);
149   else
150     return getenv (s);
151 }
152
153 /* Returns getenv(KEY) if that's non-NULL; else returns DEF. */
154 const char *
155 fn_getenv_default (const char *key, const char *def)
156 {
157   const char *value = getenv (key);
158   return value ? value : def;
159 }
160 \f
161 /* Basic file handling. */
162
163 #if HAVE_POPEN
164 /* Used for giving an error message on a set_safer security
165    violation. */
166 static FILE *
167 safety_violation (const char *fn)
168 {
169   msg (SE, _("Not opening pipe file `%s' because SAFER option set."), fn);
170   errno = EPERM;
171   return NULL;
172 }
173 #endif
174
175 /* File open routine that understands `-' as stdin/stdout and `|cmd'
176    as a pipe to command `cmd'.  Returns resultant FILE on success,
177    NULL on failure.  If NULL is returned then errno is set to a
178    sensible value.  */
179 FILE *
180 fn_open (const char *fn, const char *mode)
181 {
182   assert (mode[0] == 'r' || mode[0] == 'w' || mode[0] == 'a');
183
184   if (mode[0] == 'r')
185     {
186       if (!strcmp (fn, "stdin") || !strcmp (fn, "-"))
187         return stdin;
188     }
189   else
190     {
191       if (!strcmp (fn, "stdout") || !strcmp (fn, "-"))
192         return stdout;
193       if (!strcmp (fn, "stderr"))
194         return stderr;
195     }
196
197 #if HAVE_POPEN
198   if (fn[0] == '|')
199     {
200       if (settings_get_safer_mode ())
201         return safety_violation (fn);
202
203       return popen (&fn[1], mode[0] == 'r' ? "r" : "w");
204     }
205   else if (*fn && fn[strlen (fn) - 1] == '|')
206     {
207       char *s;
208       FILE *f;
209
210       if (settings_get_safer_mode ())
211         return safety_violation (fn);
212
213       s = xmalloca (strlen (fn));
214       memcpy (s, fn, strlen (fn) - 1);
215       s[strlen (fn) - 1] = 0;
216
217       f = popen (s, mode[0] == 'r' ? "r" : "w");
218
219       freea (s);
220
221       return f;
222     }
223   else
224 #endif
225     return fopen (fn, mode);
226 }
227
228 /* Counterpart to fn_open that closes file F with name FN; returns 0
229    on success, EOF on failure.  If EOF is returned, errno is set to a
230    sensible value. */
231 int
232 fn_close (const char *fn, FILE *f)
233 {
234   if (fileno (f) == STDIN_FILENO
235       || fileno (f) == STDOUT_FILENO
236       || fileno (f) == STDERR_FILENO)
237     return 0;
238 #if HAVE_POPEN
239   else if (fn[0] == '|' || (*fn && fn[strlen (fn) - 1] == '|'))
240     {
241       pclose (f);
242       return 0;
243     }
244 #endif
245   else
246     return fclose (f);
247 }
248
249 /* Creates a new file named FN with the given PERMISSIONS bits,
250    and returns a stream for it or a null pointer on failure.
251    MODE should be "w" or "wb". */
252 FILE *
253 create_stream (const char *fn, const char *mode, mode_t permissions)
254 {
255   int fd;
256   FILE *stream;
257
258   fd = open (fn, O_WRONLY | O_CREAT | O_TRUNC, permissions);
259   if (fd < 0)
260     return NULL;
261
262   stream = fdopen (fd, mode);
263   if (stream == NULL)
264     {
265       int save_errno = errno;
266       close (fd);
267       errno = save_errno;
268     }
269
270   return stream;
271 }
272
273 /* A file's identity:
274
275    - For a file that exists, this is its device and inode.
276
277    - For a file that does not exist, but which has a directory
278      name that exists, this is the device and inode of the
279      directory, plus the file's base name.
280
281    - For a file that does not exist and has a nonexistent
282      directory, this is the file name.
283
284    Windows doesn't have inode numbers, so we just use the name
285    there. */
286 struct file_identity
287 {
288   dev_t device;               /* Device number. */
289   ino_t inode;                /* Inode number. */
290   char *name;                 /* File name, where needed, otherwise NULL. */
291 };
292
293 /* Returns a pointer to a dynamically allocated structure whose
294    value can be used to tell whether two files are actually the
295    same file.  Returns a null pointer if no information about the
296    file is available, perhaps because it does not exist.  The
297    caller is responsible for freeing the structure with
298    fn_free_identity() when finished. */
299 struct file_identity *
300 fn_get_identity (const char *file_name)
301 {
302   struct file_identity *identity = xmalloc (sizeof *identity);
303
304 #if !(defined _WIN32 || defined __WIN32__)
305   struct stat s;
306   if (lstat (file_name, &s) == 0)
307     {
308       identity->device = s.st_dev;
309       identity->inode = s.st_ino;
310       identity->name = NULL;
311     }
312   else
313     {
314       char *dir = dir_name (file_name);
315       if (last_component (file_name) != NULL && stat (dir, &s) == 0)
316         {
317           identity->device = s.st_dev;
318           identity->inode = s.st_ino;
319           identity->name = base_name (file_name);
320         }
321       else
322         {
323           identity->device = 0;
324           identity->inode = 0;
325           identity->name = xstrdup (file_name);
326         }
327       free (dir);
328     }
329 #else /* Windows */
330   char cname[PATH_MAX];
331   int ok = GetFullPathName (file_name, sizeof cname, cname, NULL);
332   identity->device = 0;
333   identity->inode = 0;
334   identity->name = xstrdup (ok ? cname : file_name);
335   str_lowercase (identity->name);
336 #endif /* Windows */
337
338   return identity;
339 }
340
341 /* Frees IDENTITY obtained from fn_get_identity(). */
342 void
343 fn_free_identity (struct file_identity *identity)
344 {
345   if (identity != NULL)
346     {
347       free (identity->name);
348       free (identity);
349     }
350 }
351
352 /* Compares A and B, returning a strcmp()-type result. */
353 int
354 fn_compare_file_identities (const struct file_identity *a,
355                             const struct file_identity *b)
356 {
357   if (a->device != b->device)
358     return a->device < b->device ? -1 : 1;
359   else if (a->inode != b->inode)
360     return a->inode < b->inode ? -1 : 1;
361   else if (a->name != NULL)
362     return b->name != NULL ? strcmp (a->name, b->name) : 1;
363   else
364     return b->name != NULL ? -1 : 0;
365 }
366
367 /* Returns a hash value for IDENTITY. */
368 unsigned int
369 fn_hash_identity (const struct file_identity *identity)
370 {
371   unsigned int hash = hash_int (identity->device, identity->inode);
372   if (identity->name != NULL)
373     hash = hash_string (identity->name, hash);
374   return hash;
375 }
376
377 #ifdef WIN32
378
379 /* Apparently windoze users like to see output dumped into their home directory,
380    not the current directory (!) */
381 const char *
382 default_output_path (void)
383 {
384   static char *path = NULL;
385
386   if ( path == NULL)
387     {
388       /* Windows NT defines HOMEDRIVE and HOMEPATH.  But give preference
389          to HOME, because the user can change HOME.  */
390
391       const char *home_dir = getenv ("HOME");
392       int i;
393
394       if (home_dir == NULL)
395         {
396           const char *home_drive = getenv ("HOMEDRIVE");
397           const char *home_path = getenv ("HOMEPATH");
398
399           if (home_drive != NULL && home_path != NULL)
400             home_dir = xasprintf ("%s%s",
401                                   home_drive, home_path);
402         }
403
404       if (home_dir == NULL)
405         home_dir = "c:/users/default"; /* poor default */
406
407       /* Copy home_dir into path.  Add a slash at the end but
408          only if there isn't already one there, because Windows
409          treats // specially. */
410       if (home_dir[0] == '\0'
411           || strchr ("/\\", home_dir[strlen (home_dir) - 1]) == NULL)
412         path = xasprintf ("%s%c", home_dir, '/');
413       else
414         path = xstrdup (home_dir);
415
416       for(i = 0; i < strlen (path); i++)
417         if (path[i] == '\\') path[i] = '/';
418     }
419
420   return path;
421 }
422
423 #else
424
425 /* ... whereas the rest of the world just likes it to be
426    put "here" for easy access. */
427 const char *
428 default_output_path (void)
429 {
430   static char current_dir[]  = "";
431
432   return current_dir;
433 }
434
435 #endif
436