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
21static int deep_remove_dir PROTO((const char *path));
22
23/*
24 * Copies "from" to "to".
25 */
26void
27copy_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 */
114int
115isdir (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 */
128int
129islink (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 */
146int
147isfile (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 */
156int
157isreadable (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 */
166int
167iswritable (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 */
178int
179isaccessible (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 */
237FILE *
238open_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 */
252void
253make_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 */
268void
269make_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.  */
297int
298mkdir_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 */
320void
321xchmod (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 */
374void
375rename_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 */
411int
412unlink_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 */
443int
444unlink_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
492static int
493deep_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.  */
547static size_t
548block_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 */
582int
583xcmp (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
658unsigned char
659VMS_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).  */
699int
700fncmp (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).  */
725void
726fnfold (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.  */
738char *
739cvs_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.  */
753int
754isabsolute (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.  */
768char *
769last_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).  */
782char *
783get_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.  */
798void
799expand_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 */
819static char CurWorkingDir[PATH_MAX+1];
820static char **ArgvList;
821static int  CurArg;
822static int  MaxArgs;
823
824static int ew_no_op (char *fname) {
825    (void) fname;   /* Shut the compiler up */
826    return 1;       /* Continue */
827}
828
829static 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 */
865static void wait_and_protect_globs (void) {
866    return;
867}
868
869static 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 */
904void 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