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