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