xref: /openbsd-src/gnu/usr.bin/cvs/os2/filesubr.c (revision 50bf276cd1c7e20f1eda64a5e63e0fae39e12a95)
1 /* filesubr.c --- subroutines for dealing with files under OS/2
2    Jim Blandy <jimb@cyclic.com> and Karl Fogel <kfogel@cyclic.com>
3 
4    This file is part of GNU CVS.
5 
6    GNU CVS is free software; you can redistribute it and/or modify it
7    under the terms of the GNU General Public License as published by the
8    Free Software Foundation; either version 2, or (at your option) any
9    later version.
10 
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15 
16    You should have received a copy of the GNU General Public License
17    along with this program; if not, write to the Free Software
18    Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */
19 
20 /* These functions were moved out of subr.c because they need different
21    definitions under operating systems (like, say, Windows NT) with different
22    file system semantics.  */
23 
24 #include <io.h>
25 #define INCL_DOSFILEMGR   /* File Manager values */
26 #define INCL_DOSERRORS
27 #include <os2.h>
28 
29 #include "cvs.h"
30 
31 static int deep_remove_dir PROTO((const char *path));
32 
33 /*
34  * Copies "from" to "to".
35  */
36 void
37 copy_file (from, to)
38     const char *from;
39     const char *to;
40 {
41     struct stat sb;
42     struct utimbuf t;
43     int fdin, fdout;
44 
45     if (trace)
46 #ifdef SERVER_SUPPORT
47 	(void) fprintf (stderr, "%c-> copy(%s,%s)\n",
48 			(server_active) ? 'S' : ' ', from, to);
49 #else
50 	(void) fprintf (stderr, "-> copy(%s,%s)\n", from, to);
51 #endif
52     if (noexec)
53 	return;
54 
55     if ((fdin = open (from, O_RDONLY | O_BINARY)) < 0)
56 	error (1, errno, "cannot open %s for copying", from);
57     if (fstat (fdin, &sb) < 0)
58 	error (1, errno, "cannot fstat %s", from);
59     if ((fdout = open (to, O_CREAT | O_WRONLY | O_TRUNC | O_BINARY,
60 					   (int) sb.st_mode & 07777)) < 0)
61 	error (1, errno, "cannot create %s for copying", to);
62     if (sb.st_size > 0)
63     {
64 	char buf[BUFSIZ];
65 	int n;
66 
67 	for (;;)
68 	{
69 	    n = read (fdin, buf, sizeof(buf));
70 	    if (n == -1)
71 	    {
72 #ifdef EINTR
73 		if (errno == EINTR)
74 		    continue;
75 #endif
76 		error (1, errno, "cannot read file %s for copying", from);
77 	    }
78             else if (n == 0)
79 		break;
80 
81 	    if (write(fdout, buf, n) != n) {
82 		error (1, errno, "cannot write file %s for copying", to);
83 	    }
84 	}
85 
86 #ifdef HAVE_FSYNC
87 	if (fsync (fdout))
88 	    error (1, errno, "cannot fsync file %s after copying", to);
89 #endif
90     }
91 
92     if (close (fdin) < 0)
93 	error (0, errno, "cannot close %s", from);
94     if (close (fdout) < 0)
95 	error (1, errno, "cannot close %s", to);
96 
97     /* now, set the times for the copied file to match those of the original */
98     memset ((char *) &t, 0, sizeof (t));
99     t.actime = sb.st_atime;
100     t.modtime = sb.st_mtime;
101     (void) utime (to, &t);
102 }
103 
104 /*
105  * link a file, if possible.  Warning: the Windows NT version of this
106  * function just copies the file, so only use this function in ways
107  * that can deal with either a link or a copy.
108  */
109 int
110 link_file (from, to)
111     const char *from;
112     const char *to;
113 {
114     copy_file (from, to);
115     return 0;
116 }
117 
118 /* FIXME-krp: these functions would benefit from caching the char * &
119    stat buf.  */
120 
121 /*
122  * Returns non-zero if the argument file is a directory, or is a symbolic
123  * link which points to a directory.
124  */
125 int
126 isdir (file)
127     const char *file;
128 {
129     struct stat sb;
130 
131     if (stat (file, &sb) < 0)
132 	return (0);
133     return (S_ISDIR (sb.st_mode));
134 }
135 
136 /*
137  * Returns non-zero if the argument file is a symbolic link.
138  */
139 int
140 islink (file)
141     const char *file;
142 {
143 #ifdef S_ISLNK
144     struct stat sb;
145 
146     if (lstat (file, &sb) < 0)
147 	return (0);
148     return (S_ISLNK (sb.st_mode));
149 #else
150     return (0);
151 #endif
152 }
153 
154 /*
155  * Returns non-zero if the argument file exists.
156  */
157 int
158 isfile (file)
159     const char *file;
160 {
161     struct stat sb;
162 
163     if (stat (file, &sb) < 0)
164 	return (0);
165     return (1);
166 }
167 
168 /*
169  * Returns non-zero if the argument file is readable.
170  * XXX - must be careful if "cvs" is ever made setuid!
171  */
172 int
173 isreadable (file)
174     const char *file;
175 {
176     return (access (file, R_OK) != -1);
177 }
178 
179 /*
180  * Returns non-zero if the argument file is writable
181  * XXX - muct be careful if "cvs" is ever made setuid!
182  */
183 int
184 iswritable (file)
185     const char *file;
186 {
187     return (access (file, W_OK) != -1);
188 }
189 
190 /*
191  * Returns non-zero if the argument file is accessable according to
192  * mode.  If compiled with SETXID_SUPPORT also works if cvs has setxid
193  * bits set.
194  */
195 int
196 isaccessible (file, mode)
197     const char *file;
198     const int mode;
199 {
200     return access(file, mode) == 0;
201 }
202 
203 
204 /*
205  * Open a file and die if it fails
206  */
207 FILE *
208 open_file (name, mode)
209     const char *name;
210     const char *mode;
211 {
212     FILE *fp;
213 
214     if ((fp = fopen (name, mode)) == NULL)
215 	error (1, errno, "cannot open %s", name);
216     return (fp);
217 }
218 
219 /*
220  * Make a directory and die if it fails
221  */
222 void
223 make_directory (name)
224     const char *name;
225 {
226     struct stat buf;
227 
228     if (stat (name, &buf) == 0 && (!S_ISDIR (buf.st_mode)))
229 	    error (0, 0, "%s already exists but is not a directory", name);
230     if (!noexec && mkdir (name) < 0)
231 	error (1, errno, "cannot make directory %s", name);
232 }
233 
234 /*
235  * Make a path to the argument directory, printing a message if something
236  * goes wrong.
237  */
238 void
239 make_directories (name)
240     const char *name;
241 {
242     char *cp;
243 
244     if (noexec)
245 	return;
246 
247     if (mkdir (name) == 0 || errno == EACCESS)
248 	return;
249     if (! existence_error (errno))
250     {
251 	error (0, errno, "cannot make path to %s", name);
252 	return;
253     }
254     if ((cp = strrchr (name, '/')) == NULL)
255 	return;
256     *cp = '\0';
257     make_directories (name);
258     *cp++ = '/';
259     if (*cp == '\0')
260 	return;
261     (void) mkdir (name);
262 }
263 
264 /* Create directory NAME if it does not already exist; fatal error for
265    other errors.  Returns 0 if directory was created; 1 if it already
266    existed.  */
267 int
268 mkdir_if_needed (name)
269     char *name;
270 {
271     if (mkdir (name) < 0)
272     {
273 	/* Now, let me get this straight.  In IBM C/C++
274 	   under OS/2, the error string for EEXIST is:
275 
276 	       "The file already exists",
277 
278 	   and the error string for EACCESS is:
279 
280 	       "The file or directory specified is read-only".
281 
282 	   Nonetheless, mkdir() will set EACCESS if the
283 	   directory *exists*, according both to the
284 	   documentation and its actual behavior.
285 
286 	   I'm sure that this made sense, to someone,
287 	   somewhere, sometime.  Just not me, here, now.  */
288 	if (errno != EEXIST
289 #ifdef EACCESS
290 	    && errno != EACCESS
291 #endif
292 	    )
293 	    error (1, errno, "cannot make directory %s", name);
294 	return 1;
295     }
296     return 0;
297 }
298 
299 /*
300  * Change the mode of a file, either adding write permissions, or removing
301  * all write permissions.  Adding write permissions honors the current umask
302  * setting.
303  */
304 void
305 xchmod (fname, writable)
306     char *fname;
307     int writable;
308 {
309     char *attrib_cmd;
310     char *attrib_option;
311     char *whole_cmd;
312     char *p;
313     char *q;
314 
315     if (!isfile (fname))
316 	return ENOENT;
317 
318     attrib_cmd = "attrib "; /* No, really? */
319 
320     if (writable)
321         attrib_option = "-r ";  /* make writeable */
322     else
323         attrib_option = "+r ";  /* make read-only */
324 
325     whole_cmd = xmalloc (strlen (attrib_cmd)
326                          + strlen (attrib_option)
327                          + strlen (fname)
328                          + 1);
329 
330     strcpy (whole_cmd, attrib_cmd);
331     strcat (whole_cmd, attrib_option);
332 
333     /* Copy fname to the end of whole_cmd, translating / to \.
334 	   Attrib doesn't take / but many parts of CVS rely
335        on being able to use it.  */
336     p = whole_cmd + strlen (whole_cmd);
337     q = fname;
338     while (*q)
339     {
340 	if (*q == '/')
341 	    *p++ = '\\';
342 	else
343 	    *p++ = *q;
344 	++q;
345     }
346     *p = '\0';
347 
348     system (whole_cmd);
349     free (whole_cmd);
350 }
351 
352 
353 /* Read the value of a symbolic link.
354    Under OS/2, this function always returns EINVAL.  */
355 int
356 readlink (char *path, char *buf, int buf_size)
357 {
358     errno = EINVAL;
359     return -1;
360 }
361 
362 /*
363  * unlink a file, if possible.
364  */
365 int
366 unlink_file (f)
367     const char *f;
368 {
369     if (trace)
370 #ifdef SERVER_SUPPORT
371 	(void) fprintf (stderr, "%c-> unlink(%s)\n",
372 			(server_active) ? 'S' : ' ', f);
373 #else
374 	(void) fprintf (stderr, "-> unlink(%s)\n", f);
375 #endif
376     if (noexec)
377 	return (0);
378 
379    /* Win32 unlink is stupid - it fails if the file is read-only.
380     * OS/2 is similarly stupid.  It does have a remove() function,
381     * but the documentation does not make clear why remove() is or
382     * isn't preferable to unlink().  I'll use unlink() because the
383     * name is closer to our interface, what the heck.  Also, we know
384     * unlink()'s error code when trying to remove a directory.
385     */
386     xchmod (f, 1);
387     return (unlink (f));
388 }
389 
390 /*
391  * Unlink a file or dir, if possible.  If it is a directory do a deep
392  * removal of all of the files in the directory.  Return -1 on error
393  * (in which case errno is set).
394  */
395 int
396 unlink_file_dir (f)
397     const char *f;
398 {
399     if (trace)
400 #ifdef SERVER_SUPPORT
401 	(void) fprintf (stderr, "%c-> unlink_file_dir(%s)\n",
402 			(server_active) ? 'S' : ' ', f);
403 #else
404 	(void) fprintf (stderr, "-> unlink_file_dir(%s)\n", f);
405 #endif
406     if (noexec)
407 	return (0);
408 
409     if (unlink_file (f) != 0)
410     {
411 	/* under OS/2, unlink returns EACCESS if the path
412 	   is a directory.  */
413         if (errno == EACCESS)
414                 return deep_remove_dir (f);
415         else
416 		/* The file wasn't a directory and some other
417 		 * error occured
418 		 */
419                 return -1;
420     }
421     /* We were able to remove the file from the disk */
422     return 0;
423 }
424 
425 /* Remove a directory and everything it contains.  Returns 0 for
426  * success, -1 for failure (in which case errno is set).
427  */
428 
429 static int
430 deep_remove_dir (path)
431     const char *path;
432 {
433     DIR		  *dirp;
434     struct dirent *dp;
435     char	   buf[PATH_MAX];
436 
437     if ( rmdir (path) != 0 && errno == EACCESS )
438     {
439 	if ((dirp = opendir (path)) == NULL)
440 	    /* If unable to open the directory return
441 	     * an error
442 	     */
443 	    return -1;
444 
445 	while ((dp = readdir (dirp)) != NULL)
446 	{
447 	    if (strcmp (dp->d_name, ".") == 0 ||
448 			strcmp (dp->d_name, "..") == 0)
449 		continue;
450 
451 	    sprintf (buf, "%s/%s", path, dp->d_name);
452 
453 	    if (unlink_file (buf) != 0 )
454 	    {
455 		if (errno == EACCES)
456 		{
457 		    if (deep_remove_dir (buf))
458 		    {
459 			closedir (dirp);
460 			return -1;
461 		    }
462 		}
463 		else
464 		{
465 		    /* buf isn't a directory, or there are
466 		     * some sort of permision problems
467 		     */
468 		    closedir (dirp);
469 		    return -1;
470 		}
471 	    }
472 	}
473 	closedir (dirp);
474 	return rmdir (path);
475     }
476     /* Was able to remove the directory return 0 */
477     return 0;
478 }
479 
480 
481 /*
482  * Rename a file and die if it fails
483  */
484 void
485 rename_file (from, to)
486     const char *from;
487     const char *to;
488 {
489     if (trace)
490 #ifdef SERVER_SUPPORT
491 	(void) fprintf (stderr, "%c-> rename(%s,%s)\n",
492 			(server_active) ? 'S' : ' ', from, to);
493 #else
494 	(void) fprintf (stderr, "-> rename(%s,%s)\n", from, to);
495 #endif
496     if (noexec)
497 	return;
498 
499     unlink_file (to);
500     if (rename (from, to) != 0)
501 	error (1, errno, "cannot rename file %s to %s", from, to);
502 }
503 
504 
505 /* Read NCHARS bytes from descriptor FD into BUF.
506    Return the number of characters successfully read.
507    The number returned is always NCHARS unless end-of-file or error.  */
508 static size_t
509 block_read (fd, buf, nchars)
510     int fd;
511     char *buf;
512     size_t nchars;
513 {
514     char *bp = buf;
515     size_t nread;
516 
517     do
518     {
519 	nread = read (fd, bp, nchars);
520 	if (nread == (size_t)-1)
521 	{
522 #ifdef EINTR
523 	    if (errno == EINTR)
524 		continue;
525 #endif
526 	    return (size_t)-1;
527 	}
528 
529 	if (nread == 0)
530 	    break;
531 
532 	bp += nread;
533 	nchars -= nread;
534     } while (nchars != 0);
535 
536     return bp - buf;
537 }
538 
539 
540 /*
541  * Compare "file1" to "file2". Return non-zero if they don't compare exactly.
542  */
543 int
544 xcmp (file1, file2)
545     const char *file1;
546     const char *file2;
547 {
548     char *buf1, *buf2;
549     struct stat sb1, sb2;
550     int fd1, fd2;
551     int ret;
552 
553     if ((fd1 = open (file1, O_RDONLY | O_BINARY)) < 0)
554 	error (1, errno, "cannot open file %s for comparing", file1);
555     if ((fd2 = open (file2, O_RDONLY | O_BINARY)) < 0)
556 	error (1, errno, "cannot open file %s for comparing", file2);
557     if (fstat (fd1, &sb1) < 0)
558 	error (1, errno, "cannot fstat %s", file1);
559     if (fstat (fd2, &sb2) < 0)
560 	error (1, errno, "cannot fstat %s", file2);
561 
562     /* A generic file compare routine might compare st_dev & st_ino here
563        to see if the two files being compared are actually the same file.
564        But that won't happen in CVS, so we won't bother. */
565 
566     if (sb1.st_size != sb2.st_size)
567 	ret = 1;
568     else if (sb1.st_size == 0)
569 	ret = 0;
570     else
571     {
572 	/* FIXME: compute the optimal buffer size by computing the least
573 	   common multiple of the files st_blocks field */
574 	size_t buf_size = 8 * 1024;
575 	size_t read1;
576 	size_t read2;
577 
578 	buf1 = xmalloc (buf_size);
579 	buf2 = xmalloc (buf_size);
580 
581 	do
582 	{
583 	    read1 = block_read (fd1, buf1, buf_size);
584 	    if (read1 == (size_t)-1)
585 		error (1, errno, "cannot read file %s for comparing", file1);
586 
587 	    read2 = block_read (fd2, buf2, buf_size);
588 	    if (read2 == (size_t)-1)
589 		error (1, errno, "cannot read file %s for comparing", file2);
590 
591 	    /* assert (read1 == read2); */
592 
593 	    ret = memcmp(buf1, buf2, read1);
594 	} while (ret == 0 && read1 == buf_size);
595 
596 	free (buf1);
597 	free (buf2);
598     }
599 
600     (void) close (fd1);
601     (void) close (fd2);
602     return (ret);
603 }
604 
605 
606 /* The equivalence class mapping for filenames.
607    OS/2 filenames are case-insensitive, but case-preserving.  Both /
608    and \ are path element separators.
609    Thus, this table maps both upper and lower case to lower case, and
610    both / and \ to /.
611 
612    Much thanks to Jim Blandy, who already invented this wheel in the
613    Windows NT port. */
614 
615 #if 0
616 main ()
617 {
618   int c;
619 
620   for (c = 0; c < 256; c++)
621     {
622       int t;
623 
624       if (c == '\\')
625         t = '/';
626       else
627         t = tolower (c);
628 
629       if ((c & 0x7) == 0x0)
630          printf ("    ");
631       printf ("0x%02x,", t);
632       if ((c & 0x7) == 0x7)
633          putchar ('\n');
634       else if ((c & 0x7) == 0x3)
635          putchar (' ');
636     }
637 }
638 #endif
639 
640 
641 unsigned char
642 OS2_filename_classes[] =
643 {
644     0x00,0x01,0x02,0x03, 0x04,0x05,0x06,0x07,
645     0x08,0x09,0x0a,0x0b, 0x0c,0x0d,0x0e,0x0f,
646     0x10,0x11,0x12,0x13, 0x14,0x15,0x16,0x17,
647     0x18,0x19,0x1a,0x1b, 0x1c,0x1d,0x1e,0x1f,
648     0x20,0x21,0x22,0x23, 0x24,0x25,0x26,0x27,
649     0x28,0x29,0x2a,0x2b, 0x2c,0x2d,0x2e,0x2f,
650     0x30,0x31,0x32,0x33, 0x34,0x35,0x36,0x37,
651     0x38,0x39,0x3a,0x3b, 0x3c,0x3d,0x3e,0x3f,
652     0x40,0x61,0x62,0x63, 0x64,0x65,0x66,0x67,
653     0x68,0x69,0x6a,0x6b, 0x6c,0x6d,0x6e,0x6f,
654     0x70,0x71,0x72,0x73, 0x74,0x75,0x76,0x77,
655     0x78,0x79,0x7a,0x5b, 0x2f,0x5d,0x5e,0x5f,
656     0x60,0x61,0x62,0x63, 0x64,0x65,0x66,0x67,
657     0x68,0x69,0x6a,0x6b, 0x6c,0x6d,0x6e,0x6f,
658     0x70,0x71,0x72,0x73, 0x74,0x75,0x76,0x77,
659     0x78,0x79,0x7a,0x7b, 0x7c,0x7d,0x7e,0x7f,
660     0x80,0x81,0x82,0x83, 0x84,0x85,0x86,0x87,
661     0x88,0x89,0x8a,0x8b, 0x8c,0x8d,0x8e,0x8f,
662     0x90,0x91,0x92,0x93, 0x94,0x95,0x96,0x97,
663     0x98,0x99,0x9a,0x9b, 0x9c,0x9d,0x9e,0x9f,
664     0xa0,0xa1,0xa2,0xa3, 0xa4,0xa5,0xa6,0xa7,
665     0xa8,0xa9,0xaa,0xab, 0xac,0xad,0xae,0xaf,
666     0xb0,0xb1,0xb2,0xb3, 0xb4,0xb5,0xb6,0xb7,
667     0xb8,0xb9,0xba,0xbb, 0xbc,0xbd,0xbe,0xbf,
668     0xc0,0xc1,0xc2,0xc3, 0xc4,0xc5,0xc6,0xc7,
669     0xc8,0xc9,0xca,0xcb, 0xcc,0xcd,0xce,0xcf,
670     0xd0,0xd1,0xd2,0xd3, 0xd4,0xd5,0xd6,0xd7,
671     0xd8,0xd9,0xda,0xdb, 0xdc,0xdd,0xde,0xdf,
672     0xe0,0xe1,0xe2,0xe3, 0xe4,0xe5,0xe6,0xe7,
673     0xe8,0xe9,0xea,0xeb, 0xec,0xed,0xee,0xef,
674     0xf0,0xf1,0xf2,0xf3, 0xf4,0xf5,0xf6,0xf7,
675     0xf8,0xf9,0xfa,0xfb, 0xfc,0xfd,0xfe,0xff,
676 };
677 
678 /* Like strcmp, but with the appropriate tweaks for file names.
679    Under OS/2, filenames are case-insensitive but case-preserving, and
680    both \ and / are path element separators.  */
681 int
682 fncmp (const char *n1, const char *n2)
683 {
684     while (*n1 && *n2
685            && (OS2_filename_classes[(unsigned char) *n1]
686 	       == OS2_filename_classes[(unsigned char) *n2]))
687         n1++, n2++;
688     return (OS2_filename_classes[(unsigned char) *n1]
689             - OS2_filename_classes[(unsigned char) *n2]);
690 }
691 
692 /* Fold characters in FILENAME to their canonical forms.
693    If FOLD_FN_CHAR is not #defined, the system provides a default
694    definition for this.  */
695 void
696 fnfold (char *filename)
697 {
698     while (*filename)
699     {
700         *filename = FOLD_FN_CHAR (*filename);
701 	filename++;
702     }
703 }
704 
705 
706 /* Generate a unique temporary filename.  Returns a pointer to a newly
707    malloc'd string containing the name.  Returns successfully or not at
708    all.  */
709 char *
710 cvs_temp_name ()
711 {
712     char value[L_tmpnam + 1];
713     char *retval;
714 
715     /* FIXME: Does OS/2 have some equivalent to TMPDIR?  */
716     retval = tmpnam (value);
717     if (retval == NULL)
718 	error (1, errno, "cannot generate temporary filename");
719     return xstrdup (retval);
720 }
721 
722 /* Return non-zero iff FILENAME is absolute.
723    Trivial under Unix, but more complicated under other systems.  */
724 int
725 isabsolute (filename)
726     const char *filename;
727 {
728     return (ISDIRSEP (filename[0])
729             || (filename[0] != '\0'
730                 && filename[1] == ':'
731                 && ISDIRSEP (filename[2])));
732 }
733 
734 /* Return a pointer into PATH's last component.  */
735 char *
736 last_component (char *path)
737 {
738     char *scan;
739     char *last = 0;
740 
741     for (scan = path; *scan; scan++)
742         if (ISDIRSEP (*scan))
743 	    last = scan;
744 
745     if (last)
746         return last + 1;
747     else
748         return path;
749 }
750 
751 
752 /* Read data from INFILE, and copy it to OUTFILE.
753    Open INFILE using INFLAGS, and OUTFILE using OUTFLAGS.
754    This is useful for converting between CRLF and LF line formats.  */
755 void
756 convert_file (char *infile,  int inflags,
757 	      char *outfile, int outflags)
758 {
759     int infd, outfd;
760     char buf[8192];
761     int len;
762 
763     if ((infd = open (infile, inflags, S_IREAD | S_IWRITE)) < 0)
764         error (1, errno, "couldn't read %s", infile);
765     if ((outfd = open (outfile, outflags, S_IREAD | S_IWRITE)) < 0)
766         error (1, errno, "couldn't write %s", outfile);
767 
768     while ((len = read (infd, buf, sizeof (buf))) > 0)
769         if (write (outfd, buf, len) < 0)
770 	    error (1, errno, "error writing %s", outfile);
771     if (len < 0)
772         error (1, errno, "error reading %s", infile);
773 
774     if (close (outfd) < 0)
775         error (0, errno, "warning: couldn't close %s", outfile);
776     if (close (infd) < 0)
777         error (0, errno, "warning: couldn't close %s", infile);
778 }
779 
780 /* Return the home directory.  Returns a pointer to storage
781    managed by this function or its callees (currently getenv).  */
782 char *
783 get_homedir ()
784 {
785     return getenv ("HOME");
786 }
787 
788 /* See cvs.h for description.  */
789 void
790 expand_wild (argc, argv, pargc, pargv)
791     int argc;
792     char **argv;
793     int *pargc;
794     char ***pargv;
795 {
796     int i;
797     int new_argc;
798     char **new_argv;
799     /* Allocated size of new_argv.  We arrange it so there is always room for
800 	   one more element.  */
801     int max_new_argc;
802 
803     new_argc = 0;
804     /* Add one so this is never zero.  */
805     max_new_argc = argc + 1;
806     new_argv = (char **) xmalloc (max_new_argc * sizeof (char *));
807     for (i = 0; i < argc; ++i)
808     {
809 	HDIR          FindHandle = 0x0001;
810 	FILEFINDBUF3  FindBuffer;
811 	ULONG         FindCount = 1;
812 	APIRET        rc;          /* Return code */
813 #define ALL_FILES (FILE_ARCHIVED|FILE_DIRECTORY|FILE_SYSTEM|FILE_HIDDEN|FILE_READONLY)
814 
815 	rc = DosFindFirst(argv[i],               /* File pattern */
816 			  &FindHandle,           /* Directory search handle */
817 			  ALL_FILES,             /* Search attribute */
818 			  (PVOID) &FindBuffer,   /* Result buffer */
819 			  sizeof(FindBuffer),    /* Result buffer length */
820 			  &FindCount,            /* Number of entries to find */
821 			  FIL_STANDARD);	 /* Return level 1 file info */
822 
823 	if (rc != 0)
824 	{
825 	    if (rc == ERROR_FILE_NOT_FOUND)
826 	    {
827 		/* No match.  The file specified didn't contain a wildcard (in which case
828 		   we clearly should return it unchanged), or it contained a wildcard which
829 		   didn't match (in which case it might be better for it to be an error,
830 		   but we don't try to do that).  */
831 		new_argv [new_argc++] = xstrdup (argv[i]);
832 		if (new_argc == max_new_argc)
833 		{
834 		    max_new_argc *= 2;
835 		    new_argv = xrealloc (new_argv, max_new_argc * sizeof (char *));
836 		}
837 	    }
838 	    else
839 	    {
840 		error (1, rc, "cannot find %s", argv[i]);
841 	    }
842 	}
843 	else
844 	{
845 	    while (1)
846 	    {
847 		/*
848 		 * Don't match ".", "..", and files starting with '.'
849 		 * (unless pattern also starts with '.').  This is
850 		 * (more or less) what standard Unix globbing does.
851 		 */
852 		if ((strcmp(FindBuffer.achName, ".") != 0) &&
853 		    (strcmp(FindBuffer.achName, "..") != 0) &&
854 		    ((argv[i][0] == '.') || (FindBuffer.achName[0] != '.')))
855 		{
856 		    new_argv [new_argc++] = xstrdup (FindBuffer.achName);
857 		    if (new_argc == max_new_argc)
858 		    {
859 			max_new_argc *= 2;
860 			new_argv = xrealloc (new_argv, max_new_argc * sizeof (char *));
861 		    }
862 		}
863 
864 		rc = DosFindNext(FindHandle,
865                      (PVOID) &FindBuffer,
866                      sizeof(FindBuffer),
867                      &FindCount);
868 		if (rc == ERROR_NO_MORE_FILES)
869 		    break;
870 		else if (rc != NO_ERROR)
871 		    error (1, rc, "cannot find %s", argv[i]);
872 	    }
873 	    rc = DosFindClose(FindHandle);
874 	    if (rc != 0)
875 		error (1, rc, "cannot close %s", argv[i]);
876 	}
877     }
878     *pargc = new_argc;
879     *pargv = new_argv;
880 }
881