xref: /openbsd/gnu/usr.bin/cvs/vms/filesubr.c (revision 3cab2bb3)
1 /* filesubr.c --- subroutines for dealing with files
2    Gratuitously adapted toward VMS quirks.
3 
4    Jim Blandy <jimb@cyclic.com>
5    Benjamin J. Lee <benjamin@cyclic.com>
6 
7    This file is part of GNU CVS.
8 
9    GNU CVS is free software; you can redistribute it and/or modify it
10    under the terms of the GNU General Public License as published by the
11    Free Software Foundation; either version 2, or (at your option) any
12    later version.
13 
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License for more details.  */
18 
19 #include "cvs.h"
20 
21 static int deep_remove_dir PROTO((const char *path));
22 
23 /*
24  * Copies "from" to "to".
25  */
26 void
27 copy_file (from_file, to_file)
28     const char *from_file;
29     const char *to_file;
30 {
31     char from[PATH_MAX], to[PATH_MAX];
32     struct stat sb;
33     struct utimbuf t;
34     int fdin, fdout;
35 
36     /* Prefer local relative paths to files at expense of logical name
37        access to files. */
38 
39     if (isabsolute(from_file))
40       strcpy(from, from_file);
41     else
42       sprintf(from, "./%s", from_file);
43 
44     if (isabsolute(to_file))
45       strcpy(to, to_file);
46     else
47       sprintf(to, "./%s", to_file);
48 
49     if (trace)
50 #ifdef SERVER_SUPPORT
51 	(void) fprintf (stderr, "%c-> copy(%s,%s)\n",
52 			(server_active) ? 'S' : ' ', from, to);
53 #else
54 	(void) fprintf (stderr, "-> copy(%s,%s)\n", from, to);
55 #endif
56     if (noexec)
57 	return;
58 
59     if ((fdin = open (from, O_RDONLY)) < 0)
60 	error (1, errno, "cannot open %s for copying", from);
61     if (fstat (fdin, &sb) < 0)
62 	error (1, errno, "cannot fstat %s", from);
63     if ((fdout = creat (to, (int) sb.st_mode & 07777)) < 0)
64 	error (1, errno, "cannot create %s for copying", to);
65     if (sb.st_size > 0)
66     {
67 	char buf[BUFSIZ];
68 	int n;
69 
70 	for (;;)
71 	{
72 	    n = read (fdin, buf, sizeof(buf));
73 	    if (n == -1)
74 	    {
75 #ifdef EINTR
76 		if (errno == EINTR)
77 		    continue;
78 #endif
79 		error (1, errno, "cannot read file %s for copying", from);
80 	    }
81             else if (n == 0)
82 		break;
83 
84 	    if (write(fdout, buf, n) != n) {
85 		error (1, errno, "cannot write file %s for copying", to);
86 	    }
87 	}
88 
89 #ifdef HAVE_FSYNC
90 	if (fsync (fdout))
91 	    error (1, errno, "cannot fsync file %s after copying", to);
92 #endif
93     }
94 
95     if (close (fdin) < 0)
96 	error (0, errno, "cannot close %s", from);
97     if (close (fdout) < 0)
98 	error (1, errno, "cannot close %s", to);
99 
100     /* now, set the times for the copied file to match those of the original */
101     memset ((char *) &t, 0, sizeof (t));
102     t.actime = sb.st_atime;
103     t.modtime = sb.st_mtime;
104     (void) utime (to, &t);
105 }
106 
107 /* FIXME-krp: these functions would benefit from caching the char * &
108    stat buf.  */
109 
110 /*
111  * Returns non-zero if the argument file is a directory, or is a symbolic
112  * link which points to a directory.
113  */
114 int
115 isdir (file)
116     const char *file;
117 {
118     struct stat sb;
119 
120     if (stat (file, &sb) < 0)
121 	return (0);
122     return (S_ISDIR (sb.st_mode));
123 }
124 
125 /*
126  * Returns non-zero if the argument file is a symbolic link.
127  */
128 int
129 islink (file)
130     const char *file;
131 {
132 #ifdef S_ISLNK
133     struct stat sb;
134 
135     if (lstat (file, &sb) < 0)
136 	return (0);
137     return (S_ISLNK (sb.st_mode));
138 #else
139     return (0);
140 #endif
141 }
142 
143 /*
144  * Returns non-zero if the argument file exists.
145  */
146 int
147 isfile (file)
148     const char *file;
149 {
150     return isaccessible(file, F_OK);
151 }
152 
153 /*
154  * Returns non-zero if the argument file is readable.
155  */
156 int
157 isreadable (file)
158     const char *file;
159 {
160     return isaccessible(file, R_OK);
161 }
162 
163 /*
164  * Returns non-zero if the argument file is writable.
165  */
166 int
167 iswritable (file)
168     const char *file;
169 {
170     return isaccessible(file, W_OK);
171 }
172 
173 /*
174  * Returns non-zero if the argument file is accessable according to
175  * mode.  If compiled with SETXID_SUPPORT also works if cvs has setxid
176  * bits set.
177  */
178 int
179 isaccessible (file, mode)
180     const char *file;
181     const int mode;
182 {
183 #ifdef SETXID_SUPPORT
184     struct stat sb;
185     int umask = 0;
186     int gmask = 0;
187     int omask = 0;
188     int uid;
189 
190     if (stat(file, &sb) == -1)
191 	return 0;
192     if (mode == F_OK)
193 	return 1;
194 
195     uid = geteuid();
196     if (uid == 0)		/* superuser */
197     {
198 	if (mode & X_OK)
199 	    return sb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH);
200 	else
201 	    return 1;
202     }
203 
204     if (mode & R_OK)
205     {
206 	umask |= S_IRUSR;
207 	gmask |= S_IRGRP;
208 	omask |= S_IROTH;
209     }
210     if (mode & W_OK)
211     {
212 	umask |= S_IWUSR;
213 	gmask |= S_IWGRP;
214 	omask |= S_IWOTH;
215     }
216     if (mode & X_OK)
217     {
218 	umask |= S_IXUSR;
219 	gmask |= S_IXGRP;
220 	omask |= S_IXOTH;
221     }
222 
223     if (sb.st_uid == uid)
224 	return (sb.st_mode & umask) == umask;
225     else if (sb.st_gid == getegid())
226 	return (sb.st_mode & gmask) == gmask;
227     else
228 	return (sb.st_mode & omask) == omask;
229 #else
230     return access(file, mode) == 0;
231 #endif
232 }
233 
234 /*
235  * Open a file and die if it fails
236  */
237 FILE *
238 open_file (name, mode)
239     const char *name;
240     const char *mode;
241 {
242     FILE *fp;
243 
244     if ((fp = fopen (name, mode)) == NULL)
245 	error (1, errno, "cannot open %s", name);
246     return (fp);
247 }
248 
249 /*
250  * Make a directory and die if it fails
251  */
252 void
253 make_directory (name)
254     const char *name;
255 {
256     struct stat sb;
257 
258     if (stat (name, &sb) == 0 && (!S_ISDIR (sb.st_mode)))
259 	    error (0, 0, "%s already exists but is not a directory", name);
260     if (!noexec && mkdir (name, 0777) < 0)
261 	error (1, errno, "cannot make directory %s", name);
262 }
263 
264 /*
265  * Make a path to the argument directory, printing a message if something
266  * goes wrong.
267  */
268 void
269 make_directories (name)
270     const char *name;
271 {
272     char *cp;
273 
274     if (noexec)
275 	return;
276 
277     if (mkdir (name, 0777) == 0 || errno == EEXIST)
278 	return;
279     if (! existence_error (errno))
280     {
281 	error (0, errno, "cannot make path to %s", name);
282 	return;
283     }
284     if ((cp = strrchr (name, '/')) == NULL)
285 	return;
286     *cp = '\0';
287     make_directories (name);
288     *cp++ = '/';
289     if (*cp == '\0')
290 	return;
291     (void) mkdir (name, 0777);
292 }
293 
294 /* Create directory NAME if it does not already exist; fatal error for
295    other errors.  Returns 0 if directory was created; 1 if it already
296    existed.  */
297 int
298 mkdir_if_needed (name)
299     char *name;
300 {
301     if (mkdir (name, 0777) < 0)
302     {
303 	if (errno != EEXIST
304 #ifdef EACCESS
305 	    /* This was copied over from the OS/2 code; I would guess it
306 	       isn't needed here but that has not been verified.  */
307 	    && errno != EACCESS
308 #endif
309 	    )
310 	    error (1, errno, "cannot make directory %s", name);
311 	return 1;
312     }
313     return 0;
314 }
315 
316 /*
317  * Change the mode of a file, either adding write permissions, or removing
318  * all write permissions.  Either change honors the current umask setting.
319  */
320 void
321 xchmod (fname_file, writable)
322     char *fname_file;
323     int writable;
324 {
325     char fname[PATH_MAX];
326     struct stat sb;
327     mode_t mode, oumask;
328 
329     /* Prefer local relative paths to files at expense of logical name
330        access to files. */
331 
332     if (isabsolute(fname_file))
333       strcpy(fname, fname_file);
334     else
335       sprintf(fname, "./%s", fname_file);
336 
337     if (stat (fname, &sb) < 0)
338     {
339 	if (!noexec)
340 	    error (0, errno, "cannot stat %s", fname);
341 	return;
342     }
343     oumask = umask (0);
344     (void) umask (oumask);
345     if (writable)
346     {
347 	mode = sb.st_mode | (~oumask
348 			     & (((sb.st_mode & S_IRUSR) ? S_IWUSR : 0)
349 				| ((sb.st_mode & S_IRGRP) ? S_IWGRP : 0)
350 				| ((sb.st_mode & S_IROTH) ? S_IWOTH : 0)));
351     }
352     else
353     {
354 	mode = sb.st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH) & ~oumask;
355     }
356 
357     if (trace)
358 #ifdef SERVER_SUPPORT
359 	(void) fprintf (stderr, "%c-> chmod(%s,%o)\n",
360 			(server_active) ? 'S' : ' ', fname, mode);
361 #else
362 	(void) fprintf (stderr, "-> chmod(%s,%o)\n", fname, mode);
363 #endif
364     if (noexec)
365 	return;
366 
367     if (chmod (fname, mode) < 0)
368 	error (0, errno, "cannot change mode of file %s", fname);
369 }
370 
371 /*
372  * Rename a file and die if it fails
373  */
374 void
375 rename_file (from_file, to_file)
376     const char *from_file;
377     const char *to_file;
378 {
379     char from[PATH_MAX], to[PATH_MAX];
380 
381     /* Prefer local relative paths to files at expense of logical name
382        access to files. */
383 
384     if (isabsolute(from_file))
385       strcpy(from, from_file);
386     else
387       sprintf(from, "./%s", from_file);
388 
389     if (isabsolute(to_file))
390       strcpy(to, to_file);
391     else
392       sprintf(to, "./%s", to_file);
393 
394     if (trace)
395 #ifdef SERVER_SUPPORT
396 	(void) fprintf (stderr, "%c-> rename(%s,%s)\n",
397 			(server_active) ? 'S' : ' ', from, to);
398 #else
399 	(void) fprintf (stderr, "-> rename(%s,%s)\n", from, to);
400 #endif
401     if (noexec)
402 	return;
403 
404     if (rename (from, to) < 0)
405 	error (1, errno, "cannot rename file %s to %s", from, to);
406 }
407 
408 /*
409  * unlink a file, if possible.
410  */
411 int
412 unlink_file (f_file)
413     const char *f_file;
414 {
415     char f[PATH_MAX];
416 
417     /* Prefer local relative paths to files at expense of logical name
418        access to files. */
419 
420     if (isabsolute(f_file))
421       strcpy(f, f_file);
422     else
423       sprintf(f, "./%s", f_file);
424 
425     if (trace)
426 #ifdef SERVER_SUPPORT
427 	(void) fprintf (stderr, "%c-> unlink(%s)\n",
428 			(server_active) ? 'S' : ' ', f);
429 #else
430 	(void) fprintf (stderr, "-> unlink(%s)\n", f);
431 #endif
432     if (noexec)
433 	return (0);
434 
435     return (vms_unlink (f));
436 }
437 
438 /*
439  * Unlink a file or dir, if possible.  If it is a directory do a deep
440  * removal of all of the files in the directory.  Return -1 on error
441  * (in which case errno is set).
442  */
443 int
444 unlink_file_dir (f_file)
445     const char *f_file;
446 {
447     char f[PATH_MAX];
448 
449     /* Prefer local relative paths to files at expense of logical name
450        access to files. */
451 
452     if (isabsolute(f_file))
453       strcpy(f, f_file);
454     else
455       sprintf(f, "./%s", f_file);
456 
457     if (trace)
458 #ifdef SERVER_SUPPORT
459 	(void) fprintf (stderr, "%c-> unlink_file_dir(%s)\n",
460 			(server_active) ? 'S' : ' ', f);
461 #else
462 	(void) fprintf (stderr, "-> unlink_file_dir(%s)\n", f);
463 #endif
464     if (noexec)
465 	return (0);
466 
467     if (vms_unlink (f) != 0)
468     {
469 	/* under NEXTSTEP errno is set to return EPERM if
470 	 * the file is a directory,or if the user is not
471 	 * allowed to read or write to the file.
472 	 * [This is probably a bug in the O/S]
473 	 * other systems will return EISDIR to indicate
474 	 * that the path is a directory.
475 	 */
476         if (errno == EISDIR || errno == EPERM)
477                 return deep_remove_dir (f);
478         else
479 		/* The file wasn't a directory and some other
480 		 * error occured
481 		 */
482                 return -1;
483     }
484     /* We were able to remove the file from the disk */
485     return 0;
486 }
487 
488 /* Remove a directory and everything it contains.  Returns 0 for
489  * success, -1 for failure (in which case errno is set).
490  */
491 
492 static int
493 deep_remove_dir (path)
494     const char *path;
495 {
496     DIR		  *dirp;
497     struct dirent *dp;
498     char	   buf[PATH_MAX];
499 
500     if (rmdir (path) != 0 && (errno == ENOTEMPTY || errno == EEXIST))
501     {
502 	if ((dirp = CVS_OPENDIR (path)) == NULL)
503 	    /* If unable to open the directory return
504 	     * an error
505 	     */
506 	    return -1;
507 
508 	while ((dp = CVS_READDIR (dirp)) != NULL)
509 	{
510 	    if (strcmp (dp->d_name, ".") == 0 ||
511 			strcmp (dp->d_name, "..") == 0)
512 		continue;
513 
514 	    sprintf (buf, "%s/%s", path, dp->d_name);
515 
516 	    if (vms_unlink (buf) != 0 )
517 	    {
518 		if (errno == EISDIR || errno == EPERM)
519 		{
520 		    if (deep_remove_dir (buf))
521 		    {
522 			CVS_CLOSEDIR (dirp);
523 			return -1;
524 		    }
525 		}
526 		else
527 		{
528 		    /* buf isn't a directory, or there are
529 		     * some sort of permision problems
530 		     */
531 		    CVS_CLOSEDIR (dirp);
532 		    return -1;
533 		}
534 	    }
535 	}
536 	CVS_CLOSEDIR (dirp);
537 	return rmdir (path);
538 	}
539 
540     /* Was able to remove the directory return 0 */
541     return 0;
542 }
543 
544 /* Read NCHARS bytes from descriptor FD into BUF.
545    Return the number of characters successfully read.
546    The number returned is always NCHARS unless end-of-file or error.  */
547 static size_t
548 block_read (fd, buf, nchars)
549     int fd;
550     char *buf;
551     size_t nchars;
552 {
553     char *bp = buf;
554     size_t nread;
555 
556     do
557     {
558 	nread = read (fd, bp, nchars);
559 	if (nread == (size_t)-1)
560 	{
561 #ifdef EINTR
562 	    if (errno == EINTR)
563 		continue;
564 #endif
565 	    return (size_t)-1;
566 	}
567 
568 	if (nread == 0)
569 	    break;
570 
571 	bp += nread;
572 	nchars -= nread;
573     } while (nchars != 0);
574 
575     return bp - buf;
576 }
577 
578 
579 /*
580  * Compare "file1" to "file2". Return non-zero if they don't compare exactly.
581  */
582 int
583 xcmp (file1_file, file2_file)
584     const char *file1_file;
585     const char *file2_file;
586 {
587     char file1[PATH_MAX], file2[PATH_MAX];
588     char *buf1, *buf2;
589     struct stat sb1, sb2;
590     int fd1, fd2;
591     int ret;
592 
593     /* Prefer local relative paths to files at expense of logical name
594        access to files. */
595 
596     if (isabsolute(file1_file))
597       strcpy(file1, file1_file);
598     else
599       sprintf(file1, "./%s", file1_file);
600 
601     if (isabsolute(file2_file))
602       strcpy(file2, file2_file);
603     else
604       sprintf(file2, "./%s", file2_file);
605 
606     if ((fd1 = open (file1, O_RDONLY)) < 0)
607 	error (1, errno, "cannot open file %s for comparing", file1);
608     if ((fd2 = open (file2, O_RDONLY)) < 0)
609 	error (1, errno, "cannot open file %s for comparing", file2);
610     if (fstat (fd1, &sb1) < 0)
611 	error (1, errno, "cannot fstat %s", file1);
612     if (fstat (fd2, &sb2) < 0)
613 	error (1, errno, "cannot fstat %s", file2);
614 
615     /* A generic file compare routine might compare st_dev & st_ino here
616        to see if the two files being compared are actually the same file.
617        But that won't happen in CVS, so we won't bother. */
618 
619     if (sb1.st_size != sb2.st_size)
620 	ret = 1;
621     else if (sb1.st_size == 0)
622 	ret = 0;
623     else
624     {
625 	/* FIXME: compute the optimal buffer size by computing the least
626 	   common multiple of the files st_blocks field */
627 	size_t buf_size = 8 * 1024;
628 	size_t read1;
629 	size_t read2;
630 
631 	buf1 = xmalloc (buf_size);
632 	buf2 = xmalloc (buf_size);
633 
634 	do
635 	{
636 	    read1 = block_read (fd1, buf1, buf_size);
637 	    if (read1 == (size_t)-1)
638 		error (1, errno, "cannot read file %s for comparing", file1);
639 
640 	    read2 = block_read (fd2, buf2, buf_size);
641 	    if (read2 == (size_t)-1)
642 		error (1, errno, "cannot read file %s for comparing", file2);
643 
644 	    /* assert (read1 == read2); */
645 
646 	    ret = memcmp(buf1, buf2, read1);
647 	} while (ret == 0 && read1 == buf_size);
648 
649 	free (buf1);
650 	free (buf2);
651     }
652 
653     (void) close (fd1);
654     (void) close (fd2);
655     return (ret);
656 }
657 
658 unsigned char
659 VMS_filename_classes[] =
660 {
661     0x00,0x01,0x02,0x03, 0x04,0x05,0x06,0x07,
662     0x08,0x09,0x0a,0x0b, 0x0c,0x0d,0x0e,0x0f,
663     0x10,0x11,0x12,0x13, 0x14,0x15,0x16,0x17,
664     0x18,0x19,0x1a,0x1b, 0x1c,0x1d,0x1e,0x1f,
665     0x20,0x21,0x22,0x23, 0x24,0x25,0x26,0x27,
666     0x28,0x29,0x2a,0x2b, 0x2c,0x2d,0x2e,0x2f,
667     0x30,0x31,0x32,0x33, 0x34,0x35,0x36,0x37,
668     0x38,0x39,0x3a,0x3b, 0x3c,0x3d,0x3e,0x3f,
669     0x40,0x61,0x62,0x63, 0x64,0x65,0x66,0x67,
670     0x68,0x69,0x6a,0x6b, 0x6c,0x6d,0x6e,0x6f,
671     0x70,0x71,0x72,0x73, 0x74,0x75,0x76,0x77,
672     0x78,0x79,0x7a,0x5b, 0x5c,0x5d,0x5e,0x5f,
673     0x60,0x61,0x62,0x63, 0x64,0x65,0x66,0x67,
674     0x68,0x69,0x6a,0x6b, 0x6c,0x6d,0x6e,0x6f,
675     0x70,0x71,0x72,0x73, 0x74,0x75,0x76,0x77,
676     0x78,0x79,0x7a,0x7b, 0x7c,0x7d,0x7e,0x7f,
677     0x80,0x81,0x82,0x83, 0x84,0x85,0x86,0x87,
678     0x88,0x89,0x8a,0x8b, 0x8c,0x8d,0x8e,0x8f,
679     0x90,0x91,0x92,0x93, 0x94,0x95,0x96,0x97,
680     0x98,0x99,0x9a,0x9b, 0x9c,0x9d,0x9e,0x9f,
681     0xa0,0xa1,0xa2,0xa3, 0xa4,0xa5,0xa6,0xa7,
682     0xa8,0xa9,0xaa,0xab, 0xac,0xad,0xae,0xaf,
683     0xb0,0xb1,0xb2,0xb3, 0xb4,0xb5,0xb6,0xb7,
684     0xb8,0xb9,0xba,0xbb, 0xbc,0xbd,0xbe,0xbf,
685     0xc0,0xc1,0xc2,0xc3, 0xc4,0xc5,0xc6,0xc7,
686     0xc8,0xc9,0xca,0xcb, 0xcc,0xcd,0xce,0xcf,
687     0xd0,0xd1,0xd2,0xd3, 0xd4,0xd5,0xd6,0xd7,
688     0xd8,0xd9,0xda,0xdb, 0xdc,0xdd,0xde,0xdf,
689     0xe0,0xe1,0xe2,0xe3, 0xe4,0xe5,0xe6,0xe7,
690     0xe8,0xe9,0xea,0xeb, 0xec,0xed,0xee,0xef,
691     0xf0,0xf1,0xf2,0xf3, 0xf4,0xf5,0xf6,0xf7,
692     0xf8,0xf9,0xfa,0xfb, 0xfc,0xfd,0xfe,0xff,
693 };
694 
695 /* Like strcmp, but with the appropriate tweaks for file names.
696    Under VMS, filenames are case-insensitive but case-preserving.
697    FIXME: this should compare y.tab.c equal with y_tab.c, at least
698    if fnfold is modified (see below).  */
699 int
700 fncmp (const char *n1, const char *n2)
701 {
702     while (*n1 && *n2
703            && (VMS_filename_classes[(unsigned char) *n1]
704                == VMS_filename_classes[(unsigned char) *n2]))
705         n1++, n2++;
706     return (VMS_filename_classes[(unsigned char) *n1]
707             - VMS_filename_classes[(unsigned char) *n2]);
708 }
709 
710 /* Fold characters in FILENAME to their canonical forms.  FIXME: this
711    probably should be mapping y.tab.c to y_tab.c but first we have to
712    figure out whether fnfold is the right hook for that functionality
713    (probable answer: yes, but it should not fold case on OS/2, VMS, or
714    NT.  You see, fnfold isn't called anywhere, so we can define it to
715    mean whatever makes sense.  Of course to solve the VMS y.tab.c
716    problem we'd need to call it where appropriate.  It would need to
717    be redocumented as "fold to a form we can create in the filesystem"
718    rather than "canonical form").  The idea is that files we create
719    would get thusly munged, but CVS can cope with their names being
720    different the same way that the NT port copes with it if the user
721    renames a file from "foo" to "FOO".
722 
723    Alternately, this kind of handling could/should go into CVS_FOPEN
724    and friends (if we want to do it like the Mac port, anyway).  */
725 void
726 fnfold (char *filename)
727 {
728     while (*filename)
729     {
730         *filename = FOLD_FN_CHAR (*filename);
731 	filename++;
732     }
733 }
734 
735 /* Generate a unique temporary filename.  Returns a pointer to a newly
736    malloc'd string containing the name.  Returns successfully or not at
737    all.  */
738 char *
739 cvs_temp_name ()
740 {
741     char value[L_tmpnam + 1];
742     char *retval;
743 
744     /* FIXME: what is the VMS equivalent to TMPDIR?  */
745     retval = tmpnam (value);
746     if (retval == NULL)
747 	error (1, errno, "cannot generate temporary filename");
748     return xstrdup (retval);
749 }
750 
751 /* Return non-zero iff FILENAME is absolute.
752    Trivial under Unix, but more complicated under other systems.  */
753 int
754 isabsolute (filename)
755     const char *filename;
756 {
757     if(filename[0] == '/'
758        || filename[0] == '['
759        || filename[0] == '<'
760        || strchr(filename, ':'))
761         return 1;
762     else
763         return 0;
764 }
765 
766 
767 /* Return a pointer into PATH's last component.  */
768 char *
769 last_component (path)
770     char *path;
771 {
772     char *last = strrchr (path, '/');
773 
774     if (last && (last != path))
775         return last + 1;
776     else
777         return path;
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 #ifndef __VMS_VER
789 #define __VMS_VER 0
790 #endif
791 #ifndef __DECC_VER
792 #define __DECC_VER 0
793 #endif
794 
795 #if __VMS_VER < 70200000 || __DECC_VER < 50700000
796 /* See cvs.h for description.  On VMS this currently does nothing, although
797    I think we should be expanding wildcards here.  */
798 void
799 expand_wild (argc, argv, pargc, pargv)
800     int argc;
801     char **argv;
802     int *pargc;
803     char ***pargv;
804 {
805     int i;
806     *pargc = argc;
807     *pargv = (char **) xmalloc (argc * sizeof (char *));
808     for (i = 0; i < argc; ++i)
809         (*pargv)[i] = xstrdup (argv[i]);
810 }
811 
812 #else  /*  __VMS_VER >= 70200000 && __DECC_VER >= 50700000  */
813 
814 /* These global variables are necessary to pass information from the
815  * routine that calls decc$from_vms into the callback routine.  In a
816  * multi-threaded environment, access to these variables MUST be
817  * serialized.
818  */
819 static char CurWorkingDir[PATH_MAX+1];
820 static char **ArgvList;
821 static int  CurArg;
822 static int  MaxArgs;
823 
824 static int ew_no_op (char *fname) {
825     (void) fname;   /* Shut the compiler up */
826     return 1;       /* Continue */
827 }
828 
829 static int ew_add_file (char *fname) {
830     char *lastslash, *firstper;
831     int i;
832 
833     if (strncmp(fname,CurWorkingDir,strlen(CurWorkingDir)) == 0) {
834         fname += strlen(CurWorkingDir);
835     }
836     lastslash = strrchr(fname,'/');
837     if (!lastslash) {
838         lastslash = fname;
839     }
840     if ((firstper=strchr(lastslash,'.')) != strrchr(lastslash,'.')) {
841         /* We have two periods -- one is to separate the version off */
842         *strrchr(fname,'.') = '\0';
843     }
844     if (firstper && firstper[1]=='\0') {
845         *firstper = '\0';
846     }
847     /* The following code is to insure that no duplicates appear,
848      * because most of the time it will just be a different version
849      */
850     for (i=0;  i<CurArg && strcmp(ArgvList[i],fname)!=0;  ++i) {
851         ;
852     }
853     if (i==CurArg && CurArg<MaxArgs) {
854         ArgvList[CurArg++] = strdup(fname);
855     }
856     return ArgvList[CurArg-1] != 0; /* Stop if we couldn't dup the string */
857 }
858 
859 /* The following two routines are meant to allow future versions of new_arglist
860  * routine to be multi-thread-safe.  It will be necessary in that environment
861  * to serialize access to CurWorkingDir, ArgvList, MaxArg, and CurArg.  We
862  * currently don't do any multi-threaded programming, so right now these
863  * routines are no-ops.
864  */
865 static void wait_and_protect_globs (void) {
866     return;
867 }
868 
869 static void release_globs (void) {
870     return;
871 }
872 
873 /*pf---------------------------------------------------------------- expand_wild
874  *
875  *  New Argument List - (SDS)
876  *
877  *  DESCRIPTION:
878  *      This routine takes the argc, argv passed in from main() and returns a
879  *  new argc, argv list, which simulates (to an extent) Unix-Style filename
880  *  globbing with VMS wildcards.  The key difference is that it will return
881  *  Unix-style filenames, i.e., no VMS file version numbers.  The complexity
882  *  comes from the desire to not simply allocate 10000 argv entries.
883  *
884  *  INPUTS:
885  *      argc - The integer argc passed into main
886  *      argv - The pointer to the array of char*'s passed into main
887  *
888  *  OUTPUTS:
889  *      pargv - A pointer to a (char **) to hold the new argv list
890  *      pargc - A pointer to an int to hold the new argc
891  *
892  *  RETURNS:
893  *      NONE
894  *
895  *  SIDE EFFECTS:
896  *      This routine will normally modify the global statics CurArg, MaxArg,
897  *  ArgvList, and CurWorkingDir.
898  *
899  *  NOTES:
900  *      It is ok for &argc == pargc and &argv == pargv.
901  *
902  *------------------------------------------------------------------------------
903  */
904 void expand_wild (int argc, char **argv, int *pargc, char ***pargv) {
905     int totfiles, filesgotten;
906     int i;
907     int largc;
908     char **largv;
909 
910     /* This first loop is to find out AT MOST how big to make the
911      * pargv array.
912      */
913     for (totfiles=0,i=0;  i<argc;  ++i) {
914         char *arg = argv[i];
915 
916         if (arg != 0 && (   strchr(arg,' ') != 0
917                          || strcmp(arg,".") == 0
918                          || strcmp(arg,"..") == 0) ) {
919             ++totfiles;
920         }else if (arg != 0) {
921             int num;
922             char *p = arg;
923             /* Handle comma-separated filelists */
924             while ( (p=strchr(p,',')) != 0) {
925                 *p = '\0';
926                 num = decc$from_vms (arg, ew_no_op, 1);
927                 totfiles += num>0 ? num : 1;
928                 *p++ = ',';
929                 arg = p;
930             }
931             if (*arg != '\0') {
932                 num = decc$from_vms (arg, ew_no_op, 1);
933                 totfiles += num>0 ? num : 1;
934             }
935         }
936     }
937     largv = 0;
938     if (totfiles) {
939         largv = malloc (sizeof*largv * (totfiles + 1));
940     }
941     filesgotten = 0;
942     if (largv != 0) {
943         int len;
944         /* All bits set to zero may not be a NULL ptr */
945         for (i=totfiles;  --i>=0;  ) {
946             largv[i] = 0;
947         }
948         largv[totfiles] = 0;
949 
950         wait_and_protect_globs ();
951 
952         /*--- getcwd has an OpenVMS extension that allows us to ---*/
953         /*--- get back Unix-style path names ---*/
954         (void) getcwd (CurWorkingDir, sizeof CurWorkingDir - 1, 0);
955         len = strlen (CurWorkingDir);
956         if (   len > 0 && CurWorkingDir[len-1] != '/') {
957             (void) strcat (CurWorkingDir, "/");
958         }
959         CurArg = 0;
960         ArgvList = largv;
961         MaxArgs = totfiles + 1;
962 
963         for (i=0;  i<argc;  ++i) {
964             char *arg = argv[i];
965 
966             if (arg != 0 && (   strchr(arg,' ') != 0
967                              || strcmp(arg,".") == 0
968                              || strcmp(arg,"..") == 0) ) {
969                 if (CurArg < MaxArgs) {
970                     ArgvList[CurArg++] = strdup(arg);
971                 }
972                 ++filesgotten;
973             }else if (arg != 0) {
974                 char *p = arg;
975                 int num;
976                 /* Handle comma-separated filelists */
977                 while ( (p=strchr(p,',')) != 0) {
978                     *p = '\0';
979                     num = decc$from_vms (arg, ew_add_file, 1);
980                     if (num <= 0 && CurArg < MaxArgs) {
981                         ArgvList[CurArg++] = strdup(arg);
982                     }
983                     filesgotten += num>0 ? num : 1;
984                     *p++ = ',';
985                     arg = p;
986                 }
987                 if (*arg != '\0') {
988                     num = decc$from_vms (arg, ew_add_file, 1);
989                     if (num <= 0 && CurArg < MaxArgs) {
990                         ArgvList[CurArg++] = strdup(arg);
991                     }
992                     filesgotten += num>0 ? num : 1;
993                 }
994             }
995         }
996         if (filesgotten != totfiles) {
997             /*--- Files must have been created/deleted here ---*/;
998         }
999         filesgotten = CurArg;
1000 
1001         release_globs();
1002     }
1003     if (!largv) {
1004         (*pargv) = malloc (sizeof(char *));
1005         if ((*pargv) != 0) {
1006             *(*pargv) = 0;
1007         }
1008     }else {
1009         (*pargv) = largv;
1010     }
1011     (*pargc) = largv ? filesgotten : 0;
1012 
1013     return;
1014 }
1015 
1016 #endif  /*  __VMS_VER >= 70200000 && __DECC_VER >= 50700000  */
1017