xref: /illumos-gate/usr/src/lib/libtecla/common/expand.c (revision 8b80e8cb6855118d46f605e91b5ed4ce83417395)
1 /*
2  * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd.
3  *
4  * All rights reserved.
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a
7  * copy of this software and associated documentation files (the
8  * "Software"), to deal in the Software without restriction, including
9  * without limitation the rights to use, copy, modify, merge, publish,
10  * distribute, and/or sell copies of the Software, and to permit persons
11  * to whom the Software is furnished to do so, provided that the above
12  * copyright notice(s) and this permission notice appear in all copies of
13  * the Software and that both the above copyright notice(s) and this
14  * permission notice appear in supporting documentation.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
19  * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
20  * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL
21  * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING
22  * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
23  * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
24  * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
25  *
26  * Except as contained in this notice, the name of a copyright holder
27  * shall not be used in advertising or otherwise to promote the sale, use
28  * or other dealings in this Software without prior written authorization
29  * of the copyright holder.
30  */
31 
32 #pragma ident	"%Z%%M%	%I%	%E% SMI"
33 
34 /*
35  * If file-system access is to be excluded, this module has no function,
36  * so all of its code should be excluded.
37  */
38 #ifndef WITHOUT_FILE_SYSTEM
39 
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <errno.h>
44 
45 #include "freelist.h"
46 #include "direader.h"
47 #include "pathutil.h"
48 #include "homedir.h"
49 #include "stringrp.h"
50 #include "libtecla.h"
51 #include "ioutil.h"
52 #include "expand.h"
53 #include "errmsg.h"
54 
55 /*
56  * Specify the number of elements to extend the files[] array by
57  * when it proves to be too small. This also sets the initial size
58  * of the array.
59  */
60 #define MATCH_BLK_FACT 256
61 
62 /*
63  * A list of directory iterators is maintained using nodes of the
64  * following form.
65  */
66 typedef struct DirNode DirNode;
67 struct DirNode {
68   DirNode *next;       /* The next directory in the list */
69   DirNode *prev;       /* The node that precedes this node in the list */
70   DirReader *dr;       /* The directory reader object */
71 };
72 
73 typedef struct {
74   FreeList *mem;       /* Memory for DirNode list nodes */
75   DirNode *head;       /* The head of the list of used and unused cache nodes */
76   DirNode *next;       /* The next unused node between head and tail */
77   DirNode *tail;       /* The tail of the list of unused cache nodes */
78 } DirCache;
79 
80 /*
81  * Specify how many directory cache nodes to allocate at a time.
82  */
83 #define DIR_CACHE_BLK 20
84 
85 /*
86  * Set the maximum length allowed for usernames.
87  */
88 #define USR_LEN 100
89 
90 /*
91  * Set the maximum length allowed for environment variable names.
92  */
93 #define ENV_LEN 100
94 
95 /*
96  * Set the default number of spaces place between columns when listing
97  * a set of expansions.
98  */
99 #define EF_COL_SEP 2
100 
101 struct ExpandFile {
102   ErrMsg *err;            /* The error reporting buffer */
103   StringGroup *sg;        /* A list of string segments in which */
104                           /*  matching filenames are stored. */
105   DirCache cache;         /* The cache of directory reader objects */
106   PathName *path;         /* The pathname being matched */
107   HomeDir *home;          /* Home-directory lookup object */
108   int files_dim;          /* The allocated dimension of result.files[] */
109   char usrnam[USR_LEN+1]; /* A user name */
110   char envnam[ENV_LEN+1]; /* An environment variable name */
111   FileExpansion result;   /* The container used to return the results of */
112                           /*  expanding a path. */
113 };
114 
115 static int ef_record_pathname(ExpandFile *ef, const char *pathname,
116 			      int remove_escapes);
117 static char *ef_cache_pathname(ExpandFile *ef, const char *pathname,
118 			       int remove_escapes);
119 static void ef_clear_files(ExpandFile *ef);
120 
121 static DirNode *ef_open_dir(ExpandFile *ef, const char *pathname);
122 static DirNode *ef_close_dir(ExpandFile *ef, DirNode *node);
123 static char *ef_expand_special(ExpandFile *ef, const char *path, int pathlen);
124 static int ef_match_relative_pathname(ExpandFile *ef, DirReader *dr,
125 				      const char *pattern, int separate);
126 static int ef_matches_range(int c, const char *pattern, const char **endp);
127 static int ef_string_matches_pattern(const char *file, const char *pattern,
128 				      int xplicit, const char *nextp);
129 static int ef_cmp_strings(const void *v1, const void *v2);
130 
131 /*
132  * Encapsulate the formatting information needed to layout a
133  * multi-column listing of expansions.
134  */
135 typedef struct {
136   int term_width;     /* The width of the terminal (characters) */
137   int column_width;   /* The number of characters within in each column. */
138   int ncol;           /* The number of columns needed */
139   int nline;          /* The number of lines needed */
140 } EfListFormat;
141 
142 /*
143  * Given the current terminal width, and a list of file expansions,
144  * determine how to best use the terminal width to display a multi-column
145  * listing of expansions.
146  */
147 static void ef_plan_listing(FileExpansion *result, int term_width,
148 			    EfListFormat *fmt);
149 
150 /*
151  * Display a given line of a multi-column list of file-expansions.
152  */
153 static int ef_format_line(FileExpansion *result, EfListFormat *fmt, int lnum,
154 			  GlWriteFn *write_fn, void *data);
155 
156 /*.......................................................................
157  * Create the resources needed to expand filenames.
158  *
159  * Output:
160  *  return  ExpandFile *  The new object, or NULL on error.
161  */
162 ExpandFile *new_ExpandFile(void)
163 {
164   ExpandFile *ef;  /* The object to be returned */
165 /*
166  * Allocate the container.
167  */
168   ef = (ExpandFile *) malloc(sizeof(ExpandFile));
169   if(!ef) {
170     errno = ENOMEM;
171     return NULL;
172   };
173 /*
174  * Before attempting any operation that might fail, initialize the
175  * container at least up to the point at which it can safely be passed
176  * to del_ExpandFile().
177  */
178   ef->err = NULL;
179   ef->sg = NULL;
180   ef->cache.mem = NULL;
181   ef->cache.head = NULL;
182   ef->cache.next = NULL;
183   ef->cache.tail = NULL;
184   ef->path = NULL;
185   ef->home = NULL;
186   ef->result.files = NULL;
187   ef->result.nfile = 0;
188   ef->usrnam[0] = '\0';
189   ef->envnam[0] = '\0';
190 /*
191  * Allocate a place to record error messages.
192  */
193   ef->err = _new_ErrMsg();
194   if(!ef->err)
195     return del_ExpandFile(ef);
196 /*
197  * Allocate a list of string segments for storing filenames.
198  */
199   ef->sg = _new_StringGroup(_pu_pathname_dim());
200   if(!ef->sg)
201     return del_ExpandFile(ef);
202 /*
203  * Allocate a freelist for allocating directory cache nodes.
204  */
205   ef->cache.mem = _new_FreeList(sizeof(DirNode), DIR_CACHE_BLK);
206   if(!ef->cache.mem)
207     return del_ExpandFile(ef);
208 /*
209  * Allocate a pathname buffer.
210  */
211   ef->path = _new_PathName();
212   if(!ef->path)
213     return del_ExpandFile(ef);
214 /*
215  * Allocate an object for looking up home-directories.
216  */
217   ef->home = _new_HomeDir();
218   if(!ef->home)
219     return del_ExpandFile(ef);
220 /*
221  * Allocate an array for files. This will be extended later if needed.
222  */
223   ef->files_dim = MATCH_BLK_FACT;
224   ef->result.files = (char **) malloc(sizeof(ef->result.files[0]) *
225 				      ef->files_dim);
226   if(!ef->result.files) {
227     errno = ENOMEM;
228     return del_ExpandFile(ef);
229   };
230   return ef;
231 }
232 
233 /*.......................................................................
234  * Delete a ExpandFile object.
235  *
236  * Input:
237  *  ef     ExpandFile *  The object to be deleted.
238  * Output:
239  *  return ExpandFile *  The deleted object (always NULL).
240  */
241 ExpandFile *del_ExpandFile(ExpandFile *ef)
242 {
243   if(ef) {
244     DirNode *dnode;
245 /*
246  * Delete the string segments.
247  */
248     ef->sg = _del_StringGroup(ef->sg);
249 /*
250  * Delete the cached directory readers.
251  */
252     for(dnode=ef->cache.head; dnode; dnode=dnode->next)
253       dnode->dr = _del_DirReader(dnode->dr);
254 /*
255  * Delete the memory from which the DirNode list was allocated, thus
256  * deleting the list at the same time.
257  */
258     ef->cache.mem = _del_FreeList(ef->cache.mem, 1);
259     ef->cache.head = ef->cache.tail = ef->cache.next = NULL;
260 /*
261  * Delete the pathname buffer.
262  */
263     ef->path = _del_PathName(ef->path);
264 /*
265  * Delete the home-directory lookup object.
266  */
267     ef->home = _del_HomeDir(ef->home);
268 /*
269  * Delete the array of pointers to files.
270  */
271     if(ef->result.files) {
272       free(ef->result.files);
273       ef->result.files = NULL;
274     };
275 /*
276  * Delete the error report buffer.
277  */
278     ef->err = _del_ErrMsg(ef->err);
279 /*
280  * Delete the container.
281  */
282     free(ef);
283   };
284   return NULL;
285 }
286 
287 /*.......................................................................
288  * Expand a pathname, converting ~user/ and ~/ patterns at the start
289  * of the pathname to the corresponding home directories, replacing
290  * $envvar with the value of the corresponding environment variable,
291  * and then, if there are any wildcards, matching these against existing
292  * filenames.
293  *
294  * If no errors occur, a container is returned containing the array of
295  * files that resulted from the expansion. If there were no wildcards
296  * in the input pathname, this will contain just the original pathname
297  * after expansion of ~ and $ expressions. If there were any wildcards,
298  * then the array will contain the files that matched them. Note that
299  * if there were any wildcards but no existing files match them, this
300  * is counted as an error and NULL is returned.
301  *
302  * The supported wildcards and their meanings are:
303  *  *        -  Match any sequence of zero or more characters.
304  *  ?        -  Match any single character.
305  *  [chars]  -  Match any single character that appears in 'chars'.
306  *              If 'chars' contains an expression of the form a-b,
307  *              then any character between a and b, including a and b,
308  *              matches. The '-' character looses its special meaning
309  *              as a range specifier when it appears at the start
310  *              of the sequence of characters.
311  *  [^chars] -  The same as [chars] except that it matches any single
312  *              character that doesn't appear in 'chars'.
313  *
314  * Wildcard expressions are applied to individual filename components.
315  * They don't match across directory separators. A '.' character at
316  * the beginning of a filename component must also be matched
317  * explicitly by a '.' character in the input pathname, since these
318  * are UNIX's hidden files.
319  *
320  * Input:
321  *  ef         ExpandFile *  The pathname expansion resource object.
322  *  path             char *  The path name to be expanded.
323  *  pathlen           int    The length of the suffix of path[] that
324  *                           constitutes the filename to be expanded,
325  *                           or -1 to specify that the whole of the
326  *                           path string should be used. Note that
327  *                           regardless of the value of this argument,
328  *                           path[] must contain a '\0' terminated
329  *                           string, since this function checks that
330  *                           pathlen isn't mistakenly too long.
331  * Output:
332  *  return  FileExpansion *  A pointer to a container within the given
333  *                           ExpandFile object. This contains an array
334  *                           of the pathnames that resulted from expanding
335  *                           ~ and $ expressions and from matching any
336  *                           wildcards, sorted into lexical order.
337  *                           This container and its contents will be
338  *                           recycled on subsequent calls, so if you need
339  *                           to keep the results of two successive runs,
340  *                           you will either have to allocate a private
341  *                           copy of the array, or use two ExpandFile
342  *                           objects.
343  *
344  *                           On error NULL is returned. A description
345  *                           of the error can be acquired by calling the
346  *                           ef_last_error() function.
347  */
348 FileExpansion *ef_expand_file(ExpandFile *ef, const char *path, int pathlen)
349 {
350   DirNode *dnode;       /* A directory-reader cache node */
351   const char *dirname;  /* The name of the top level directory of the search */
352   const char *pptr;     /* A pointer into path[] */
353   int wild;             /* True if the path contains any wildcards */
354 /*
355  * Check the arguments.
356  */
357   if(!ef || !path) {
358     if(ef) {
359       _err_record_msg(ef->err, "ef_expand_file: NULL path argument",
360 		      END_ERR_MSG);
361     };
362     errno = EINVAL;
363     return NULL;
364   };
365 /*
366  * If the caller specified that the whole of path[] be matched,
367  * work out the corresponding length.
368  */
369   if(pathlen < 0 || pathlen > strlen(path))
370     pathlen = strlen(path);
371 /*
372  * Discard previous expansion results.
373  */
374   ef_clear_files(ef);
375 /*
376  * Preprocess the path, expanding ~/, ~user/ and $envvar references,
377  * using ef->path as a work directory and returning a pointer to
378  * a copy of the resulting pattern in the cache.
379  */
380   path = ef_expand_special(ef, path, pathlen);
381   if(!path)
382     return NULL;
383 /*
384  * Clear the pathname buffer.
385  */
386   _pn_clear_path(ef->path);
387 /*
388  * Does the pathname contain any wildcards?
389  */
390   for(wild=0,pptr=path; !wild && *pptr; pptr++) {
391     switch(*pptr) {
392     case '\\':                      /* Skip escaped characters */
393       if(pptr[1])
394 	pptr++;
395       break;
396     case '*': case '?': case '[':   /* A wildcard character? */
397       wild = 1;
398       break;
399     };
400   };
401 /*
402  * If there are no wildcards to match, copy the current expanded
403  * path into the output array, removing backslash escapes while doing so.
404  */
405   if(!wild) {
406     if(ef_record_pathname(ef, path, 1))
407       return NULL;
408 /*
409  * Does the filename exist?
410  */
411     ef->result.exists = _pu_file_exists(ef->result.files[0]);
412 /*
413  * Match wildcards against existing files.
414  */
415   } else {
416 /*
417  * Only existing files that match the pattern will be returned in the
418  * cache.
419  */
420     ef->result.exists = 1;
421 /*
422  * Treat matching of the root-directory as a special case since it
423  * isn't contained in a directory.
424  */
425     if(strcmp(path, FS_ROOT_DIR) == 0) {
426       if(ef_record_pathname(ef, FS_ROOT_DIR, 0))
427 	return NULL;
428     } else {
429 /*
430  * What should the top level directory of the search be?
431  */
432       if(strncmp(path, FS_ROOT_DIR, FS_ROOT_DIR_LEN) == 0) {
433 	dirname = FS_ROOT_DIR;
434 	if(!_pn_append_to_path(ef->path, FS_ROOT_DIR, -1, 0)) {
435 	  _err_record_msg(ef->err, "Insufficient memory to record path",
436 			  END_ERR_MSG);
437 	  return NULL;
438 	};
439 	path += FS_ROOT_DIR_LEN;
440       } else {
441 	dirname = FS_PWD;
442       };
443 /*
444  * Open the top-level directory of the search.
445  */
446       dnode = ef_open_dir(ef, dirname);
447       if(!dnode)
448 	return NULL;
449 /*
450  * Recursively match successive directory components of the path.
451  */
452       if(ef_match_relative_pathname(ef, dnode->dr, path, 0)) {
453 	dnode = ef_close_dir(ef, dnode);
454 	return NULL;
455       };
456 /*
457  * Cleanup.
458  */
459       dnode = ef_close_dir(ef, dnode);
460     };
461 /*
462  * No files matched?
463  */
464     if(ef->result.nfile < 1) {
465       _err_record_msg(ef->err, "No files match", END_ERR_MSG);
466       return NULL;
467     };
468 /*
469  * Sort the pathnames that matched.
470  */
471     qsort(ef->result.files, ef->result.nfile, sizeof(ef->result.files[0]),
472 	  ef_cmp_strings);
473   };
474 /*
475  * Return the result container.
476  */
477   return &ef->result;
478 }
479 
480 /*.......................................................................
481  * Attempt to recursively match the given pattern with the contents of
482  * the current directory, descending sub-directories as needed.
483  *
484  * Input:
485  *  ef      ExpandFile *  The pathname expansion resource object.
486  *  dr       DirReader *  The directory reader object of the directory
487  *                        to be searched.
488  *  pattern const char *  The pattern to match with files in the current
489  *                        directory.
490  *  separate       int    When appending a filename from the specified
491  *                        directory to ef->pathname, insert a directory
492  *                        separator between the existing pathname and
493  *                        the filename, unless separate is zero.
494  * Output:
495  *  return         int    0 - OK.
496  *                        1 - Error.
497  */
498 static int ef_match_relative_pathname(ExpandFile *ef, DirReader *dr,
499 				       const char *pattern, int separate)
500 {
501   const char *nextp;  /* The pointer to the character that follows the part */
502                       /*  of the pattern that is to be matched with files */
503                       /*  in the current directory. */
504   char *file;         /* The name of the file being matched */
505   int pathlen;        /* The length of ef->pathname[] on entry to this */
506                       /*  function */
507 /*
508  * Record the current length of the pathname string recorded in
509  * ef->pathname[].
510  */
511   pathlen = strlen(ef->path->name);
512 /*
513  * Get a pointer to the character that follows the end of the part of
514  * the pattern that should be matched to files within the current directory.
515  * This will either point to a directory separator, or to the '\0' terminator
516  * of the pattern string.
517  */
518   for(nextp=pattern; *nextp && strncmp(nextp, FS_DIR_SEP, FS_DIR_SEP_LEN) != 0;
519       nextp++)
520     ;
521 /*
522  * Read each file from the directory, attempting to match it to the
523  * current pattern.
524  */
525   while((file=_dr_next_file(dr)) != NULL) {
526 /*
527  * Does the latest file match the pattern up to nextp?
528  */
529     if(ef_string_matches_pattern(file, pattern, file[0]=='.', nextp)) {
530 /*
531  * Append the new directory entry to the current matching pathname.
532  */
533       if((separate && _pn_append_to_path(ef->path, FS_DIR_SEP, -1, 0)==NULL) ||
534 	 _pn_append_to_path(ef->path, file, -1, 0)==NULL) {
535 	_err_record_msg(ef->err, "Insufficient memory to record path",
536 			END_ERR_MSG);
537 	return 1;
538       };
539 /*
540  * If we have reached the end of the pattern, record the accumulated
541  * pathname in the list of matching files.
542  */
543       if(*nextp == '\0') {
544 	if(ef_record_pathname(ef, ef->path->name, 0))
545 	  return 1;
546 /*
547  * If the matching directory entry is a subdirectory, and the
548  * next character of the pattern is a directory separator,
549  * recursively call the current function to scan the sub-directory
550  * for matches.
551  */
552       } else if(_pu_path_is_dir(ef->path->name) &&
553 		strncmp(nextp, FS_DIR_SEP, FS_DIR_SEP_LEN) == 0) {
554 /*
555  * If the pattern finishes with the directory separator, then
556  * record the pathame as matching.
557  */
558 	if(nextp[FS_DIR_SEP_LEN] == '\0') {
559 	  if(ef_record_pathname(ef, ef->path->name, 0))
560 	    return 1;
561 /*
562  * Match files within the directory.
563  */
564 	} else {
565 	  DirNode *subdnode = ef_open_dir(ef, ef->path->name);
566 	  if(subdnode) {
567 	    if(ef_match_relative_pathname(ef, subdnode->dr,
568 					   nextp+FS_DIR_SEP_LEN, 1)) {
569 	      subdnode = ef_close_dir(ef, subdnode);
570 	      return 1;
571 	    };
572 	    subdnode = ef_close_dir(ef, subdnode);
573 	  };
574 	};
575       };
576 /*
577  * Remove the latest filename from the pathname string, so that
578  * another matching file can be appended.
579  */
580       ef->path->name[pathlen] = '\0';
581     };
582   };
583   return 0;
584 }
585 
586 /*.......................................................................
587  * Record a new matching filename.
588  *
589  * Input:
590  *  ef        ExpandFile *  The filename-match resource object.
591  *  pathname  const char *  The pathname to record.
592  *  remove_escapes   int    If true, remove backslash escapes in the
593  *                          recorded copy of the pathname.
594  * Output:
595  *  return           int     0 - OK.
596  *                           1 - Error (ef->err will contain a
597  *                                      description of the error).
598  */
599 static int ef_record_pathname(ExpandFile *ef, const char *pathname,
600 			      int remove_escapes)
601 {
602   char *copy;          /* The recorded copy of pathname[] */
603 /*
604  * Attempt to make a copy of the pathname in the cache.
605  */
606   copy = ef_cache_pathname(ef, pathname, remove_escapes);
607   if(!copy)
608     return 1;
609 /*
610  * If there isn't room to record a pointer to the recorded pathname in the
611  * array of files, attempt to extend the array.
612  */
613   if(ef->result.nfile + 1 > ef->files_dim) {
614     int files_dim = ef->files_dim + MATCH_BLK_FACT;
615     char **files = (char **) realloc(ef->result.files,
616 				     files_dim * sizeof(files[0]));
617     if(!files) {
618       _err_record_msg(ef->err,
619 	     "Insufficient memory to record all of the matching filenames",
620 	     END_ERR_MSG);
621       errno = ENOMEM;
622       return 1;
623     };
624     ef->result.files = files;
625     ef->files_dim = files_dim;
626   };
627 /*
628  * Record a pointer to the new match.
629  */
630   ef->result.files[ef->result.nfile++] = copy;
631   return 0;
632 }
633 
634 /*.......................................................................
635  * Record a pathname in the cache.
636  *
637  * Input:
638  *  ef       ExpandFile *  The filename-match resource object.
639  *  pathname       char *  The pathname to record.
640  *  remove_escapes  int    If true, remove backslash escapes in the
641  *                         copy of the pathname.
642  * Output:
643  *  return         char *  The pointer to the copy of the pathname.
644  *                         On error NULL is returned and a description
645  *                         of the error is left in ef->err.
646  */
647 static char *ef_cache_pathname(ExpandFile *ef, const char *pathname,
648 			       int remove_escapes)
649 {
650   char *copy = _sg_store_string(ef->sg, pathname, remove_escapes);
651   if(!copy)
652     _err_record_msg(ef->err, "Insufficient memory to store pathname",
653 		    END_ERR_MSG);
654   return copy;
655 }
656 
657 /*.......................................................................
658  * Clear the results of the previous expansion operation, ready for the
659  * next.
660  *
661  * Input:
662  *  ef    ExpandFile *  The pathname expansion resource object.
663  */
664 static void ef_clear_files(ExpandFile *ef)
665 {
666   _clr_StringGroup(ef->sg);
667   _pn_clear_path(ef->path);
668   ef->result.exists = 0;
669   ef->result.nfile = 0;
670   _err_clear_msg(ef->err);
671   return;
672 }
673 
674 /*.......................................................................
675  * Get a new directory reader object from the cache.
676  *
677  * Input:
678  *  ef        ExpandFile *  The pathname expansion resource object.
679  *  pathname  const char *  The pathname of the directory.
680  * Output:
681  *  return       DirNode *  The cache entry of the new directory reader,
682  *                          or NULL on error. On error, ef->err will
683  *                          contain a description of the error.
684  */
685 static DirNode *ef_open_dir(ExpandFile *ef, const char *pathname)
686 {
687   char *errmsg = NULL;  /* An error message from a called function */
688   DirNode *node;        /* The cache node used */
689 /*
690  * Get the directory reader cache.
691  */
692   DirCache *cache = &ef->cache;
693 /*
694  * Extend the cache if there are no free cache nodes.
695  */
696   if(!cache->next) {
697     node = (DirNode *) _new_FreeListNode(cache->mem);
698     if(!node) {
699       _err_record_msg(ef->err, "Insufficient memory to open a new directory",
700 		      END_ERR_MSG);
701       return NULL;
702     };
703 /*
704  * Initialize the cache node.
705  */
706     node->next = NULL;
707     node->prev = NULL;
708     node->dr = NULL;
709 /*
710  * Allocate a directory reader object.
711  */
712     node->dr = _new_DirReader();
713     if(!node->dr) {
714       _err_record_msg(ef->err, "Insufficient memory to open a new directory",
715 		      END_ERR_MSG);
716       node = (DirNode *) _del_FreeListNode(cache->mem, node);
717       return NULL;
718     };
719 /*
720  * Append the node to the cache list.
721  */
722     node->prev = cache->tail;
723     if(cache->tail)
724       cache->tail->next = node;
725     else
726       cache->head = node;
727     cache->next = cache->tail = node;
728   };
729 /*
730  * Get the first unused node, but don't remove it from the list yet.
731  */
732   node = cache->next;
733 /*
734  * Attempt to open the specified directory.
735  */
736   if(_dr_open_dir(node->dr, pathname, &errmsg)) {
737     _err_record_msg(ef->err, errmsg, END_ERR_MSG);
738     return NULL;
739   };
740 /*
741  * Now that we have successfully opened the specified directory,
742  * remove the cache node from the list, and relink the list around it.
743  */
744   cache->next = node->next;
745   if(node->prev)
746     node->prev->next = node->next;
747   else
748     cache->head = node->next;
749   if(node->next)
750     node->next->prev = node->prev;
751   else
752     cache->tail = node->prev;
753   node->next = node->prev = NULL;
754 /*
755  * Return the successfully initialized cache node to the caller.
756  */
757   return node;
758 }
759 
760 /*.......................................................................
761  * Return a directory reader object to the cache, after first closing
762  * the directory that it was managing.
763  *
764  * Input:
765  *  ef    ExpandFile *  The pathname expansion resource object.
766  *  node     DirNode *  The cache entry of the directory reader, as returned
767  *                      by ef_open_dir().
768  * Output:
769  *  return   DirNode *  The deleted DirNode (ie. allways NULL).
770  */
771 static DirNode *ef_close_dir(ExpandFile *ef, DirNode *node)
772 {
773 /*
774  * Get the directory reader cache.
775  */
776   DirCache *cache = &ef->cache;
777 /*
778  * Close the directory.
779  */
780   _dr_close_dir(node->dr);
781 /*
782  * Return the node to the tail of the cache list.
783  */
784   node->next = NULL;
785   node->prev = cache->tail;
786   if(cache->tail)
787     cache->tail->next = node;
788   else
789     cache->head = cache->tail = node;
790   if(!cache->next)
791     cache->next = node;
792   return NULL;
793 }
794 
795 /*.......................................................................
796  * Return non-zero if the specified file name matches a given glob
797  * pattern.
798  *
799  * Input:
800  *  file     const char *  The file-name component to be matched to the pattern.
801  *  pattern  const char *  The start of the pattern to match against file[].
802  *  xplicit         int    If non-zero, the first character must be matched
803  *                         explicitly (ie. not with a wildcard).
804  *  nextp    const char *  The pointer to the the character following the
805  *                         end of the pattern in pattern[].
806  * Output:
807  *  return    int          0 - Doesn't match.
808  *                         1 - The file-name string matches the pattern.
809  */
810 static int ef_string_matches_pattern(const char *file, const char *pattern,
811 				      int xplicit, const char *nextp)
812 {
813   const char *pptr = pattern; /* The pointer used to scan the pattern */
814   const char *fptr = file;    /* The pointer used to scan the filename string */
815 /*
816  * Match each character of the pattern in turn.
817  */
818   while(pptr < nextp) {
819 /*
820  * Handle the next character of the pattern.
821  */
822     switch(*pptr) {
823 /*
824  * A match zero-or-more characters wildcard operator.
825  */
826     case '*':
827 /*
828  * Skip the '*' character in the pattern.
829  */
830       pptr++;
831 /*
832  * If wildcards aren't allowed, the pattern doesn't match.
833  */
834       if(xplicit)
835 	return 0;
836 /*
837  * If the pattern ends with a the '*' wildcard, then the
838  * rest of the filename matches this.
839  */
840       if(pptr >= nextp)
841 	return 1;
842 /*
843  * Using the wildcard to match successively longer sections of
844  * the remaining characters of the filename, attempt to match
845  * the tail of the filename against the tail of the pattern.
846  */
847       for( ; *fptr; fptr++) {
848 	if(ef_string_matches_pattern(fptr, pptr, 0, nextp))
849 	  return 1;
850       };
851       return 0; /* The pattern following the '*' didn't match */
852       break;
853 /*
854  * A match-one-character wildcard operator.
855  */
856     case '?':
857 /*
858  * If there is a character to be matched, skip it and advance the
859  * pattern pointer.
860  */
861       if(!xplicit && *fptr) {
862         fptr++;
863         pptr++;
864 /*
865  * If we hit the end of the filename string, there is no character
866  * matching the operator, so the string doesn't match.
867  */
868       } else {
869         return 0;
870       };
871       break;
872 /*
873  * A character range operator, with the character ranges enclosed
874  * in matching square brackets.
875  */
876     case '[':
877       if(xplicit || !ef_matches_range(*fptr++, ++pptr, &pptr))
878         return 0;
879       break;
880 /*
881  * A backslash in the pattern prevents the following character as
882  * being seen as a special character.
883  */
884     case '\\':
885       pptr++;
886       /* Note fallthrough to default */
887 /*
888  * A normal character to be matched explicitly.
889  */
890     default:
891       if(*fptr == *pptr) {
892         fptr++;
893         pptr++;
894       } else {
895         return 0;
896       };
897       break;
898     };
899 /*
900  * After passing the first character, turn off the explicit match
901  * requirement.
902  */
903     xplicit = 0;
904   };
905 /*
906  * To get here the pattern must have been exhausted. If the filename
907  * string matched, then the filename string must also have been
908  * exhausted.
909  */
910   return *fptr == '\0';
911 }
912 
913 /*.......................................................................
914  * Match a character range expression terminated by an unescaped close
915  * square bracket.
916  *
917  * Input:
918  *  c               int     The character to be matched with the range
919  *                          pattern.
920  *  pattern  const char *   The range pattern to be matched (ie. after the
921  *                          initiating '[' character).
922  *  endp     const char **  On output a pointer to the character following the
923  *                          range expression will be assigned to *endp.
924  * Output:
925  *  return          int     0 - Doesn't match.
926  *                          1 - The character matched.
927  */
928 static int ef_matches_range(int c, const char *pattern, const char **endp)
929 {
930   const char *pptr = pattern;  /* The pointer used to scan the pattern */
931   int invert = 0;              /* True to invert the sense of the match */
932   int matched = 0;             /* True if the character matched the pattern */
933 /*
934  * If the first character is a caret, the sense of the match is
935  * inverted and only if the character isn't one of those in the
936  * range, do we say that it matches.
937  */
938   if(*pptr == '^') {
939     pptr++;
940     invert = 1;
941   };
942 /*
943  * The hyphen is only a special character when it follows the first
944  * character of the range (not including the caret).
945  */
946   if(*pptr == '-') {
947     pptr++;
948     if(c == '-') {
949       *endp = pptr;
950       matched = 1;
951     };
952 /*
953  * Skip other leading '-' characters since they make no sense.
954  */
955     while(*pptr == '-')
956       pptr++;
957   };
958 /*
959  * The hyphen is only a special character when it follows the first
960  * character of the range (not including the caret or a hyphen).
961  */
962   if(*pptr == ']') {
963     pptr++;
964     if(c == ']') {
965       *endp = pptr;
966       matched = 1;
967     };
968   };
969 /*
970  * Having dealt with the characters that have special meanings at
971  * the beginning of a character range expression, see if the
972  * character matches any of the remaining characters of the range,
973  * up until a terminating ']' character is seen.
974  */
975   while(!matched && *pptr && *pptr != ']') {
976 /*
977  * Is this a range of characters signaled by the two end characters
978  * separated by a hyphen?
979  */
980     if(*pptr == '-') {
981       if(pptr[1] != ']') {
982         if(c >= pptr[-1] && c <= pptr[1])
983 	  matched = 1;
984 	pptr += 2;
985       };
986 /*
987  * A normal character to be compared directly.
988  */
989     } else if(*pptr++ == c) {
990       matched = 1;
991     };
992   };
993 /*
994  * Find the terminating ']'.
995  */
996   while(*pptr && *pptr != ']')
997     pptr++;
998 /*
999  * Did we find a terminating ']'?
1000  */
1001   if(*pptr == ']') {
1002     *endp = pptr + 1;
1003     return matched ? !invert : invert;
1004   };
1005 /*
1006  * If the pattern didn't end with a ']' then it doesn't match, regardless
1007  * of the value of the required sense of the match.
1008  */
1009   *endp = pptr;
1010   return 0;
1011 }
1012 
1013 /*.......................................................................
1014  * This is a qsort() comparison function used to sort strings.
1015  *
1016  * Input:
1017  *  v1, v2   void *  Pointers to the two strings to be compared.
1018  * Output:
1019  *  return    int    -1 -> v1 < v2.
1020  *                    0 -> v1 == v2
1021  *                    1 -> v1 > v2
1022  */
1023 static int ef_cmp_strings(const void *v1, const void *v2)
1024 {
1025   char * const *s1 = (char * const *) v1;
1026   char * const *s2 = (char * const *) v2;
1027   return strcmp(*s1, *s2);
1028 }
1029 
1030 /*.......................................................................
1031  * Preprocess a path, expanding ~/, ~user/ and $envvar references, using
1032  * ef->path as a work buffer, then copy the result into a cache entry,
1033  * and return a pointer to this copy.
1034  *
1035  * Input:
1036  *  ef    ExpandFile *  The resource object of the file matcher.
1037  *  pathlen      int    The length of the prefix of path[] to be expanded.
1038  * Output:
1039  *  return      char *  A pointer to a copy of the output path in the
1040  *                      cache. On error NULL is returned, and a description
1041  *                      of the error is left in ef->err.
1042  */
1043 static char *ef_expand_special(ExpandFile *ef, const char *path, int pathlen)
1044 {
1045   int spos;      /* The index of the start of the path segment that needs */
1046                  /*  to be copied from path[] to the output pathname. */
1047   int ppos;      /* The index of a character in path[] */
1048   char *pptr;    /* A pointer into the output path */
1049   int escaped;   /* True if the previous character was a '\' */
1050   int i;
1051 /*
1052  * Clear the pathname buffer.
1053  */
1054   _pn_clear_path(ef->path);
1055 /*
1056  * We need to perform two passes, one to expand environment variables
1057  * and a second to do tilde expansion. This caters for the case
1058  * where an initial dollar expansion yields a tilde expression.
1059  */
1060   escaped = 0;
1061   for(spos=ppos=0; ppos < pathlen; ppos++) {
1062     int c = path[ppos];
1063     if(escaped) {
1064       escaped = 0;
1065     } else if(c == '\\') {
1066       escaped = 1;
1067     } else if(c == '$') {
1068       int envlen;   /* The length of the environment variable */
1069       char *value;  /* The value of the environment variable */
1070 /*
1071  * Record the preceding unrecorded part of the pathname.
1072  */
1073       if(spos < ppos && _pn_append_to_path(ef->path, path + spos, ppos-spos, 0)
1074 	 == NULL) {
1075 	_err_record_msg(ef->err, "Insufficient memory to expand path",
1076 			END_ERR_MSG);
1077 	return NULL;
1078       };
1079 /*
1080  * Skip the dollar.
1081  */
1082       ppos++;
1083 /*
1084  * Copy the environment variable name that follows the dollar into
1085  * ef->envnam[], stopping if a directory separator or end of string
1086  * is seen.
1087  */
1088       for(envlen=0; envlen<ENV_LEN && ppos < pathlen &&
1089 	  strncmp(path + ppos, FS_DIR_SEP, FS_DIR_SEP_LEN); envlen++)
1090 	ef->envnam[envlen] = path[ppos++];
1091 /*
1092  * If the username overflowed the buffer, treat it as invalid (note that
1093  * on most unix systems only 8 characters are allowed in a username,
1094  * whereas our ENV_LEN is much bigger than that.
1095  */
1096       if(envlen >= ENV_LEN) {
1097 	_err_record_msg(ef->err, "Environment variable name too long",
1098 			END_ERR_MSG);
1099 	return NULL;
1100       };
1101 /*
1102  * Terminate the environment variable name.
1103  */
1104       ef->envnam[envlen] = '\0';
1105 /*
1106  * Lookup the value of the environment variable.
1107  */
1108       value = getenv(ef->envnam);
1109       if(!value) {
1110 	_err_record_msg(ef->err, "No expansion found for: $", ef->envnam,
1111 			END_ERR_MSG);
1112 	return NULL;
1113       };
1114 /*
1115  * Copy the value of the environment variable into the output pathname.
1116  */
1117       if(_pn_append_to_path(ef->path, value, -1, 0) == NULL) {
1118 	_err_record_msg(ef->err, "Insufficient memory to expand path",
1119 			END_ERR_MSG);
1120 	return NULL;
1121       };
1122 /*
1123  * Record the start of the uncopied tail of the input pathname.
1124  */
1125       spos = ppos;
1126     };
1127   };
1128 /*
1129  * Record the uncopied tail of the pathname.
1130  */
1131   if(spos < ppos && _pn_append_to_path(ef->path, path + spos, ppos-spos, 0)
1132      == NULL) {
1133     _err_record_msg(ef->err, "Insufficient memory to expand path", END_ERR_MSG);
1134     return NULL;
1135   };
1136 /*
1137  * If the first character of the resulting pathname is a tilde,
1138  * then attempt to substitute the home directory of the specified user.
1139  */
1140   pptr = ef->path->name;
1141   if(*pptr == '~' && path[0] != '\\') {
1142     int usrlen;           /* The length of the username following the tilde */
1143     const char *homedir;  /* The home directory of the user */
1144     int homelen;          /* The length of the home directory string */
1145     int plen;             /* The current length of the path */
1146     int skip=0;           /* The number of characters to skip after the ~user */
1147 /*
1148  * Get the current length of the output path.
1149  */
1150     plen = strlen(ef->path->name);
1151 /*
1152  * Skip the tilde.
1153  */
1154     pptr++;
1155 /*
1156  * Copy the optional username that follows the tilde into ef->usrnam[].
1157  */
1158     for(usrlen=0; usrlen<USR_LEN && *pptr &&
1159 	strncmp(pptr, FS_DIR_SEP, FS_DIR_SEP_LEN); usrlen++)
1160       ef->usrnam[usrlen] = *pptr++;
1161 /*
1162  * If the username overflowed the buffer, treat it as invalid (note that
1163  * on most unix systems only 8 characters are allowed in a username,
1164  * whereas our USR_LEN is much bigger than that.
1165  */
1166     if(usrlen >= USR_LEN) {
1167       _err_record_msg(ef->err, "Username too long", END_ERR_MSG);
1168       return NULL;
1169     };
1170 /*
1171  * Terminate the username string.
1172  */
1173     ef->usrnam[usrlen] = '\0';
1174 /*
1175  * Lookup the home directory of the user.
1176  */
1177     homedir = _hd_lookup_home_dir(ef->home, ef->usrnam);
1178     if(!homedir) {
1179       _err_record_msg(ef->err, _hd_last_home_dir_error(ef->home), END_ERR_MSG);
1180       return NULL;
1181     };
1182     homelen = strlen(homedir);
1183 /*
1184  * ~user and ~ are usually followed by a directory separator to
1185  * separate them from the file contained in the home directory.
1186  * If the home directory is the root directory, then we don't want
1187  * to follow the home directory by a directory separator, so we must
1188  * erase it.
1189  */
1190     if(strcmp(homedir, FS_ROOT_DIR) == 0 &&
1191        strncmp(pptr, FS_DIR_SEP, FS_DIR_SEP_LEN) == 0) {
1192       skip = FS_DIR_SEP_LEN;
1193     };
1194 /*
1195  * If needed, increase the size of the pathname buffer to allow it
1196  * to accomodate the home directory instead of the tilde expression.
1197  * Note that pptr may not be valid after this call.
1198  */
1199     if(_pn_resize_path(ef->path, plen - usrlen - 1 - skip + homelen)==NULL) {
1200       _err_record_msg(ef->err, "Insufficient memory to expand filename",
1201 		      END_ERR_MSG);
1202       return NULL;
1203     };
1204 /*
1205  * Move the part of the pathname that follows the tilde expression to
1206  * the end of where the home directory will need to be inserted.
1207  */
1208     memmove(ef->path->name + homelen,
1209 	    ef->path->name + 1 + usrlen + skip, plen - usrlen - 1 - skip+1);
1210 /*
1211  * Write the home directory at the beginning of the string.
1212  */
1213     for(i=0; i<homelen; i++)
1214       ef->path->name[i] = homedir[i];
1215   };
1216 /*
1217  * Copy the result into the cache, and return a pointer to the copy.
1218  */
1219   return ef_cache_pathname(ef, ef->path->name, 0);
1220 }
1221 
1222 /*.......................................................................
1223  * Return a description of the last path-expansion error that occurred.
1224  *
1225  * Input:
1226  *  ef     ExpandFile *   The path-expansion resource object.
1227  * Output:
1228  *  return       char *   The description of the last error.
1229  */
1230 const char *ef_last_error(ExpandFile *ef)
1231 {
1232   return ef ? _err_get_msg(ef->err) : "NULL ExpandFile argument";
1233 }
1234 
1235 /*.......................................................................
1236  * Print out an array of matching files.
1237  *
1238  * Input:
1239  *  result  FileExpansion *   The container of the sorted array of
1240  *                            expansions.
1241  *  fp               FILE *   The output stream to write to.
1242  *  term_width        int     The width of the terminal.
1243  * Output:
1244  *  return            int     0 - OK.
1245  *                            1 - Error.
1246  */
1247 int ef_list_expansions(FileExpansion *result, FILE *fp, int term_width)
1248 {
1249   return _ef_output_expansions(result, _io_write_stdio, fp, term_width);
1250 }
1251 
1252 /*.......................................................................
1253  * Print out an array of matching files via a callback.
1254  *
1255  * Input:
1256  *  result  FileExpansion *  The container of the sorted array of
1257  *                           expansions.
1258  *  write_fn    GlWriteFn *  The function to call to write the
1259  *                           expansions or 0 to discard the output.
1260  *  data             void *  Anonymous data to pass to write_fn().
1261  *  term_width        int    The width of the terminal.
1262  * Output:
1263  *  return            int    0 - OK.
1264  *                           1 - Error.
1265  */
1266 int _ef_output_expansions(FileExpansion *result, GlWriteFn *write_fn,
1267 			  void *data, int term_width)
1268 {
1269   EfListFormat fmt; /* List formatting information */
1270   int lnum;          /* The sequential number of the line to print next */
1271 /*
1272  * Not enough space to list anything?
1273  */
1274   if(term_width < 1)
1275     return 0;
1276 /*
1277  * Do we have a callback to write via, and any expansions to be listed?
1278  */
1279   if(write_fn && result && result->nfile>0) {
1280 /*
1281  * Work out how to arrange the listing into fixed sized columns.
1282  */
1283     ef_plan_listing(result, term_width, &fmt);
1284 /*
1285  * Print the listing to the specified stream.
1286  */
1287     for(lnum=0; lnum < fmt.nline; lnum++) {
1288       if(ef_format_line(result, &fmt, lnum, write_fn, data))
1289 	return 1;
1290     };
1291   };
1292   return 0;
1293 }
1294 
1295 /*.......................................................................
1296  * Work out how to arrange a given array of completions into a listing
1297  * of one or more fixed size columns.
1298  *
1299  * Input:
1300  *  result   FileExpansion *   The set of completions to be listed.
1301  *  term_width         int     The width of the terminal. A lower limit of
1302  *                             zero is quietly enforced.
1303  * Input/Output:
1304  *  fmt       EfListFormat *   The formatting information will be assigned
1305  *                             to the members of *fmt.
1306  */
1307 static void ef_plan_listing(FileExpansion *result, int term_width,
1308 			    EfListFormat *fmt)
1309 {
1310   int maxlen;    /* The length of the longest matching string */
1311   int i;
1312 /*
1313  * Ensure that term_width >= 0.
1314  */
1315   if(term_width < 0)
1316     term_width = 0;
1317 /*
1318  * Start by assuming the worst case, that either nothing will fit
1319  * on the screen, or that there are no matches to be listed.
1320  */
1321   fmt->term_width = term_width;
1322   fmt->column_width = 0;
1323   fmt->nline = fmt->ncol = 0;
1324 /*
1325  * Work out the maximum length of the matching strings.
1326  */
1327   maxlen = 0;
1328   for(i=0; i<result->nfile; i++) {
1329     int len = strlen(result->files[i]);
1330     if(len > maxlen)
1331       maxlen = len;
1332   };
1333 /*
1334  * Nothing to list?
1335  */
1336   if(maxlen == 0)
1337     return;
1338 /*
1339  * Split the available terminal width into columns of
1340  * maxlen + EF_COL_SEP characters.
1341  */
1342   fmt->column_width = maxlen;
1343   fmt->ncol = fmt->term_width / (fmt->column_width + EF_COL_SEP);
1344 /*
1345  * If the column width is greater than the terminal width, zero columns
1346  * will have been selected. Set a lower limit of one column. Leave it
1347  * up to the caller how to deal with completions who's widths exceed
1348  * the available terminal width.
1349  */
1350   if(fmt->ncol < 1)
1351     fmt->ncol = 1;
1352 /*
1353  * How many lines of output will be needed?
1354  */
1355   fmt->nline = (result->nfile + fmt->ncol - 1) / fmt->ncol;
1356   return;
1357 }
1358 
1359 /*.......................................................................
1360  * Render one line of a multi-column listing of completions, using a
1361  * callback function to pass the output to an arbitrary destination.
1362  *
1363  * Input:
1364  *  result   FileExpansion *  The container of the sorted array of
1365  *                            completions.
1366  *  fmt       EfListFormat *  Formatting information.
1367  *  lnum               int    The index of the line to print, starting
1368  *                            from 0, and incrementing until the return
1369  *                            value indicates that there is nothing more
1370  *                            to be printed.
1371  *  write_fn     GlWriteFn *  The function to call to write the line, or
1372  *                            0 to discard the output.
1373  *  data              void *  Anonymous data to pass to write_fn().
1374  * Output:
1375  *  return             int    0 - Line printed ok.
1376  *                            1 - Nothing to print.
1377  */
1378 static int ef_format_line(FileExpansion *result, EfListFormat *fmt, int lnum,
1379 			  GlWriteFn *write_fn, void *data)
1380 {
1381   int col;             /* The index of the list column being output */
1382 /*
1383  * If the line index is out of bounds, there is nothing to be written.
1384  */
1385   if(lnum < 0 || lnum >= fmt->nline)
1386     return 1;
1387 /*
1388  * If no output function has been provided, return as though the line
1389  * had been printed.
1390  */
1391   if(!write_fn)
1392     return 0;
1393 /*
1394  * Print the matches in 'ncol' columns, sorted in line order within each
1395  * column.
1396  */
1397   for(col=0; col < fmt->ncol; col++) {
1398     int m = col*fmt->nline + lnum;
1399 /*
1400  * Is there another match to be written? Note that in general
1401  * the last line of a listing will have fewer filled columns
1402  * than the initial lines.
1403  */
1404     if(m < result->nfile) {
1405       char *file = result->files[m];
1406 /*
1407  * How long are the completion and type-suffix strings?
1408  */
1409       int flen = strlen(file);
1410 /*
1411  * Write the completion string.
1412  */
1413       if(write_fn(data, file, flen) != flen)
1414 	return 1;
1415 /*
1416  * If another column follows the current one, pad to its start with spaces.
1417  */
1418       if(col+1 < fmt->ncol) {
1419 /*
1420  * The following constant string of spaces is used to pad the output.
1421  */
1422 	static const char spaces[] = "                    ";
1423 	static const int nspace = sizeof(spaces) - 1;
1424 /*
1425  * Pad to the next column, using as few sub-strings of the spaces[]
1426  * array as possible.
1427  */
1428 	int npad = fmt->column_width + EF_COL_SEP - flen;
1429 	while(npad>0) {
1430 	  int n = npad > nspace ? nspace : npad;
1431 	  if(write_fn(data, spaces + nspace - n, n) != n)
1432 	    return 1;
1433 	  npad -= n;
1434 	};
1435       };
1436     };
1437   };
1438 /*
1439  * Start a new line.
1440  */
1441   {
1442     char s[] = "\r\n";
1443     int n = strlen(s);
1444     if(write_fn(data, s, n) != n)
1445       return 1;
1446   };
1447   return 0;
1448 }
1449 
1450 #endif  /* ifndef WITHOUT_FILE_SYSTEM */
1451