1 // File name globbing
2 // Copyright (C) 2000 Core Technologies.
3
4 // This file is part of e93.
5 //
6 // e93 is free software; you can redistribute it and/or modify
7 // it under the terms of the e93 LICENSE AGREEMENT.
8 //
9 // e93 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 // e93 LICENSE AGREEMENT for more details.
13 //
14 // You should have received a copy of the e93 LICENSE AGREEMENT
15 // along with e93; see the file "LICENSE.TXT".
16
17 #include "includes.h"
18
19 enum
20 {
21 NO_USER=0,
22 MATCH_FAILED,
23 PATH_TOO_LONG,
24 UNBALANCED_SQUARE_BRACKET,
25 DANGLING_QUOTE
26 };
27
28 static const char *errorDescriptions[]=
29 {
30 "Failed to locate user",
31 "Nothing matched",
32 "Path grew too large",
33 "Unbalanced [ or [^",
34 "Incomplete \\ expression"
35 };
36
37 typedef struct
38 {
39 bool
40 dontGlobLast, // if true, the last element of the pattern will not be checked for existence if it contains no meta characters
41 passNoMeta; // if true, the entire pattern will not be checked for existence if it contains no meta characters
42 bool
43 hadMeta; // tells if we have seen a meta character in the path so far
44 UINT32
45 numPaths; // count of total paths so far
46 char
47 **paths; // list of paths matching pattern
48 } GLOB_DATA;
49
ExtractFileName(char * fullPath,char * filePart)50 static void ExtractFileName(char *fullPath,char *filePart)
51 // given fullPath which is considered to be a complete path and file name,
52 // truncate fullPath to just the path part, and copy the file part
53 // to filePart
54 // NOTE: filePart must be at least as large an array as fullPath
55 {
56 int
57 length;
58
59 length=strlen(fullPath);
60 while(length>=0&&fullPath[length]!=PATH_SEP)
61 {
62 length--;
63 }
64 if(length>0)
65 {
66 strcpy(filePart,&(fullPath[length+1]));
67 fullPath[length]='\0';
68 }
69 else
70 {
71 if(fullPath[0]==PATH_SEP) // / at 0 is special
72 {
73 strcpy(filePart,&(fullPath[1]));
74 fullPath[1]='\0';
75 }
76 else
77 {
78 strcpy(filePart,fullPath);
79 fullPath[0]='\0';
80 }
81 }
82 }
83
SplitPathAndFile(char * fullPath,char * pathPart,char * filePart)84 void SplitPathAndFile(char *fullPath,char *pathPart,char *filePart)
85 // split fullPath into pathPart, and filePart
86 // NOTE: pathPart and filePart must be at least MAXPATHLEN+1
87 // bytes long
88 // if fullPath is longer than MAXPATHLEN, parts will get truncated when copied
89 // NOTE: if fullPath is not a valid path, return the whole thing as filePart,
90 // and the current directory as the pathPart
91 {
92 struct stat
93 statOut;
94 char
95 tempPath[MAXPATHLEN+1];
96
97 if(realpath2(fullPath,pathPart))
98 {
99 if((stat(pathPart,&statOut)!=-1)&&S_ISDIR(statOut.st_mode)) // point to a file, or a directory?
100 {
101 filePart[0]='\0'; // points to a directory, so no file part, this is done
102 }
103 else
104 {
105 ExtractFileName(pathPart,filePart); // pointed to a file, so separate them
106 }
107 }
108 else
109 {
110 strncpy(tempPath,fullPath,MAXPATHLEN); // copy this to separate it
111 tempPath[MAXPATHLEN]='\0';
112 ExtractFileName(tempPath,filePart); // separate the pieces
113 if(!realpath2(tempPath,pathPart)) // try to get the path to the unknown file part
114 {
115 if(!realpath2(".",pathPart)) // get the current directory
116 {
117 pathPart[0]='\0'; // just return empty string
118 }
119 strncpy(filePart,fullPath,MAXPATHLEN);
120 filePart[MAXPATHLEN]='\0';
121 }
122 }
123 }
124
ConcatPathAndFile(const char * path,const char * file,char * result)125 void ConcatPathAndFile(const char *path,const char *file,char *result)
126 // merge path and file to form a fully qualified file name
127 // result must be at least MAXPATHLEN+1 characters long
128 // NOTE: if path and file combine into a string longer than MAXPATHLEN,
129 // the combined string will be truncated
130 // NOTE ALSO, if file specifies a complete Path (starts with a PATH_SEP) then
131 // it alone is added to result
132 {
133 int
134 pathLength,
135 fileLength;
136
137 if(file[0]!=PATH_SEP)
138 {
139 pathLength=strlen(path);
140 if(pathLength>MAXPATHLEN)
141 {
142 pathLength=MAXPATHLEN;
143 }
144 strncpy(result,path,pathLength);
145 if(pathLength&&(path[pathLength-1]!=PATH_SEP)&&(pathLength<MAXPATHLEN))
146 {
147 result[pathLength++]=PATH_SEP;
148 }
149 }
150 else
151 {
152 pathLength=0;
153 }
154 fileLength=strlen(file);
155 if(pathLength+fileLength>MAXPATHLEN)
156 {
157 fileLength=(MAXPATHLEN-pathLength);
158 }
159 strncpy(&(result[pathLength]),file,fileLength);
160 result[pathLength+fileLength]='\0';
161 }
162
163 // string list functions
164
FreeStringList(char ** list)165 void FreeStringList(char **list)
166 // free a list of strings created by calling NewStringList
167 {
168 UINT32
169 index;
170
171 index=0;
172 while(list&&list[index])
173 {
174 MDisposePtr(list[index]);
175 index++;
176 }
177 MDisposePtr(list);
178 }
179
CompareStrings(const void * i,const void * j)180 static int CompareStrings(const void *i,const void *j)
181 // compare two elements of the list
182 {
183 char
184 *elementA,
185 *elementB;
186
187 elementA=*((char **)i);
188 elementB=*((char **)j);
189
190 return(strcmp(elementA,elementB));
191 }
192
SortStringList(char ** list,UINT32 numElements)193 void SortStringList(char **list,UINT32 numElements)
194 // run through any elements of list, and sort them
195 // by calling qsort
196 {
197 if(list)
198 {
199 qsort((char *)list,numElements,sizeof(char **),CompareStrings);
200 }
201 }
202
ReplaceStringInList(const char * string,char ** list,UINT32 element)203 bool ReplaceStringInList(const char *string,char **list,UINT32 element)
204 // place string into list at the given position, freeing
205 // any string that was there
206 // NOTE: element must be in range
207 // if there is a problem leave the entry untouched, SetError, and return false
208 {
209 char
210 *newElement;
211
212 if((newElement=(char *)MNewPtr(strlen(string)+1)))
213 {
214 strcpy(newElement,string);
215 MDisposePtr(list[element]);
216 list[element]=newElement;
217 return(true);
218 }
219 return(false);
220 }
221
AddStringToList(const char * string,char *** list,UINT32 * numElements)222 bool AddStringToList(const char *string,char ***list,UINT32 *numElements)
223 // add string to list
224 // at any point, list can be deleted by calling FreeStringList
225 // if there is a problem, do not add the element, SetError, and return false
226 {
227 char
228 **newList;
229 UINT32
230 newLength;
231
232 newLength=sizeof(char *)*((*numElements)+2);
233 if((newList=(char **)MResizePtr(*list,newLength)))
234 {
235 (*list)=newList;
236 if(((*list)[*numElements]=(char *)MNewPtr(strlen(string)+1)))
237 {
238 strcpy((*list)[(*numElements)++],string);
239 (*list)[*numElements]=NULL; // keep the list terminated with a NULL
240 return(true);
241 }
242 }
243 return(false);
244 }
245
NewStringList()246 char **NewStringList()
247 // Create an empty string list
248 // if there is a problem, SetError, return NULL
249 {
250 char
251 **list;
252
253 if((list=(char **)MNewPtr(sizeof(char *))))
254 {
255 list[0]=NULL;
256 }
257 return(list);
258 }
259
260 // tilde expansion routines
261
ExpandTildes(const char * pattern,char * newPattern,UINT32 newPatternLength)262 static bool ExpandTildes(const char *pattern,char *newPattern,UINT32 newPatternLength)
263 // expand tilde sequences in pattern, write output into newPattern
264 // if there is a problem, SetError, and return false
265 {
266 bool
267 done,
268 fail;
269 char
270 character;
271 UINT32
272 inIndex,
273 outIndex;
274 char
275 *home;
276 struct passwd
277 *passwordEntry;
278 UINT32
279 length;
280
281 fail=false;
282 if(pattern[0]=='~') // see if a tilde exists at the start of pattern
283 {
284 inIndex=1; // skip the tilde
285 outIndex=0;
286 done=false;
287 while(!done&&!fail&&(character=pattern[inIndex])&&outIndex<(newPatternLength-1))
288 {
289 switch(character)
290 {
291 case PATH_SEP:
292 inIndex++;
293 done=true;
294 break;
295 case '\\':
296 inIndex++;
297 if(!(newPattern[outIndex++]=pattern[inIndex]))
298 {
299 SetError(errorDescriptions[DANGLING_QUOTE]);
300 fail=true;
301 }
302 break;
303 default:
304 newPattern[outIndex++]=character;
305 inIndex++;
306 break;
307 }
308 }
309 if(!fail)
310 {
311 newPattern[outIndex]='\0'; // terminate the user name string
312 home=NULL; // no home found yet
313 if(outIndex) // name was specified
314 {
315 if((passwordEntry=getpwnam(newPattern)))
316 {
317 home=passwordEntry->pw_dir;
318 }
319 }
320 else
321 {
322 if(!(home=getenv("HOME"))) // try to find environment variable
323 {
324 if((passwordEntry=getpwuid(getuid()))) // if no environment, try to get from password database
325 {
326 home=passwordEntry->pw_dir;
327 }
328 }
329 }
330 if(home)
331 {
332 strncpy(newPattern,home,newPatternLength-1);
333 newPattern[newPatternLength-1]='\0';
334 length=strlen(newPattern);
335 if(length&&length<newPatternLength-1)
336 {
337 if(newPattern[length-1]!=PATH_SEP) // add path separator if needed
338 {
339 newPattern[length++]=PATH_SEP;
340 newPattern[length]='\0';
341 }
342 }
343 strncat(newPattern,&pattern[inIndex],newPatternLength-length);
344 }
345 else
346 {
347 SetError(errorDescriptions[NO_USER]); // user not there
348 fail=true;
349 }
350 }
351 }
352 else
353 {
354 strncpy(newPattern,pattern,newPatternLength-1);
355 newPattern[newPatternLength-1]='\0';
356 }
357 return(!fail);
358 }
359
360 // wildcard expansion routines
361
MatchRange(char character,const char * pattern,UINT32 * patternIndex,bool * haveMatch)362 static bool MatchRange(char character,const char *pattern,UINT32 *patternIndex,bool *haveMatch)
363 // a range is starting in pattern, so attempt to match character
364 // against it
365 {
366 bool
367 fail,
368 done,
369 isNot;
370 char
371 testChar,
372 lastChar;
373
374 isNot=done=fail=false;
375 if(pattern[*patternIndex]=='^') // check for not flag
376 {
377 isNot=true;
378 (*patternIndex)++;
379 }
380 lastChar='\0'; // start with bottom of range at 0
381 *haveMatch=false;
382 while(!done&&!fail&&(testChar=pattern[(*patternIndex)++]))
383 {
384 switch(testChar)
385 {
386 case '\\':
387 if((testChar=pattern[(*patternIndex)++])) // get next character to test
388 {
389 if(character==testChar) // test it literally
390 {
391 *haveMatch=true;
392 }
393 }
394 else
395 {
396 SetError(errorDescriptions[DANGLING_QUOTE]);
397 fail=true; // got \ at the end of the pattern
398 }
399 break;
400 case '-':
401 if((testChar=pattern[(*patternIndex)++])) // get the next character for the range (if there is one)
402 {
403 switch(testChar)
404 {
405 case '\\':
406 if((testChar=pattern[(*patternIndex)++]))
407 {
408 if(character>=lastChar&&character<=testChar)
409 {
410 *haveMatch=true;
411 }
412 }
413 else
414 {
415 SetError(errorDescriptions[DANGLING_QUOTE]);
416 fail=true; // got \ at the end of the pattern
417 }
418 break;
419 case ']':
420 if(character>=lastChar) // empty range at end, so take as infinite end
421 {
422 *haveMatch=true;
423 }
424 done=true;
425 break;
426 default:
427 if(character>=lastChar&&character<=testChar)
428 {
429 *haveMatch=true;
430 }
431 break;
432 }
433 }
434 else
435 {
436 SetError(errorDescriptions[UNBALANCED_SQUARE_BRACKET]);
437 fail=true;
438 }
439 break;
440 case ']':
441 done=true; // scanning is done (empty lists are allowed)
442 break;
443 default: // otherwise it is normal, and just gets tested
444 if(character==testChar)
445 {
446 *haveMatch=true;
447 }
448 break;
449 }
450 lastChar=testChar; // remember this for next time around the loop
451 }
452 if(!done&&!fail) // ran out of things to scan
453 {
454 SetError(errorDescriptions[UNBALANCED_SQUARE_BRACKET]);
455 fail=true;
456 }
457 if(!fail)
458 {
459 if(isNot)
460 {
461 *haveMatch=!*haveMatch;
462 }
463 }
464 return(!fail);
465 }
466
MatchName(const char * name,const char * pattern,UINT32 * patternIndex,bool * haveMatch)467 static bool MatchName(const char *name,const char *pattern,UINT32 *patternIndex,bool *haveMatch)
468 // match name against pattern
469 // pattern points to the start of a pattern terminated with a '\0', or a PATH_SEP
470 // update patternIndex to point to the character that stopped the match
471 // if a match occurs, return true if have match
472 // if there is a problem SetError, and return false
473 {
474 UINT32
475 nameIndex;
476 UINT32
477 startPatternIndex;
478 bool
479 fail,
480 done,
481 matching;
482 char
483 character;
484
485 fail=*haveMatch=done=false;
486 matching=true;
487 nameIndex=0;
488 while(!done&&matching)
489 {
490 switch(pattern[*patternIndex])
491 {
492 case '\0': // ran out of characters of the pattern
493 case PATH_SEP:
494 matching=(name[nameIndex]=='\0');
495 done=true;
496 break;
497 case '*':
498 (*patternIndex)++; // move past the *
499 matching=false;
500 do
501 {
502 startPatternIndex=*patternIndex;
503 fail=!MatchName(&name[nameIndex],pattern,&startPatternIndex,&matching);
504 }
505 while(name[nameIndex++]&&!fail&&!matching); // recurse trying to make a match
506 if(matching)
507 {
508 *patternIndex=startPatternIndex;
509 done=true;
510 }
511 break;
512 case '?':
513 (*patternIndex)++;
514 matching=(name[nameIndex++]!='\0');
515 break;
516 case '[':
517 (*patternIndex)++;
518 if((character=name[nameIndex++]))
519 {
520 fail=!MatchRange(character,pattern,patternIndex,&matching);
521 }
522 else
523 {
524 matching=false;
525 }
526 break;
527 case '\\':
528 (*patternIndex)++;
529 if((character=pattern[(*patternIndex)++]))
530 {
531 matching=(name[nameIndex++]==character);
532 }
533 else
534 {
535 SetError(errorDescriptions[DANGLING_QUOTE]);
536 fail=true;
537 }
538 break;
539 default:
540 matching=(name[nameIndex++]==pattern[(*patternIndex)++]);
541 break;
542 }
543 }
544 if(!fail)
545 {
546 *haveMatch=matching;
547 }
548 return(!fail);
549 }
550
551 // prototype this because it is mutually recursive with MoveFurther
552 static bool GlobAgainstDirectory(const char *pattern,UINT32 patternIndex,char *pathBuffer,UINT32 pathIndex,GLOB_DATA *data);
553
MoveFurther(const char * pattern,UINT32 patternIndex,char * pathBuffer,UINT32 pathIndex,GLOB_DATA * data)554 static bool MoveFurther(const char *pattern,UINT32 patternIndex,char *pathBuffer,UINT32 pathIndex,GLOB_DATA *data)
555 // this attempts to add to pathBuffer by reading from pattern at patternIndex
556 // pattern data is added to pathBuffer so that all data containing no meta characters is copied
557 // up to the first PATH_SEP before data that does contain meta characters
558 // if there is a problem, SetError, and return false
559 {
560 bool
561 fail;
562 bool
563 done;
564 UINT32
565 lastPatternIndex,
566 lastPathIndex;
567 bool
568 nextSpecial;
569 bool
570 hadMeta; // tells if locally, we have seen any meta characters yet
571
572 fail=done=nextSpecial=hadMeta=false;
573 lastPatternIndex=patternIndex;
574 lastPathIndex=pathIndex;
575 while(!done&&pathIndex<MAXPATHLEN)
576 {
577 // copy all of pattern that contains no meta characters until the next PATH_SEP into pathBuffer
578 pathBuffer[pathIndex]=pattern[patternIndex];
579 switch(pattern[patternIndex])
580 {
581 case '\0': // ran out of characters of the pattern, so this is not path information
582 if((!hadMeta&&data->dontGlobLast)||(!data->hadMeta&&data->passNoMeta)) // if no meta characters, then this IS the path (assuming we dont glob the last one)
583 {
584 pathBuffer[pathIndex]='\0'; // terminate the current path
585 fail=!AddStringToList(pathBuffer,&data->paths,&data->numPaths); // add this to the list
586 }
587 done=true;
588 break;
589 case '*': // meta character (this means we stop here)
590 case '?':
591 case '[':
592 if(nextSpecial)
593 {
594 patternIndex++;
595 pathIndex++;
596 nextSpecial=false;
597 }
598 else
599 {
600 hadMeta=true; // remember locally that there were meta characters
601 data->hadMeta=true; // remember it globally as well
602 done=true;
603 }
604 break;
605 case PATH_SEP:
606 patternIndex++;
607 pathIndex++;
608 lastPatternIndex=patternIndex; // remember these
609 lastPathIndex=pathIndex;
610 nextSpecial=false;
611 break;
612 case '\\':
613 if(nextSpecial)
614 {
615 patternIndex++;
616 pathIndex++;
617 nextSpecial=false;
618 }
619 else
620 {
621 nextSpecial=true;
622 patternIndex++; // skip the \, but do not move forward in path
623 }
624 break;
625 default:
626 patternIndex++;
627 pathIndex++;
628 nextSpecial=false;
629 break;
630 }
631 }
632 if(done) // we got something
633 {
634 if(!((!hadMeta&&data->dontGlobLast)||(!data->hadMeta&&data->passNoMeta))) // if we aren't passing this verbatim, then glob against it
635 {
636 patternIndex=lastPatternIndex; // move back
637 pathIndex=lastPathIndex;
638 pathBuffer[pathIndex]='\0'; // terminate the current path
639 fail=!GlobAgainstDirectory(pattern,patternIndex,pathBuffer,pathIndex,data); // expand the pattern at the end of the current path
640 }
641 }
642 else // path was about to overflow output
643 {
644 SetError(errorDescriptions[PATH_TOO_LONG]);
645 fail=true;
646 }
647 return(!fail);
648
649 }
650
GlobAgainstDirectory(const char * pattern,UINT32 patternIndex,char * pathBuffer,UINT32 pathIndex,GLOB_DATA * data)651 static bool GlobAgainstDirectory(const char *pattern,UINT32 patternIndex,char *pathBuffer,UINT32 pathIndex,GLOB_DATA *data)
652 // try pattern against all files at pathBuffer, adding each that matches completely, and calling
653 // move further on each one
654 // if there is some sort of problem, SetError, and return false
655 {
656 DIR
657 *directory;
658 struct dirent
659 *entry;
660 bool
661 fail;
662 const char
663 *path;
664 UINT32
665 length;
666 UINT32
667 newIndex;
668 bool
669 haveMatch;
670
671 fail=false;
672 if(pathBuffer[0])
673 {
674 path=pathBuffer; // point at the passed path if one given
675 }
676 else
677 {
678 path="./"; // if passed path is empty, it means current directory
679 }
680 if((directory=opendir(path))) // if it does not open, assume it is because path did not point to something valid
681 {
682 while(!fail&&(entry=readdir(directory)))
683 {
684 newIndex=patternIndex;
685 if(entry->d_name[0]!='.'||pattern[patternIndex]=='.') // the first . must be matched exactly
686 {
687 if(MatchName(entry->d_name,pattern,&newIndex,&haveMatch))
688 {
689 if(haveMatch) // was there a match?
690 {
691 length=strlen(entry->d_name);
692 if(pathIndex+length<MAXPATHLEN) // append to path (if there's room)
693 {
694 strcpy(&(pathBuffer[pathIndex]),entry->d_name);
695 if(pattern[newIndex]) // see if there's more pattern left
696 {
697 fail=!MoveFurther(pattern,newIndex,pathBuffer,pathIndex+length,data);
698 }
699 else
700 {
701 fail=!AddStringToList(pathBuffer,&data->paths,&data->numPaths);
702 }
703 }
704 else
705 {
706 SetError(errorDescriptions[PATH_TOO_LONG]);
707 fail=true;
708 }
709 }
710 }
711 else
712 {
713 fail=true;
714 }
715 }
716 }
717 closedir(directory);
718 }
719 return(!fail);
720 }
721
Glob(const char * path,const char * pattern,bool dontGlobLast,bool passNoMeta,UINT32 * numElements)722 char **Glob(const char *path,const char *pattern,bool dontGlobLast,bool passNoMeta,UINT32 *numElements)
723 // glob pattern into an array of paths, and return it
724 // if there is a problem, SetError, and return NULL
725 // path is the initial path to begin globbing at if pattern
726 // does not specify an absolute path
727 // NOTE: the returned paths must be deleted by a call to
728 // FreeStringList
729 // if dontGlobLast is true, the last element of pattern (if it contains
730 // no meta characters) will not be checked against its directory,
731 // but instead will be taken to exist, and will be placed into the output
732 // this is useful when trying to save to a globbed path, and the last element
733 // of the path is the name of a file which does not necessarily exist yet.
734 // if passNoMeta is true, any pattern that contains no wild card
735 // characters will be placed into the output without being checked
736 {
737 GLOB_DATA
738 data;
739 char
740 newPattern[MAXPATHLEN+1]; // tilde expand the pattern into here
741 char
742 pathBuffer[MAXPATHLEN+1]; // this is the place to build the output path
743 UINT32
744 length;
745
746 *numElements=0; // no elements yet
747 if((data.paths=NewStringList()))
748 {
749 data.numPaths=0; // no paths so far
750 data.dontGlobLast=dontGlobLast; // set up flags that tell how to glob
751 data.passNoMeta=passNoMeta;
752 data.hadMeta=false; // no meta characters seen so far
753 if(ExpandTildes(pattern,&newPattern[0],MAXPATHLEN+1)) // expand tilde sequences in pattern, write output into newPattern
754 {
755 if(newPattern[0]==PATH_SEP)
756 {
757 pathBuffer[0]='\0'; // pattern is absolute, so ignore given path
758 }
759 else
760 {
761 strncpy(pathBuffer,path,MAXPATHLEN); // use the passed path as a prefix to the glob
762 }
763 length=strlen(pathBuffer);
764 if(length&&length<MAXPATHLEN) // if we have something, check to see if it needs a PATH_SEP
765 {
766 if(pathBuffer[length-1]!=PATH_SEP) // add separator to end of path
767 {
768 pathBuffer[length++]=PATH_SEP;
769 pathBuffer[length]='\0';
770 }
771 }
772 if(MoveFurther(&newPattern[0],0,pathBuffer,length,&data))
773 {
774 if(data.numPaths)
775 {
776 SortStringList(data.paths,data.numPaths);
777 *numElements=data.numPaths;
778 return(data.paths);
779 }
780 else
781 {
782 SetError(errorDescriptions[MATCH_FAILED]);
783 }
784 }
785 }
786 FreeStringList(data.paths);
787 }
788 return(NULL);
789 }
790
GlobAll(const char * path,UINT32 * numElements)791 char **GlobAll(const char *path,UINT32 *numElements)
792 // return a sorted list of all of the directories/files at the end of path
793 // path is not expanded in any way.
794 // NOTE: the list will contain ONLY the last component of the path
795 // if there is a problem, SetError, and return NULL
796 // NOTE: the returned list must be later deleted by a call to
797 // FreeStringList
798 {
799 char
800 **files;
801 DIR
802 *directory;
803 struct dirent
804 *entry;
805 bool
806 fail;
807
808 fail=false;
809 *numElements=0; // no paths so far
810 if((files=NewStringList()))
811 {
812 if((directory=opendir(path))) // if it does not open, assume it is because path did not point to something valid, and return NULL
813 {
814 while(!fail&&(entry=readdir(directory)))
815 {
816 fail=!AddStringToList(entry->d_name,&files,numElements);
817 }
818 closedir(directory);
819 if(!fail)
820 {
821 SortStringList(files,*numElements);
822 return(files);
823 }
824 }
825 else
826 {
827 SetStdCLibError();
828 }
829 FreeStringList(files);
830 }
831 *numElements=0;
832 return(NULL);
833 }
834