(translate_040_to_space) [MOUNTED_GETMNTENT1]: New function.
[gnulib.git] / lib / mountlist.c
1 /* mountlist.c -- return a list of mounted filesystems
2    Copyright (C) 1991, 1992, 1997, 1998, 1999 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 2, or (at your option)
7    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, write to the Free Software Foundation,
16    Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
17
18 #if HAVE_CONFIG_H
19 # include <config.h>
20 #endif
21
22 #include <stdio.h>
23 #include <sys/types.h>
24 #include "mountlist.h"
25
26 #ifdef STDC_HEADERS
27 # include <stdlib.h>
28 #else
29 void free ();
30 #endif
31 #if defined(STDC_HEADERS) || defined(HAVE_STRING_H)
32 # include <string.h>
33 #else
34 # include <strings.h>
35 #endif
36
37 #ifndef strstr
38 char *strstr ();
39 #endif
40 char *xmalloc ();
41 char *xrealloc ();
42 char *xstrdup ();
43 void error ();
44
45 #include <errno.h>
46 #ifndef errno
47 extern int errno;
48 #endif
49
50 #ifdef HAVE_FCNTL_H
51 # include <fcntl.h>
52 #endif
53
54 #ifdef HAVE_UNISTD_H
55 # include <unistd.h>
56 #endif
57
58 #if HAVE_SYS_PARAM_H
59 # include <sys/param.h>
60 #endif
61
62 #if defined (MOUNTED_GETFSSTAT) /* __alpha running OSF_1 */
63 # include <sys/mount.h>
64 # include <sys/fs_types.h>
65 #endif /* MOUNTED_GETFSSTAT */
66
67 #ifdef MOUNTED_GETMNTENT1       /* 4.3BSD, SunOS, HP-UX, Dynix, Irix.  */
68 # include <mntent.h>
69 # if !defined(MOUNTED)
70 #  if defined(MNT_MNTTAB)       /* HP-UX.  */
71 #   define MOUNTED MNT_MNTTAB
72 #  endif
73 #  if defined(MNTTABNAME)       /* Dynix.  */
74 #   define MOUNTED MNTTABNAME
75 #  endif
76 # endif
77 #endif
78
79 #ifdef MOUNTED_GETMNTINFO       /* 4.4BSD.  */
80 # include <sys/mount.h>
81 #endif
82
83 #ifdef MOUNTED_GETMNT           /* Ultrix.  */
84 # include <sys/mount.h>
85 # include <sys/fs_types.h>
86 #endif
87
88 #ifdef MOUNTED_FREAD            /* SVR2.  */
89 # include <mnttab.h>
90 #endif
91
92 #ifdef MOUNTED_FREAD_FSTYP      /* SVR3.  */
93 # include <mnttab.h>
94 # include <sys/fstyp.h>
95 # include <sys/statfs.h>
96 #endif
97
98 #ifdef MOUNTED_LISTMNTENT
99 # include <mntent.h>
100 #endif
101
102 #ifdef MOUNTED_GETMNTENT2       /* SVR4.  */
103 # include <sys/mnttab.h>
104 #endif
105
106 #ifdef MOUNTED_VMOUNT           /* AIX.  */
107 # include <fshelp.h>
108 # include <sys/vfs.h>
109 #endif
110
111 #ifdef DOLPHIN
112 /* So special that it's not worth putting this in autoconf.  */
113 # undef MOUNTED_FREAD_FSTYP
114 # define MOUNTED_GETMNTTBL
115 #endif
116
117 #if HAVE_SYS_MNTENT_H
118 /* This is to get MNTOPT_IGNORE on e.g. SVR4.  */
119 # include <sys/mntent.h>
120 #endif
121
122 #if defined (MNTOPT_IGNORE) && defined (HAVE_HASMNTOPT)
123 # define MNT_IGNORE(M) hasmntopt ((M), MNTOPT_IGNORE)
124 #else
125 # define MNT_IGNORE(M) 0
126 #endif
127
128 #ifdef MOUNTED_GETMNTENT1       /* 4.3BSD, SunOS, HP-UX, Dynix, Irix.  */
129 /* Return the value of the hexadecimal number represented by CP.
130    No prefix (like '0x') or suffix (like 'h') is expected to be
131    part of CP. */
132 /* FIXME: this can overflow */
133
134 static int
135 xatoi (char *cp)
136 {
137   int val;
138
139   val = 0;
140   while (*cp)
141     {
142       if (*cp >= 'a' && *cp <= 'f')
143         val = val * 16 + *cp - 'a' + 10;
144       else if (*cp >= 'A' && *cp <= 'F')
145         val = val * 16 + *cp - 'A' + 10;
146       else if (*cp >= '0' && *cp <= '9')
147         val = val * 16 + *cp - '0';
148       else
149         break;
150       cp++;
151     }
152   return val;
153 }
154
155 /* Convert, in place, each unambiguous `\040' sequence in the NUL-terminated
156    string, STR, to a single space.  `unambiguous' means that it must not be
157    immediately preceded by an odd number of backslash characters.  */
158
159 static void
160 translate_040_to_space (char *str)
161 {
162   while (1)
163     {
164       char *p;
165       char *backslash = strstr (str, "\\040");
166       unsigned int backslash_count = 0;
167
168       if (backslash == NULL)
169         break;
170
171       /* Count preceding backslashes, going no further than str.  */
172       for (p = backslash - 1; p >= str && *p == '\\'; p--)
173         ++backslash_count;
174
175       if (backslash_count % 2 == 1)
176         {
177           /* The backslash is escaped;  advance past the 040 and
178              continue searching.  */
179           str = backslash + 4;
180           continue;
181         }
182
183       /* We found an unambiguous `\040'.  Replace it with a space
184          and move everything following it back by 3 bytes.
185          The source and destination regions may overlap, so we have
186          to use memmove.  */
187       *backslash = ' ';
188       str = backslash + 1;
189       /* Be sure to copy the trailing NUL byte, too.  */
190       memmove (str, backslash + 4, strlen (backslash + 4) + 1);
191     }
192 }
193
194 #endif /* MOUNTED_GETMNTENT1.  */
195
196 #if MOUNTED_GETMNTINFO
197
198 # if ! HAVE_F_FSTYPENAME_IN_STATFS
199 static char *
200 fstype_to_string (short t)
201 {
202   switch (t)
203     {
204 #  ifdef MOUNT_PC
205     case MOUNT_PC:
206       return "pc";
207 #  endif
208 #  ifdef MOUNT_MFS
209     case MOUNT_MFS:
210       return "mfs";
211 #  endif
212 #  ifdef MOUNT_LO
213     case MOUNT_LO:
214       return "lo";
215 #  endif
216 #  ifdef MOUNT_TFS
217     case MOUNT_TFS:
218       return "tfs";
219 #  endif
220 #  ifdef MOUNT_TMP
221     case MOUNT_TMP:
222       return "tmp";
223 #  endif
224 #  ifdef MOUNT_UFS
225    case MOUNT_UFS:
226      return "ufs" ;
227 #  endif
228 #  ifdef MOUNT_NFS
229    case MOUNT_NFS:
230      return "nfs" ;
231 #  endif
232 #  ifdef MOUNT_MSDOS
233    case MOUNT_MSDOS:
234      return "msdos" ;
235 #  endif
236 #  ifdef MOUNT_LFS
237    case MOUNT_LFS:
238      return "lfs" ;
239 #  endif
240 #  ifdef MOUNT_LOFS
241    case MOUNT_LOFS:
242      return "lofs" ;
243 #  endif
244 #  ifdef MOUNT_FDESC
245    case MOUNT_FDESC:
246      return "fdesc" ;
247 #  endif
248 #  ifdef MOUNT_PORTAL
249    case MOUNT_PORTAL:
250      return "portal" ;
251 #  endif
252 #  ifdef MOUNT_NULL
253    case MOUNT_NULL:
254      return "null" ;
255 #  endif
256 #  ifdef MOUNT_UMAP
257    case MOUNT_UMAP:
258      return "umap" ;
259 #  endif
260 #  ifdef MOUNT_KERNFS
261    case MOUNT_KERNFS:
262      return "kernfs" ;
263 #  endif
264 #  ifdef MOUNT_PROCFS
265    case MOUNT_PROCFS:
266      return "procfs" ;
267 #  endif
268 #  ifdef MOUNT_AFS
269    case MOUNT_AFS:
270      return "afs" ;
271 #  endif
272 #  ifdef MOUNT_CD9660
273    case MOUNT_CD9660:
274      return "cd9660" ;
275 #  endif
276 #  ifdef MOUNT_UNION
277    case MOUNT_UNION:
278      return "union" ;
279 #  endif
280 #  ifdef MOUNT_DEVFS
281    case MOUNT_DEVFS:
282      return "devfs" ;
283 #  endif
284 #  ifdef MOUNT_EXT2FS
285    case MOUNT_EXT2FS:
286      return "ext2fs" ;
287 #  endif
288     default:
289       return "?";
290     }
291 }
292 # endif /* ! HAVE_F_FSTYPENAME_IN_STATFS */
293
294 /* __NetBSD__ || BSD_NET2 || __OpenBSD__ */
295 static char *
296 fsp_to_string (const struct statfs *fsp)
297 {
298 # if defined HAVE_F_FSTYPENAME_IN_STATFS
299   return fsp->f_fstypename;
300 # else
301   return fstype_to_string (fsp->f_type);
302 # endif
303 }
304
305 #endif /* MOUNTED_GETMNTINFO */
306
307 #ifdef MOUNTED_VMOUNT           /* AIX.  */
308 static char *
309 fstype_to_string (int t)
310 {
311   struct vfs_ent *e;
312
313   e = getvfsbytype (t);
314   if (!e || !e->vfsent_name)
315     return "none";
316   else
317     return e->vfsent_name;
318 }
319 #endif /* MOUNTED_VMOUNT */
320
321 /* Return a list of the currently mounted filesystems, or NULL on error.
322    Add each entry to the tail of the list so that they stay in order.
323    If NEED_FS_TYPE is nonzero, ensure that the filesystem type fields in
324    the returned list are valid.  Otherwise, they might not be.  */
325
326 struct mount_entry *
327 read_filesystem_list (int need_fs_type)
328 {
329   struct mount_entry *mount_list;
330   struct mount_entry *me;
331   struct mount_entry **mtail = &mount_list;
332
333 #ifdef MOUNTED_LISTMNTENT
334   {
335     struct tabmntent *mntlist, *p;
336     struct mntent *mnt;
337     struct mount_entry *me;
338
339     /* the third and fourth arguments could be used to filter mounts,
340        but Crays doesn't seem to have any mounts that we want to
341        remove. Specifically, automount create normal NFS mounts.
342        */
343
344     if(listmntent(&mntlist, KMTAB, NULL, NULL) < 0)
345       return NULL;
346     for (p = mntlist; p; p = p->next) {
347       mnt = p->ment;
348       me = (struct mount_entry*) xmalloc(sizeof (struct mount_entry));
349       me->me_devname = xstrdup(mnt->mnt_fsname);
350       me->me_mountdir = xstrdup(mnt->mnt_dir);
351       me->me_type = xstrdup(mnt->mnt_type);
352       me->me_dummy = ME_DUMMY (me->me_devname, me->me_type);
353       me->me_remote = ME_REMOTE (me->me_devname, me->me_type);
354       me->me_dev = -1;
355       *mtail = me;
356       mtail = &me->me_next;
357     }
358     freemntlist(mntlist);
359   }
360 #endif
361
362 #ifdef MOUNTED_GETMNTENT1       /* 4.3BSD, SunOS, HP-UX, Dynix, Irix.  */
363   {
364     struct mntent *mnt;
365     char *table = MOUNTED;
366     FILE *fp;
367     char *devopt;
368
369     fp = setmntent (table, "r");
370     if (fp == NULL)
371       return NULL;
372
373     while ((mnt = getmntent (fp)))
374       {
375         me = (struct mount_entry *) xmalloc (sizeof (struct mount_entry));
376         me->me_devname = xstrdup (mnt->mnt_fsname);
377         me->me_mountdir = xstrdup (mnt->mnt_dir);
378         me->me_type = xstrdup (mnt->mnt_type);
379         me->me_dummy = ME_DUMMY (me->me_devname, me->me_type);
380         me->me_remote = ME_REMOTE (me->me_devname, me->me_type);
381         devopt = strstr (mnt->mnt_opts, "dev=");
382         if (devopt)
383           {
384             if (devopt[4] == '0' && (devopt[5] == 'x' || devopt[5] == 'X'))
385               me->me_dev = xatoi (devopt + 6);
386             else
387               me->me_dev = xatoi (devopt + 4);
388           }
389         else
390           me->me_dev = (dev_t) -1;      /* Magic; means not known yet. */
391
392         /* FIXME: do the conversion only if we're using some version of
393            GNU libc -- which one?  */
394         /* Convert each `\040' string to a space.  */
395         translate_040_to_space (me->me_mountdir);
396
397         /* Add to the linked list. */
398         *mtail = me;
399         mtail = &me->me_next;
400       }
401
402     if (endmntent (fp) == 0)
403       goto free_then_fail;
404   }
405 #endif /* MOUNTED_GETMNTENT1. */
406
407 #ifdef MOUNTED_GETMNTINFO       /* 4.4BSD.  */
408   {
409     struct statfs *fsp;
410     int entries;
411
412     entries = getmntinfo (&fsp, MNT_NOWAIT);
413     if (entries < 0)
414       return NULL;
415     for (; entries-- > 0; fsp++)
416       {
417         char *fs_type = fsp_to_string (fsp);
418
419         me = (struct mount_entry *) xmalloc (sizeof (struct mount_entry));
420         me->me_devname = xstrdup (fsp->f_mntfromname);
421         me->me_mountdir = xstrdup (fsp->f_mntonname);
422         me->me_type = fs_type;
423         me->me_dummy = ME_DUMMY (me->me_devname, me->me_type);
424         me->me_remote = ME_REMOTE (me->me_devname, me->me_type);
425         me->me_dev = (dev_t) -1;        /* Magic; means not known yet. */
426
427         /* Add to the linked list. */
428         *mtail = me;
429         mtail = &me->me_next;
430       }
431   }
432 #endif /* MOUNTED_GETMNTINFO */
433
434 #ifdef MOUNTED_GETMNT           /* Ultrix.  */
435   {
436     int offset = 0;
437     int val;
438     struct fs_data fsd;
439
440     while (errno = 0,
441            0 < (val = getmnt (&offset, &fsd, sizeof (fsd), NOSTAT_MANY,
442                               (char *) 0)))
443       {
444         me = (struct mount_entry *) xmalloc (sizeof (struct mount_entry));
445         me->me_devname = xstrdup (fsd.fd_req.devname);
446         me->me_mountdir = xstrdup (fsd.fd_req.path);
447         me->me_type = gt_names[fsd.fd_req.fstype];
448         me->me_dummy = ME_DUMMY (me->me_devname, me->me_type);
449         me->me_remote = ME_REMOTE (me->me_devname, me->me_type);
450         me->me_dev = fsd.fd_req.dev;
451
452         /* Add to the linked list. */
453         *mtail = me;
454         mtail = &me->me_next;
455       }
456     if (val < 0)
457       goto free_then_fail;
458   }
459 #endif /* MOUNTED_GETMNT. */
460
461 #if defined (MOUNTED_GETFSSTAT) /* __alpha running OSF_1 */
462   {
463     int numsys, counter, bufsize;
464     struct statfs *stats;
465
466     numsys = getfsstat ((struct statfs *)0, 0L, MNT_WAIT);
467     if (numsys < 0)
468       return (NULL);
469
470     bufsize = (1 + numsys) * sizeof (struct statfs);
471     stats = (struct statfs *)xmalloc (bufsize);
472     numsys = getfsstat (stats, bufsize, MNT_WAIT);
473
474     if (numsys < 0)
475       {
476         free (stats);
477         return (NULL);
478       }
479
480     for (counter = 0; counter < numsys; counter++)
481       {
482         me = (struct mount_entry *) xmalloc (sizeof (struct mount_entry));
483         me->me_devname = xstrdup (stats[counter].f_mntfromname);
484         me->me_mountdir = xstrdup (stats[counter].f_mntonname);
485         me->me_type = mnt_names[stats[counter].f_type];
486         me->me_dummy = ME_DUMMY (me->me_devname, me->me_type);
487         me->me_remote = ME_REMOTE (me->me_devname, me->me_type);
488         me->me_dev = (dev_t) -1;        /* Magic; means not known yet. */
489
490         /* Add to the linked list. */
491         *mtail = me;
492         mtail = &me->me_next;
493       }
494
495     free (stats);
496   }
497 #endif /* MOUNTED_GETFSSTAT */
498
499 #if defined (MOUNTED_FREAD) || defined (MOUNTED_FREAD_FSTYP) /* SVR[23].  */
500   {
501     struct mnttab mnt;
502     char *table = "/etc/mnttab";
503     FILE *fp;
504
505     fp = fopen (table, "r");
506     if (fp == NULL)
507       return NULL;
508
509     while (fread (&mnt, sizeof mnt, 1, fp) > 0)
510       {
511         me = (struct mount_entry *) xmalloc (sizeof (struct mount_entry));
512 # ifdef GETFSTYP                        /* SVR3.  */
513         me->me_devname = xstrdup (mnt.mt_dev);
514 # else
515         me->me_devname = xmalloc (strlen (mnt.mt_dev) + 6);
516         strcpy (me->me_devname, "/dev/");
517         strcpy (me->me_devname + 5, mnt.mt_dev);
518 # endif
519         me->me_mountdir = xstrdup (mnt.mt_filsys);
520         me->me_dev = (dev_t) -1;        /* Magic; means not known yet. */
521         me->me_type = "";
522 # ifdef GETFSTYP                        /* SVR3.  */
523         if (need_fs_type)
524           {
525             struct statfs fsd;
526             char typebuf[FSTYPSZ];
527
528             if (statfs (me->me_mountdir, &fsd, sizeof fsd, 0) != -1
529                 && sysfs (GETFSTYP, fsd.f_fstyp, typebuf) != -1)
530               me->me_type = xstrdup (typebuf);
531           }
532 # endif
533         me->me_dummy = ME_DUMMY (me->me_devname, me->me_type);
534         me->me_remote = ME_REMOTE (me->me_devname, me->me_type);
535
536         /* Add to the linked list. */
537         *mtail = me;
538         mtail = &me->me_next;
539       }
540
541     if (ferror (fp))
542       {
543         int saved_errno = errno;
544         fclose (fp);
545         errno = saved_errno;
546         goto free_then_fail;
547       }
548
549     if (fclose (fp) == EOF)
550       goto free_then_fail;
551   }
552 #endif /* MOUNTED_FREAD || MOUNTED_FREAD_FSTYP.  */
553
554 #ifdef MOUNTED_GETMNTTBL        /* DolphinOS goes it's own way */
555   {
556     struct mntent **mnttbl=getmnttbl(),**ent;
557     for (ent=mnttbl;*ent;ent++)
558       {
559         me = (struct mount_entry *) xmalloc (sizeof (struct mount_entry));
560         me->me_devname = xstrdup ( (*ent)->mt_resource);
561         me->me_mountdir = xstrdup( (*ent)->mt_directory);
562         me->me_type =  xstrdup ((*ent)->mt_fstype);
563         me->me_dummy = ME_DUMMY (me->me_devname, me->me_type);
564         me->me_remote = ME_REMOTE (me->me_devname, me->me_type);
565         me->me_dev = (dev_t) -1;        /* Magic; means not known yet. */
566
567         /* Add to the linked list. */
568         *mtail = me;
569         mtail = &me->me_next;
570       }
571     endmnttbl();
572   }
573 #endif
574
575 #ifdef MOUNTED_GETMNTENT2       /* SVR4.  */
576   {
577     struct mnttab mnt;
578     char *table = MNTTAB;
579     FILE *fp;
580     int ret;
581     int lockfd = -1;
582
583 # if defined F_RDLCK && defined F_SETLKW
584     /* MNTTAB_LOCK is a macro name of our own invention; it's not present in
585        e.g. Solaris 2.6.  If the SVR4 folks ever define a macro
586        for this file name, we should use their macro name instead.
587        (Why not just lock MNTTAB directly?  We don't know.)  */
588 #  ifndef MNTTAB_LOCK
589 #   define MNTTAB_LOCK "/etc/.mnttab.lock"
590 #  endif
591     lockfd = open (MNTTAB_LOCK, O_RDONLY);
592     if (0 <= lockfd)
593       {
594         struct flock flock;
595         flock.l_type = F_RDLCK;
596         flock.l_whence = SEEK_SET;
597         flock.l_start = 0;
598         flock.l_len = 0;
599         while (fcntl (lockfd, F_SETLKW, &flock) == -1)
600           if (errno != EINTR)
601             {
602               int saved_errno = errno;
603               close (lockfd);
604               errno = saved_errno;
605               return NULL;
606             }
607       }
608     else if (errno != ENOENT)
609       return NULL;
610 # endif
611
612     errno = 0;
613     fp = fopen (table, "r");
614     if (fp == NULL)
615       ret = errno;
616     else
617       {
618         while ((ret = getmntent (fp, &mnt)) == 0)
619           {
620             me = (struct mount_entry *) xmalloc (sizeof (struct mount_entry));
621             me->me_devname = xstrdup (mnt.mnt_special);
622             me->me_mountdir = xstrdup (mnt.mnt_mountp);
623             me->me_type = xstrdup (mnt.mnt_fstype);
624             me->me_dummy = MNT_IGNORE (&mnt) != 0;
625             me->me_remote = ME_REMOTE (me->me_devname, me->me_type);
626             me->me_dev = (dev_t) -1;    /* Magic; means not known yet. */
627
628             /* Add to the linked list. */
629             *mtail = me;
630             mtail = &me->me_next;
631           }
632
633         ret = fclose (fp) == EOF ? errno : 0 < ret ? 0 : -1;
634       }
635
636     if (0 <= lockfd && close (lockfd) != 0)
637       ret = errno;
638
639     if (0 <= ret)
640       {
641         errno = ret;
642         goto free_then_fail;
643       }
644   }
645 #endif /* MOUNTED_GETMNTENT2.  */
646
647 #ifdef MOUNTED_VMOUNT           /* AIX.  */
648   {
649     int bufsize;
650     char *entries, *thisent;
651     struct vmount *vmp;
652
653     /* Ask how many bytes to allocate for the mounted filesystem info.  */
654     mntctl (MCTL_QUERY, sizeof bufsize, (struct vmount *) &bufsize);
655     entries = xmalloc (bufsize);
656
657     /* Get the list of mounted filesystems.  */
658     mntctl (MCTL_QUERY, bufsize, (struct vmount *) entries);
659
660     for (thisent = entries; thisent < entries + bufsize;
661          thisent += vmp->vmt_length)
662       {
663         vmp = (struct vmount *) thisent;
664         me = (struct mount_entry *) xmalloc (sizeof (struct mount_entry));
665         if (vmp->vmt_flags & MNT_REMOTE)
666           {
667             char *host, *path;
668
669             me->me_remote = 1;
670             /* Prepend the remote pathname.  */
671             host = thisent + vmp->vmt_data[VMT_HOSTNAME].vmt_off;
672             path = thisent + vmp->vmt_data[VMT_OBJECT].vmt_off;
673             me->me_devname = xmalloc (strlen (host) + strlen (path) + 2);
674             strcpy (me->me_devname, host);
675             strcat (me->me_devname, ":");
676             strcat (me->me_devname, path);
677           }
678         else
679           {
680             me->me_remote = 0;
681             me->me_devname = xstrdup (thisent +
682                                       vmp->vmt_data[VMT_OBJECT].vmt_off);
683           }
684         me->me_mountdir = xstrdup (thisent + vmp->vmt_data[VMT_STUB].vmt_off);
685         me->me_type = xstrdup (fstype_to_string (vmp->vmt_gfstype));
686         me->me_dummy = ME_DUMMY (me->me_devname, me->me_type);
687         me->me_dev = (dev_t) -1; /* vmt_fsid might be the info we want.  */
688
689         /* Add to the linked list. */
690         *mtail = me;
691         mtail = &me->me_next;
692       }
693     free (entries);
694   }
695 #endif /* MOUNTED_VMOUNT. */
696
697   *mtail = NULL;
698   return mount_list;
699
700
701  free_then_fail:
702   {
703     int saved_errno = errno;
704     *mtail = NULL;
705
706     while (mount_list)
707       {
708         me = mount_list->me_next;
709         free (mount_list->me_devname);
710         free (mount_list->me_mountdir);
711         /* FIXME: me_type is not always malloced.  */
712         free (mount_list);
713         mount_list = me;
714       }
715
716     errno = saved_errno;
717     return NULL;
718   }
719 }
720
721 #ifdef TEST
722 int
723 main (int argc, char **argv)
724 {
725   int i;
726   for (i = 1; i < argc; i++)
727     {
728       char *p = xstrdup (argv[i]);
729       translate_040_to_space (p);
730       printf ("%s: %s\n", argv[i], p);
731     }
732   exit (0);
733 }
734 #endif