2865bd33e09c9794a79b84a90bf0190ef0612aa9
[pspp-builds.git] / src / data / filename.c
1 /* PSPP - computes sample statistics.
2    Copyright (C) 1997-9, 2000, 2006 Free Software Foundation, Inc.
3    Written by Ben Pfaff <blp@gnu.org>.
4
5    This program is free software; you can redistribute it and/or
6    modify it under the terms of the GNU General Public License as
7    published by the Free Software Foundation; either version 2 of the
8    License, or (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful, but
11    WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    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
17    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18    02110-1301, USA. */
19
20 #include <config.h>
21 #include "message.h"
22 #include "filename.h"
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <ctype.h>
26 #include <errno.h>
27 #include "alloc.h"
28 #include "message.h"
29 #include "str.h"
30 #include "settings.h"
31 #include "version.h"
32 #include "xreadlink.h"
33
34 #include "gettext.h"
35 #define _(msgid) gettext (msgid)
36
37 #include "debug-print.h"
38
39 /* PORTME: Everything in this file is system dependent. */
40
41 #ifdef unix
42 #include <pwd.h>
43 #if HAVE_UNISTD_H
44 #include <unistd.h>
45 #endif
46 #include <sys/stat.h>
47 #include "stat-macros.h"
48 #endif
49
50 #ifdef __WIN32__
51 #define NOGDI
52 #define NOUSER
53 #define NONLS
54 #include <win32/windows.h>
55 #endif
56
57 #if __DJGPP__
58 #include <sys/stat.h>
59 #endif
60 \f
61 /* Initialization. */
62
63 const char *config_path;
64
65 void
66 fn_init (void)
67 {
68   config_path = fn_getenv_default ("STAT_CONFIG_PATH", default_config_path);
69 }
70 \f
71 /* Functions for performing operations on filenames. */
72
73 /* Substitutes $variables as defined by GETENV into INPUT and returns
74    a copy of the resultant string.  Supports $var and ${var} syntaxes;
75    $$ substitutes as $. */
76 char *
77 fn_interp_vars (const char *input, const char *(*getenv) (const char *))
78 {
79   struct string output;
80
81   if (NULL == strchr (input, '$'))
82     return xstrdup (input);
83
84   ds_init (&output, strlen (input));
85
86   for (;;)
87     switch (*input)
88       {
89       case '\0':
90         return ds_c_str (&output);
91         
92       case '$':
93         input++;
94
95         if (*input == '$')
96           {
97             ds_putc (&output, '$');
98             input++;
99           }
100         else
101           {
102             int stop;
103             int start;
104             const char *value;
105
106             start = ds_length (&output);
107
108             if (*input == '(')
109               {
110                 stop = ')';
111                 input++;
112               }
113             else if (*input == '{')
114               {
115                 stop = '}';
116                 input++;
117               }
118             else
119               stop = 0;
120
121             while (*input && *input != stop
122                    && (stop || isalpha ((unsigned char) *input)))
123               ds_putc (&output, *input++);
124             
125             value = getenv (ds_c_str (&output) + start);
126             ds_truncate (&output, start);
127             ds_puts (&output, value);
128
129             if (stop && *input == stop)
130               input++;
131           }
132
133       default:
134         ds_putc (&output, *input++);
135       }
136 }
137
138 #ifdef unix
139 /* Expands csh tilde notation from the path INPUT into a malloc()'d
140    returned string. */
141 char *
142 fn_tilde_expand (const char *input)
143 {
144   const char *ip;
145   struct string output;
146
147   if (NULL == strchr (input, '~'))
148     return xstrdup (input);
149   ds_init (&output, strlen (input));
150
151   ip = input;
152
153   for (ip = input; *ip; )
154     if (*ip != '~' || (ip != input && ip[-1] != PATH_DELIMITER))
155       ds_putc (&output, *ip++);
156     else
157       {
158         static const char stop_set[3] = {DIR_SEPARATOR, PATH_DELIMITER, 0};
159         const char *cp;
160         
161         ip++;
162
163         cp = ip + strcspn (ip, stop_set);
164
165         if (cp > ip)
166           {
167             struct passwd *pwd;
168             char username[9];
169
170             strncpy (username, ip, cp - ip + 1);
171             username[8] = 0;
172             pwd = getpwnam (username);
173
174             if (!pwd || !pwd->pw_dir)
175               ds_putc (&output, *ip++);
176             else
177               ds_puts (&output, pwd->pw_dir);
178           }
179         else
180           {
181             const char *home = fn_getenv ("HOME");
182             if (!home)
183               ds_putc (&output, *ip++);
184             else
185               ds_puts (&output, home);
186           }
187
188         ip = cp;
189       }
190
191   return ds_c_str (&output);
192 }
193 #else /* !unix */
194 char *
195 fn_tilde_expand (const char *input)
196 {
197   return xstrdup (input);
198 }
199 #endif /* !unix */
200
201 /* Searches for a configuration file with name NAME in the path given
202    by PATH, which is tilde- and environment-interpolated.  Directories
203    in PATH are delimited by PATH_DELIMITER, defined in <pref.h>.
204    Returns the malloc'd full name of the first file found, or NULL if
205    none is found.
206
207    If PREPEND is non-NULL, then it is prepended to each filename;
208    i.e., it looks like PREPEND/PATH_COMPONENT/NAME.  This is not done
209    with absolute directories in the path. */
210 #if defined (unix) || defined (__MSDOS__) || defined (__WIN32__)
211 char *
212 fn_search_path (const char *basename, const char *path, const char *prepend)
213 {
214   char *subst_path;
215   struct string filename;
216   const char *bp;
217
218   if (fn_absolute_p (basename))
219     return fn_tilde_expand (basename);
220   
221   {
222     char *temp = fn_interp_vars (path, fn_getenv);
223     bp = subst_path = fn_tilde_expand (temp);
224     free (temp);
225   }
226
227   msg (VM (4), _("Searching for `%s'..."), basename);
228   ds_init (&filename, 64);
229
230   for (;;)
231     {
232       const char *ep;
233       if (0 == *bp)
234         {
235           msg (VM (4), _("Search unsuccessful!"));
236           ds_destroy (&filename);
237           free (subst_path);
238           return NULL;
239         }
240
241       for (ep = bp; *ep && *ep != PATH_DELIMITER; ep++)
242         ;
243
244       /* Paste together PREPEND/PATH/BASENAME. */
245       ds_clear (&filename);
246       if (prepend && !fn_absolute_p (bp))
247         {
248           ds_puts (&filename, prepend);
249           ds_putc (&filename, DIR_SEPARATOR);
250         }
251       ds_concat (&filename, bp, ep - bp);
252       if (ep - bp
253           && ds_c_str (&filename)[ds_length (&filename) - 1] != DIR_SEPARATOR)
254         ds_putc (&filename, DIR_SEPARATOR);
255       ds_puts (&filename, basename);
256       
257       msg (VM (5), " - %s", ds_c_str (&filename));
258       if (fn_exists_p (ds_c_str (&filename)))
259         {
260           msg (VM (4), _("Found `%s'."), ds_c_str (&filename));
261           free (subst_path);
262           return ds_c_str (&filename);
263         }
264
265       if (0 == *ep)
266         {
267           msg (VM (4), _("Search unsuccessful!"));
268           free (subst_path);
269           ds_destroy (&filename);
270           return NULL;
271         }
272       bp = ep + 1;
273     }
274 }
275 #else /* not unix, msdog, lose32 */
276 char *
277 fn_search_path (const char *basename, const char *path, const char *prepend)
278 {
279   size_t size = strlen (path) + 1 + strlen (basename) + 1;
280   char *string;
281   char *cp;
282   
283   if (prepend)
284     size += strlen (prepend) + 1;
285   string = xmalloc (size);
286   
287   cp = string;
288   if (prepend)
289     {
290       cp = stpcpy (cp, prepend);
291       *cp++ = DIR_SEPARATOR;
292     }
293   cp = stpcpy (cp, path);
294   *cp++ = DIR_SEPARATOR;
295   strcpy (cp, basename);
296
297   return string;
298 }
299 #endif /* not unix, msdog, lose32 */
300
301 /* Prepends directory DIR to filename FILE and returns a malloc()'d
302    copy of it. */
303 char *
304 fn_prepend_dir (const char *file, const char *dir)
305 {
306   char *temp;
307   char *cp;
308   
309   if (fn_absolute_p (file))
310     return xstrdup (file);
311
312   temp = xmalloc (strlen (file) + 1 + strlen (dir) + 1);
313   cp = stpcpy (temp, dir);
314   if (cp != temp && cp[-1] != DIR_SEPARATOR)
315     *cp++ = DIR_SEPARATOR;
316   cp = stpcpy (cp, file);
317
318   return temp;
319 }
320
321 /* fn_normalize(): This very OS-dependent routine canonicalizes
322    filename FN1.  The filename should not need to be the name of an
323    existing file.  Returns a malloc()'d copy of the canonical name.
324    This function must always succeed; if it needs to bail out then it
325    should return xstrdup(FN1).  */
326 #ifdef unix
327 char *
328 fn_normalize (const char *filename)
329 {
330   const char *src;
331   char *fn1, *fn2, *dest;
332   int maxlen;
333
334   if (fn_special_p (filename))
335     return xstrdup (filename);
336   
337   fn1 = fn_tilde_expand (filename);
338
339   /* Follow symbolic links. */
340   for (;;)
341     {
342       fn2 = fn1;
343       fn1 = fn_readlink (fn1);
344       if (!fn1)
345         {
346           fn1 = fn2;
347           break;
348         }
349       free (fn2);
350     }
351
352   maxlen = strlen (fn1) * 2;
353   if (maxlen < 31)
354     maxlen = 31;
355   dest = fn2 = xmalloc (maxlen + 1);
356   src = fn1;
357
358   if (*src == DIR_SEPARATOR)
359     *dest++ = *src++;
360   else
361     {
362       errno = 0;
363       while (getcwd (dest, maxlen - (dest - fn2)) == NULL && errno == ERANGE)
364         {
365           maxlen *= 2;
366           dest = fn2 = xrealloc (fn2, maxlen + 1);
367           errno = 0;
368         }
369       if (errno)
370         {
371           free (fn1);
372           free (fn2);
373           return NULL;
374         }
375       dest = strchr (fn2, '\0');
376       if (dest - fn2 >= maxlen)
377         {
378           int ofs = dest - fn2;
379           maxlen *= 2;
380           fn2 = xrealloc (fn2, maxlen + 1);
381           dest = fn2 + ofs;
382         }
383       if (dest[-1] != DIR_SEPARATOR)
384         *dest++ = DIR_SEPARATOR;
385     }
386
387   for (;;)
388     {
389       int c, f;
390
391       c = *src++;
392
393       f = 0;
394       if (c == DIR_SEPARATOR || c == 0)
395         {
396           /* remove `./', `../' from directory */
397           if (dest[-1] == '.' && dest[-2] == DIR_SEPARATOR)
398             dest--;
399           else if (dest[-1] == '.' && dest[-2] == '.' && dest[-3] == DIR_SEPARATOR)
400             {
401               dest -= 3;
402               if (dest == fn2)
403                 dest++;
404               while (dest[-1] != DIR_SEPARATOR)
405                 dest--;
406             }
407           else if (dest[-1] != DIR_SEPARATOR)   /* remove extra slashes */
408             f = 1;
409
410           if (c == 0)
411             {
412               if (dest[-1] == DIR_SEPARATOR && dest > fn2 + 1)
413                 dest--;
414               *dest = 0;
415               free (fn1);
416
417               return xrealloc (fn2, strlen (fn2) + 1);
418             }
419         }
420       else
421         f = 1;
422
423       if (f)
424         {
425           if (dest - fn2 >= maxlen)
426             {
427               int ofs = dest - fn2;
428               maxlen *= 2;
429               fn2 = xrealloc (fn2, maxlen + 1);
430               dest = fn2 + ofs;
431             }
432           *dest++ = c;
433         }
434     }
435 }
436 #elif defined (__WIN32__)
437 char *
438 fn_normalize (const char *fn1)
439 {
440   DWORD len;
441   DWORD success;
442   char *fn2;
443
444   /* Don't change special filenames. */
445   if (is_special_filename (filename))
446     return xstrdup (filename);
447
448   /* First find the required buffer length. */
449   len = GetFullPathName (fn1, 0, NULL, NULL);
450   if (!len)
451     {
452       fn2 = xstrdup (fn1);
453       return fn2;
454     }
455
456   /* Then make a buffer that big. */
457   fn2 = xmalloc (len);
458   success = GetFullPathName (fn1, len, fn2, NULL);
459   if (success >= len || success == 0)
460     {
461       free (fn2);
462       fn2 = xstrdup (fn1);
463       return fn2;
464     }
465   return fn2;
466 }
467 #elif __BORLANDC__
468 char *
469 fn_normalize (const char *fn1)
470 {
471   char *fn2 = _fullpath (NULL, fn1, 0);
472   if (fn2)
473     {
474       char *cp;
475       for (cp = fn2; *cp; cp++)
476         *cp = toupper ((unsigned char) (*cp));
477       return fn2;
478     }
479   return xstrdup (fn1);
480 }
481 #elif __DJGPP__
482 char *
483 fn_normalize (const char *fn1)
484 {
485   char *fn2 = xmalloc (1024);
486   _fixpath (fn1, fn2);
487   fn2 = xrealloc (fn2, strlen (fn2) + 1);
488   return fn2;
489 }
490 #else /* not Lose32, Unix, or DJGPP */
491 char *
492 fn_normalize (const char *fn)
493 {
494   return xstrdup (fn);
495 }
496 #endif /* not Lose32, Unix, or DJGPP */
497
498 /* Returns the directory part of FILENAME, as a malloc()'d
499    string. */
500 char *
501 fn_dirname (const char *filename)
502 {
503   const char *p;
504   char *s;
505   size_t len;
506
507   len = strlen (filename);
508   if (len == 1 && filename[0] == '/')
509     p = filename + 1;
510   else if (len && filename[len - 1] == DIR_SEPARATOR)
511     p = buf_find_reverse (filename, len - 1, filename + len - 1, 1);
512   else
513     p = strrchr (filename, DIR_SEPARATOR);
514   if (p == NULL)
515     p = filename;
516
517   s = xmalloc (p - filename + 1);
518   memcpy (s, filename, p - filename);
519   s[p - filename] = 0;
520
521   return s;
522 }
523
524 /* Returns the basename part of FILENAME as a malloc()'d string. */
525 #if 0
526 char *
527 fn_basename (const char *filename)
528 {
529   /* Not used, not implemented. */
530   abort ();
531 }
532 #endif
533
534 /* Returns the extension part of FILENAME as a malloc()'d string.
535    If FILENAME does not have an extension, returns an empty
536    string. */
537 char *
538 fn_extension (const char *filename) 
539 {
540   const char *extension = strrchr (filename, '.');
541   if (extension == NULL)
542     extension = "";
543   return xstrdup (extension);
544 }
545 \f
546 #if unix
547 /* Returns the current working directory, as a malloc()'d string.
548    From libc.info. */
549 char *
550 fn_get_cwd (void)
551 {
552   int size = 100;
553   char *buffer = xmalloc (size);
554      
555   for (;;)
556     {
557       char *value = getcwd (buffer, size);
558       if (value != 0)
559         return buffer;
560
561       size *= 2;
562       free (buffer);
563       buffer = xmalloc (size);
564     }
565 }
566 #else
567 char *
568 fn_get_cwd (void)
569 {
570   int size = 2;
571   char *buffer = xmalloc (size);
572   if ( buffer) 
573   {
574     buffer[0]='.';
575     buffer[1]='\0';
576   }
577
578   return buffer;
579      
580 }
581 #endif
582 \f
583 /* Find out information about files. */
584
585 /* Returns nonzero iff NAME specifies an absolute filename. */
586 int
587 fn_absolute_p (const char *name)
588 {
589 #ifdef unix
590   if (name[0] == '/'
591       || !strncmp (name, "./", 2)
592       || !strncmp (name, "../", 3)
593       || name[0] == '~')
594     return 1;
595 #elif defined (__MSDOS__)
596   if (name[0] == '\\'
597       || !strncmp (name, ".\\", 2)
598       || !strncmp (name, "..\\", 3)
599       || (name[0] && name[1] == ':'))
600     return 1;
601 #endif
602   
603   return 0;
604 }
605   
606 /* Returns 1 if the filename specified is a virtual file that doesn't
607    really exist on disk, 0 if it's a real filename. */
608 int
609 fn_special_p (const char *filename)
610 {
611   if (!strcmp (filename, "-") || !strcmp (filename, "stdin")
612       || !strcmp (filename, "stdout") || !strcmp (filename, "stderr")
613 #ifdef unix
614       || filename[0] == '|'
615       || (*filename && filename[strlen (filename) - 1] == '|')
616 #endif
617       )
618     return 1;
619
620   return 0;
621 }
622
623 /* Returns nonzero if file with name NAME exists. */
624 int
625 fn_exists_p (const char *name)
626 {
627 #ifdef unix
628   struct stat temp;
629
630   return stat (name, &temp) == 0;
631 #else
632   FILE *f = fopen (name, "r");
633   if (!f)
634     return 0;
635   fclose (f);
636   return 1;
637 #endif
638 }
639
640 /* Returns the symbolic link value for FILENAME as a dynamically
641    allocated buffer, or a null pointer on failure. */
642 char *
643 fn_readlink (const char *filename)
644 {
645   return xreadlink (filename, 32);
646 }
647 \f
648 /* Environment variables. */
649
650 /* Simulates $VER and $ARCH environment variables. */
651 const char *
652 fn_getenv (const char *s)
653 {
654   if (!strcmp (s, "VER"))
655     return fn_getenv_default ("STAT_VER", bare_version);
656   else if (!strcmp (s, "ARCH"))
657     return fn_getenv_default ("STAT_ARCH", host_system);
658   else
659     return getenv (s);
660 }
661
662 /* Returns getenv(KEY) if that's non-NULL; else returns DEF. */
663 const char *
664 fn_getenv_default (const char *key, const char *def)
665 {
666   const char *value = getenv (key);
667   return value ? value : def;
668 }
669 \f
670 /* Basic file handling. */
671
672 /* Used for giving an error message on a set_safer security
673    violation. */
674 static FILE *
675 safety_violation (const char *fn)
676 {
677   msg (SE, _("Not opening pipe file `%s' because SAFER option set."), fn);
678   errno = EPERM;
679   return NULL;
680 }
681
682 /* As a general comment on the following routines, a `sensible value'
683    for errno includes 0 if there is no associated system error.  The
684    routines will only set errno to 0 if there is an error in a
685    callback that sets errno to 0; they themselves won't. */
686
687 /* File open routine that understands `-' as stdin/stdout and `|cmd'
688    as a pipe to command `cmd'.  Returns resultant FILE on success,
689    NULL on failure.  If NULL is returned then errno is set to a
690    sensible value.  */
691 FILE *
692 fn_open (const char *fn, const char *mode)
693 {
694   assert (mode[0] == 'r' || mode[0] == 'w');
695
696   if (mode[0] == 'r' && (!strcmp (fn, "stdin") || !strcmp (fn, "-"))) 
697     return stdin;
698   else if (mode[0] == 'w' && (!strcmp (fn, "stdout") || !strcmp (fn, "-")))
699     return stdout;
700   else if (mode[0] == 'w' && !strcmp (fn, "stderr"))
701     return stderr;
702   
703 #ifdef unix
704   if (fn[0] == '|')
705     {
706       if (get_safer_mode ())
707         return safety_violation (fn);
708
709       return popen (&fn[1], mode);
710     }
711   else if (*fn && fn[strlen (fn) - 1] == '|')
712     {
713       char *s;
714       FILE *f;
715
716       if (get_safer_mode ())
717         return safety_violation (fn);
718       
719       s = local_alloc (strlen (fn));
720       memcpy (s, fn, strlen (fn) - 1);
721       s[strlen (fn) - 1] = 0;
722       
723       f = popen (s, mode);
724
725       local_free (s);
726
727       return f;
728     }
729   else
730 #endif
731     {
732       FILE *f = fopen (fn, mode);
733
734       if (f && mode[0] == 'w')
735         setvbuf (f, NULL, _IOLBF, 0);
736
737       return f;
738     }
739 }
740
741 /* Counterpart to fn_open that closes file F with name FN; returns 0
742    on success, EOF on failure.  If EOF is returned, errno is set to a
743    sensible value. */
744 int
745 fn_close (const char *fn, FILE *f)
746 {
747   if (!strcmp (fn, "-"))
748     return 0;
749 #ifdef unix
750   else if (fn[0] == '|' || (*fn && fn[strlen (fn) - 1] == '|'))
751     {
752       pclose (f);
753       return 0;
754     }
755 #endif
756   else
757     return fclose (f);
758 }
759 \f
760 /* More extensive file handling. */
761
762 /* File open routine that extends fn_open().  Opens or reopens a
763    file according to the contents of file_ext F.  Returns nonzero on
764    success.  If 0 is returned, errno is set to a sensible value. */
765 int
766 fn_open_ext (struct file_ext *f)
767 {
768   char *p;
769
770   p = strstr (f->filename, "%d");
771   if (p)
772     {
773       char *s = local_alloc (strlen (f->filename) + INT_DIGITS - 1);
774       char *cp;
775
776       memcpy (s, f->filename, p - f->filename);
777       cp = spprintf (&s[p - f->filename], "%d", *f->sequence_no);
778       strcpy (cp, &p[2]);
779
780       if (f->file)
781         {
782           int error = 0;
783
784           if (f->preclose)
785             if (f->preclose (f) == 0)
786               error = errno;
787
788           if (EOF == fn_close (f->filename, f->file) || error)
789             {
790               f->file = NULL;
791               local_free (s);
792
793               if (error)
794                 errno = error;
795
796               return 0;
797             }
798
799           f->file = NULL;
800         }
801
802       f->file = fn_open (s, f->mode);
803       local_free (s);
804
805       if (f->file && f->postopen)
806         if (f->postopen (f) == 0)
807           {
808             int error = errno;
809             fn_close (f->filename, f->file);
810             errno = error;
811
812             return 0;
813           }
814
815       return (f->file != NULL);
816     }
817   else if (f->file)
818     return 1;
819   else
820     {
821       f->file = fn_open (f->filename, f->mode);
822
823       if (f->file && f->postopen)
824         if (f->postopen (f) == 0)
825           {
826             int error = errno;
827             fn_close (f->filename, f->file);
828             errno = error;
829
830             return 0;
831           }
832
833       return (f->file != NULL);
834     }
835 }
836
837 /* Properly closes the file associated with file_ext F, if any.
838    Return nonzero on success.  If zero is returned, errno is set to a
839    sensible value. */
840 int
841 fn_close_ext (struct file_ext *f)
842 {
843   if (f->file)
844     {
845       int error = 0;
846
847       if (f->preclose)
848         if (f->preclose (f) == 0)
849           error = errno;
850
851       if (EOF == fn_close (f->filename, f->file) || error)
852         {
853           f->file = NULL;
854
855           if (error)
856             errno = error;
857
858           return 0;
859         }
860
861       f->file = NULL;
862     }
863   return 1;
864 }
865
866 #ifdef unix
867 /* A file's identity. */
868 struct file_identity 
869   {
870     dev_t device;               /* Device number. */
871     ino_t inode;                /* Inode number. */
872   };
873
874 /* Returns a pointer to a dynamically allocated structure whose
875    value can be used to tell whether two files are actually the
876    same file.  Returns a null pointer if no information about the
877    file is available, perhaps because it does not exist.  The
878    caller is responsible for freeing the structure with
879    fn_free_identity() when finished. */  
880 struct file_identity *
881 fn_get_identity (const char *filename) 
882 {
883   struct stat s;
884
885   if (stat (filename, &s) == 0) 
886     {
887       struct file_identity *identity = xmalloc (sizeof *identity);
888       identity->device = s.st_dev;
889       identity->inode = s.st_ino;
890       return identity;
891     }
892   else
893     return NULL;
894 }
895
896 /* Frees IDENTITY obtained from fn_get_identity(). */
897 void
898 fn_free_identity (struct file_identity *identity) 
899 {
900   free (identity);
901 }
902
903 /* Compares A and B, returning a strcmp()-type result. */
904 int
905 fn_compare_file_identities (const struct file_identity *a,
906                             const struct file_identity *b) 
907 {
908   assert (a != NULL);
909   assert (b != NULL);
910   if (a->device != b->device)
911     return a->device < b->device ? -1 : 1;
912   else
913     return a->inode < b->inode ? -1 : a->inode > b->inode;
914 }
915 #else /* not unix */
916 /* A file's identity. */
917 struct file_identity 
918   {
919     char *normalized_filename;  /* File's normalized name. */
920   };
921
922 /* Returns a pointer to a dynamically allocated structure whose
923    value can be used to tell whether two files are actually the
924    same file.  Returns a null pointer if no information about the
925    file is available, perhaps because it does not exist.  The
926    caller is responsible for freeing the structure with
927    fn_free_identity() when finished. */  
928 struct file_identity *
929 fn_get_identity (const char *filename) 
930 {
931   struct file_identity *identity = xmalloc (sizeof *identity);
932   identity->normalized_filename = fn_normalize (filename);
933   return identity;
934 }
935
936 /* Frees IDENTITY obtained from fn_get_identity(). */
937 void
938 fn_free_identity (struct file_identity *identity) 
939 {
940   if (identity != NULL) 
941     {
942       free (identity->normalized_filename);
943       free (identity);
944     }
945 }
946
947 /* Compares A and B, returning a strcmp()-type result. */
948 int
949 fn_compare_file_identities (const struct file_identity *a,
950                             const struct file_identity *b) 
951 {
952   return strcmp (a->normalized_filename, b->normalized_filename);
953 }
954 #endif /* not unix */