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