xref: /openbsd-src/gnu/usr.bin/cvs/src/filesubr.c (revision 2770ece558b09db842a03eacdf247234131b8970)
1 /* filesubr.c --- subroutines for dealing with files
2    Jim Blandy <jimb@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 "cvs.h"
21 
22 static int deep_remove_dir PROTO((const char *path));
23 
24 /*
25  * Copies "from" to "to".
26  */
27 void
28 copy_file (from, to)
29     const char *from;
30     const char *to;
31 {
32     struct stat sb;
33     struct utimbuf t;
34     int fdin, fdout;
35 
36     if (trace)
37 #ifdef SERVER_SUPPORT
38 	(void) fprintf (stderr, "%c-> copy(%s,%s)\n",
39 			(server_active) ? 'S' : ' ', from, to);
40 #else
41 	(void) fprintf (stderr, "-> copy(%s,%s)\n", from, to);
42 #endif
43     if (noexec)
44 	return;
45 
46     if ((fdin = open (from, O_RDONLY)) < 0)
47 	error (1, errno, "cannot open %s for copying", from);
48     if (fstat (fdin, &sb) < 0)
49 	error (1, errno, "cannot fstat %s", from);
50     if ((fdout = creat (to, (int) sb.st_mode & 07777)) < 0)
51 	error (1, errno, "cannot create %s for copying", to);
52     if (sb.st_size > 0)
53     {
54 	char buf[BUFSIZ];
55 	int n;
56 
57 	for (;;)
58 	{
59 	    n = read (fdin, buf, sizeof(buf));
60 	    if (n == -1)
61 	    {
62 #ifdef EINTR
63 		if (errno == EINTR)
64 		    continue;
65 #endif
66 		error (1, errno, "cannot read file %s for copying", from);
67 	    }
68             else if (n == 0)
69 		break;
70 
71 	    if (write(fdout, buf, n) != n) {
72 		error (1, errno, "cannot write file %s for copying", to);
73 	    }
74 	}
75 
76 #ifdef HAVE_FSYNC
77 	if (fsync (fdout))
78 	    error (1, errno, "cannot fsync file %s after copying", to);
79 #endif
80     }
81 
82     if (close (fdin) < 0)
83 	error (0, errno, "cannot close %s", from);
84     if (close (fdout) < 0)
85 	error (1, errno, "cannot close %s", to);
86 
87     /* now, set the times for the copied file to match those of the original */
88     memset ((char *) &t, 0, sizeof (t));
89     t.actime = sb.st_atime;
90     t.modtime = sb.st_mtime;
91     (void) utime (to, &t);
92 }
93 
94 /* FIXME-krp: these functions would benefit from caching the char * &
95    stat buf.  */
96 
97 /*
98  * Returns non-zero if the argument file is a directory, or is a symbolic
99  * link which points to a directory.
100  */
101 int
102 isdir (file)
103     const char *file;
104 {
105     struct stat sb;
106 
107     if (stat (file, &sb) < 0)
108 	return (0);
109     return (S_ISDIR (sb.st_mode));
110 }
111 
112 /*
113  * Returns non-zero if the argument file is a symbolic link.
114  */
115 int
116 islink (file)
117     const char *file;
118 {
119 #ifdef S_ISLNK
120     struct stat sb;
121 
122     if (lstat (file, &sb) < 0)
123 	return (0);
124     return (S_ISLNK (sb.st_mode));
125 #else
126     return (0);
127 #endif
128 }
129 
130 /*
131  * Returns non-zero if the argument file exists.
132  */
133 int
134 isfile (file)
135     const char *file;
136 {
137     return isaccessible(file, F_OK);
138 }
139 
140 /*
141  * Returns non-zero if the argument file is readable.
142  */
143 int
144 isreadable (file)
145     const char *file;
146 {
147     return isaccessible(file, R_OK);
148 }
149 
150 /*
151  * Returns non-zero if the argument file is writable.
152  */
153 int
154 iswritable (file)
155     const char *file;
156 {
157     return isaccessible(file, W_OK);
158 }
159 
160 /*
161  * Returns non-zero if the argument file is accessable according to
162  * mode.  If compiled with SETXID_SUPPORT also works if cvs has setxid
163  * bits set.
164  */
165 int
166 isaccessible (file, mode)
167     const char *file;
168     const int mode;
169 {
170 #ifdef SETXID_SUPPORT
171     struct stat sb;
172     int umask = 0;
173     int gmask = 0;
174     int omask = 0;
175     int uid;
176 
177     if (stat(file, &sb) == -1)
178 	return 0;
179     if (mode == F_OK)
180 	return 1;
181 
182     uid = geteuid();
183     if (uid == 0)		/* superuser */
184     {
185 	if (mode & X_OK)
186 	    return sb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH);
187 	else
188 	    return 1;
189     }
190 
191     if (mode & R_OK)
192     {
193 	umask |= S_IRUSR;
194 	gmask |= S_IRGRP;
195 	omask |= S_IROTH;
196     }
197     if (mode & W_OK)
198     {
199 	umask |= S_IWUSR;
200 	gmask |= S_IWGRP;
201 	omask |= S_IWOTH;
202     }
203     if (mode & X_OK)
204     {
205 	umask |= S_IXUSR;
206 	gmask |= S_IXGRP;
207 	omask |= S_IXOTH;
208     }
209 
210     if (sb.st_uid == uid)
211 	return (sb.st_mode & umask) == umask;
212     else if (sb.st_gid == getegid())
213 	return (sb.st_mode & gmask) == gmask;
214     else
215 	return (sb.st_mode & omask) == omask;
216 #else
217     return access(file, mode) == 0;
218 #endif
219 }
220 
221 /*
222  * Open a file and die if it fails
223  */
224 FILE *
225 open_file (name, mode)
226     const char *name;
227     const char *mode;
228 {
229     FILE *fp;
230 
231     if ((fp = fopen (name, mode)) == NULL)
232 	error (1, errno, "cannot open %s", name);
233     return (fp);
234 }
235 
236 /*
237  * Make a directory and die if it fails
238  */
239 void
240 make_directory (name)
241     const char *name;
242 {
243     struct stat sb;
244 
245     if (stat (name, &sb) == 0 && (!S_ISDIR (sb.st_mode)))
246 	    error (0, 0, "%s already exists but is not a directory", name);
247     if (!noexec && mkdir (name, 0777) < 0)
248 	error (1, errno, "cannot make directory %s", name);
249 }
250 
251 /*
252  * Make a path to the argument directory, printing a message if something
253  * goes wrong.
254  */
255 void
256 make_directories (name)
257     const char *name;
258 {
259     char *cp;
260 
261     if (noexec)
262 	return;
263 
264     if (mkdir (name, 0777) == 0 || errno == EEXIST)
265 	return;
266     if (! existence_error (errno))
267     {
268 	error (0, errno, "cannot make path to %s", name);
269 	return;
270     }
271     if ((cp = strrchr (name, '/')) == NULL)
272 	return;
273     *cp = '\0';
274     make_directories (name);
275     *cp++ = '/';
276     if (*cp == '\0')
277 	return;
278     (void) mkdir (name, 0777);
279 }
280 
281 /* Create directory NAME if it does not already exist; fatal error for
282    other errors.  Returns 0 if directory was created; 1 if it already
283    existed.  */
284 int
285 mkdir_if_needed (name)
286     char *name;
287 {
288     if (mkdir (name, 0777) < 0)
289     {
290 	if (errno != EEXIST)
291 	    error (1, errno, "cannot make directory %s", name);
292 	return 1;
293     }
294     return 0;
295 }
296 
297 /*
298  * Change the mode of a file, either adding write permissions, or removing
299  * all write permissions.  Either change honors the current umask setting.
300  */
301 void
302 xchmod (fname, writable)
303     char *fname;
304     int writable;
305 {
306     struct stat sb;
307     mode_t mode, oumask;
308 
309     if (stat (fname, &sb) < 0)
310     {
311 	if (!noexec)
312 	    error (0, errno, "cannot stat %s", fname);
313 	return;
314     }
315     oumask = umask (0);
316     (void) umask (oumask);
317     if (writable)
318     {
319 	mode = sb.st_mode | (~oumask
320 			     & (((sb.st_mode & S_IRUSR) ? S_IWUSR : 0)
321 				| ((sb.st_mode & S_IRGRP) ? S_IWGRP : 0)
322 				| ((sb.st_mode & S_IROTH) ? S_IWOTH : 0)));
323     }
324     else
325     {
326 	mode = sb.st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH) & ~oumask;
327     }
328 
329     if (trace)
330 #ifdef SERVER_SUPPORT
331 	(void) fprintf (stderr, "%c-> chmod(%s,%o)\n",
332 			(server_active) ? 'S' : ' ', fname,
333 			(unsigned int) mode);
334 #else
335 	(void) fprintf (stderr, "-> chmod(%s,%o)\n", fname,
336 			(unsigned int) mode);
337 #endif
338     if (noexec)
339 	return;
340 
341     if (chmod (fname, mode) < 0)
342 	error (0, errno, "cannot change mode of file %s", fname);
343 }
344 
345 /*
346  * Rename a file and die if it fails
347  */
348 void
349 rename_file (from, to)
350     const char *from;
351     const char *to;
352 {
353     if (trace)
354 #ifdef SERVER_SUPPORT
355 	(void) fprintf (stderr, "%c-> rename(%s,%s)\n",
356 			(server_active) ? 'S' : ' ', from, to);
357 #else
358 	(void) fprintf (stderr, "-> rename(%s,%s)\n", from, to);
359 #endif
360     if (noexec)
361 	return;
362 
363     if (rename (from, to) < 0)
364 	error (1, errno, "cannot rename file %s to %s", from, to);
365 }
366 
367 /*
368  * link a file, if possible.  Warning: the Windows NT version of this
369  * function just copies the file, so only use this function in ways
370  * that can deal with either a link or a copy.
371  */
372 int
373 link_file (from, to)
374     const char *from;
375     const char *to;
376 {
377     if (trace)
378 #ifdef SERVER_SUPPORT
379 	(void) fprintf (stderr, "%c-> link(%s,%s)\n",
380 			(server_active) ? 'S' : ' ', from, to);
381 #else
382 	(void) fprintf (stderr, "-> link(%s,%s)\n", from, to);
383 #endif
384     if (noexec)
385 	return (0);
386 
387     return (link (from, to));
388 }
389 
390 /*
391  * unlink a file, if possible.
392  */
393 int
394 unlink_file (f)
395     const char *f;
396 {
397     if (trace)
398 #ifdef SERVER_SUPPORT
399 	(void) fprintf (stderr, "%c-> unlink(%s)\n",
400 			(server_active) ? 'S' : ' ', f);
401 #else
402 	(void) fprintf (stderr, "-> unlink(%s)\n", f);
403 #endif
404     if (noexec)
405 	return (0);
406 
407     return (unlink (f));
408 }
409 
410 /*
411  * Unlink a file or dir, if possible.  If it is a directory do a deep
412  * removal of all of the files in the directory.  Return -1 on error
413  * (in which case errno is set).
414  */
415 int
416 unlink_file_dir (f)
417     const char *f;
418 {
419     if (trace)
420 #ifdef SERVER_SUPPORT
421 	(void) fprintf (stderr, "%c-> unlink_file_dir(%s)\n",
422 			(server_active) ? 'S' : ' ', f);
423 #else
424 	(void) fprintf (stderr, "-> unlink_file_dir(%s)\n", f);
425 #endif
426     if (noexec)
427 	return (0);
428 
429     /* For at least some unices, if root tries to unlink() a directory,
430        instead of doing something rational like returning EISDIR,
431        the system will gleefully go ahead and corrupt the filesystem.
432        So we first call isdir() to see if it is OK to call unlink().  This
433        doesn't quite work--if someone creates a directory between the
434        call to isdir() and the call to unlink(), we'll still corrupt
435        the filesystem.  Where is the Unix Haters Handbook when you need
436        it?  */
437     if (isdir(f))
438 	return deep_remove_dir(f);
439     else
440     {
441 	if (unlink (f) != 0)
442 	    return -1;
443     }
444     /* We were able to remove the file from the disk */
445     return 0;
446 }
447 
448 /* Remove a directory and everything it contains.  Returns 0 for
449  * success, -1 for failure (in which case errno is set).
450  */
451 
452 static int
453 deep_remove_dir (path)
454     const char *path;
455 {
456     DIR		  *dirp;
457     struct dirent *dp;
458 
459     if (rmdir (path) != 0)
460     {
461 	if (errno == ENOTEMPTY
462 	    || errno == EEXIST
463 	    /* Ugly workaround for ugly AIX 4.1 (and 3.2) header bug
464 	       (it defines ENOTEMPTY and EEXIST to 17 but actually
465 	       returns 87).  */
466 	    || (ENOTEMPTY == 17 && EEXIST == 17 && errno == 87))
467 	{
468 	    if ((dirp = opendir (path)) == NULL)
469 		/* If unable to open the directory return
470 		 * an error
471 		 */
472 		return -1;
473 
474 	    while ((dp = readdir (dirp)) != NULL)
475 	    {
476 		char *buf;
477 
478 		if (strcmp (dp->d_name, ".") == 0 ||
479 			    strcmp (dp->d_name, "..") == 0)
480 		    continue;
481 
482 		buf = xmalloc (strlen (path) + strlen (dp->d_name) + 5);
483 		sprintf (buf, "%s/%s", path, dp->d_name);
484 
485 		/* See comment in unlink_file_dir explanation of why we use
486 		   isdir instead of just calling unlink and checking the
487 		   status.  */
488 		if (isdir(buf))
489 		{
490 		    if (deep_remove_dir(buf))
491 		    {
492 			closedir(dirp);
493 			free (buf);
494 			return -1;
495 		    }
496 		}
497 		else
498 		{
499 		    if (unlink (buf) != 0)
500 		    {
501 			closedir(dirp);
502 			free (buf);
503 			return -1;
504 		    }
505 		}
506 		free (buf);
507 	    }
508 	    closedir (dirp);
509 	    return rmdir (path);
510 	}
511 	else
512 	    return -1;
513     }
514 
515     /* Was able to remove the directory return 0 */
516     return 0;
517 }
518 
519 /* Read NCHARS bytes from descriptor FD into BUF.
520    Return the number of characters successfully read.
521    The number returned is always NCHARS unless end-of-file or error.  */
522 static size_t
523 block_read (fd, buf, nchars)
524     int fd;
525     char *buf;
526     size_t nchars;
527 {
528     char *bp = buf;
529     size_t nread;
530 
531     do
532     {
533 	nread = read (fd, bp, nchars);
534 	if (nread == (size_t)-1)
535 	{
536 #ifdef EINTR
537 	    if (errno == EINTR)
538 		continue;
539 #endif
540 	    return (size_t)-1;
541 	}
542 
543 	if (nread == 0)
544 	    break;
545 
546 	bp += nread;
547 	nchars -= nread;
548     } while (nchars != 0);
549 
550     return bp - buf;
551 }
552 
553 
554 /*
555  * Compare "file1" to "file2". Return non-zero if they don't compare exactly.
556  */
557 int
558 xcmp (file1, file2)
559     const char *file1;
560     const char *file2;
561 {
562     char *buf1, *buf2;
563     struct stat sb1, sb2;
564     int fd1, fd2;
565     int ret;
566 
567     if ((fd1 = open (file1, O_RDONLY)) < 0)
568 	error (1, errno, "cannot open file %s for comparing", file1);
569     if ((fd2 = open (file2, O_RDONLY)) < 0)
570 	error (1, errno, "cannot open file %s for comparing", file2);
571     if (fstat (fd1, &sb1) < 0)
572 	error (1, errno, "cannot fstat %s", file1);
573     if (fstat (fd2, &sb2) < 0)
574 	error (1, errno, "cannot fstat %s", file2);
575 
576     /* A generic file compare routine might compare st_dev & st_ino here
577        to see if the two files being compared are actually the same file.
578        But that won't happen in CVS, so we won't bother. */
579 
580     if (sb1.st_size != sb2.st_size)
581 	ret = 1;
582     else if (sb1.st_size == 0)
583 	ret = 0;
584     else
585     {
586 	/* FIXME: compute the optimal buffer size by computing the least
587 	   common multiple of the files st_blocks field */
588 	size_t buf_size = 8 * 1024;
589 	size_t read1;
590 	size_t read2;
591 
592 	buf1 = xmalloc (buf_size);
593 	buf2 = xmalloc (buf_size);
594 
595 	do
596 	{
597 	    read1 = block_read (fd1, buf1, buf_size);
598 	    if (read1 == (size_t)-1)
599 		error (1, errno, "cannot read file %s for comparing", file1);
600 
601 	    read2 = block_read (fd2, buf2, buf_size);
602 	    if (read2 == (size_t)-1)
603 		error (1, errno, "cannot read file %s for comparing", file2);
604 
605 	    /* assert (read1 == read2); */
606 
607 	    ret = memcmp(buf1, buf2, read1);
608 	} while (ret == 0 && read1 == buf_size);
609 
610 	free (buf1);
611 	free (buf2);
612     }
613 
614     (void) close (fd1);
615     (void) close (fd2);
616     return (ret);
617 }
618 
619 /* Generate a unique temporary filename.  Returns a pointer to a newly
620    malloc'd string containing the name.  Returns successfully or not at
621    all.  */
622 /* There are at least three functions for generating temporary
623    filenames.  We use tempnam (SVID 3) if possible, else mktemp (BSD
624    4.3), and as last resort tmpnam (POSIX). Reason is that tempnam and
625    mktemp both allow to specify the directory in which the temporary
626    file will be created.  */
627 #ifdef HAVE_TEMPNAM
628 char *
629 cvs_temp_name ()
630 {
631     char *retval;
632 
633     retval = tempnam (Tmpdir, "cvs");
634     if (retval == NULL)
635 	error (1, errno, "cannot generate temporary filename");
636     /* tempnam returns a pointer to a newly malloc'd string, so there's
637        no need for a xstrdup  */
638     return retval;
639 }
640 #else
641 char *
642 cvs_temp_name ()
643 {
644 #  ifdef HAVE_MKTEMP
645     char *value;
646     char *retval;
647 
648     value = xmalloc (strlen (Tmpdir) + 40);
649     sprintf (value, "%s/%s", Tmpdir, "cvsXXXXXX" );
650     retval = mktemp (value);
651 
652     if (retval == NULL)
653 	error (1, errno, "cannot generate temporary filename");
654     return value;
655 #  else
656     char value[L_tmpnam + 1];
657     char *retval;
658 
659     retval = tmpnam (value);
660     if (retval == NULL)
661 	error (1, errno, "cannot generate temporary filename");
662     return xstrdup (value);
663 #  endif
664 }
665 #endif
666 
667 /* Return non-zero iff FILENAME is absolute.
668    Trivial under Unix, but more complicated under other systems.  */
669 int
670 isabsolute (filename)
671     const char *filename;
672 {
673     return filename[0] == '/';
674 }
675 
676 
677 /* Return a pointer into PATH's last component.  */
678 char *
679 last_component (path)
680     char *path;
681 {
682     char *last = strrchr (path, '/');
683 
684     if (last)
685         return last + 1;
686     else
687         return path;
688 }
689 
690 /* Return the home directory.  Returns a pointer to storage
691    managed by this function or its callees (currently getenv).
692    This function will return the same thing every time it is
693    called.  */
694 char *
695 get_homedir ()
696 {
697     static char *home = NULL;
698     char *env = getenv ("HOME");
699     struct passwd *pw;
700 
701     if (home != NULL)
702 	return home;
703 
704     if (env)
705 	home = env;
706     else if ((pw = (struct passwd *) getpwuid (getuid ()))
707 	     && pw->pw_dir)
708 	home = xstrdup (pw->pw_dir);
709     else
710 	return 0;
711 
712     return home;
713 }
714 
715 /* See cvs.h for description.  On unix this does nothing, because the
716    shell expands the wildcards.  */
717 void
718 expand_wild (argc, argv, pargc, pargv)
719     int argc;
720     char **argv;
721     int *pargc;
722     char ***pargv;
723 {
724     int i;
725     *pargc = argc;
726     *pargv = (char **) xmalloc (argc * sizeof (char *));
727     for (i = 0; i < argc; ++i)
728 	(*pargv)[i] = xstrdup (argv[i]);
729 }
730 
731 #ifdef SERVER_SUPPORT
732 /* Case-insensitive string compare.  I know that some systems
733    have such a routine, but I'm not sure I see any reasons for
734    dealing with the hair of figuring out whether they do (I haven't
735    looked into whether this is a performance bottleneck; I would guess
736    not).  */
737 int
738 cvs_casecmp (str1, str2)
739     char *str1;
740     char *str2;
741 {
742     char *p;
743     char *q;
744     int pqdiff;
745 
746     p = str1;
747     q = str2;
748     while ((pqdiff = tolower (*p) - tolower (*q)) == 0)
749     {
750 	if (*p == '\0')
751 	    return 0;
752 	++p;
753 	++q;
754     }
755     return pqdiff;
756 }
757 
758 /* Case-insensitive file open.  As you can see, this is an expensive
759    call.  We don't regard it as our main strategy for dealing with
760    case-insensitivity.  Returns errno code or 0 for success.  Puts the
761    new file in *FP.  NAME and MODE are as for fopen.  If PATHP is not
762    NULL, then put a malloc'd string containing the pathname as found
763    into *PATHP.  *PATHP is only set if the return value is 0.
764 
765    Might be cleaner to separate the file finding (which just gives
766    *PATHP) from the file opening (which the caller can do).  For one
767    thing, might make it easier to know whether to put NAME or *PATHP
768    into error messages.  */
769 int
770 fopen_case (name, mode, fp, pathp)
771     char *name;
772     char *mode;
773     FILE **fp;
774     char **pathp;
775 {
776     struct dirent *dp;
777     DIR *dirp;
778     char *dir;
779     char *fname;
780     char *found_name;
781     int retval;
782 
783     /* Separate NAME into directory DIR and filename within the directory
784        FNAME.  */
785     dir = xstrdup (name);
786     fname = strrchr (dir, '/');
787     if (fname == NULL)
788 	error (1, 0, "internal error: relative pathname in fopen_case");
789     *fname++ = '\0';
790 
791     found_name = NULL;
792     dirp = CVS_OPENDIR (dir);
793     if (dirp == NULL)
794     {
795 	if (existence_error (errno))
796 	{
797 	    /* This can happen if we are looking in the Attic and the Attic
798 	       directory does not exist.  Return the error to the caller;
799 	       they know what to do with it.  */
800 	    retval = errno;
801 	    goto out;
802 	}
803 	else
804 	{
805 	    /* Give a fatal error; that way the error message can be
806 	       more specific than if we returned the error to the caller.  */
807 	    error (1, errno, "cannot read directory %s", dir);
808 	}
809     }
810     errno = 0;
811     while ((dp = readdir (dirp)) != NULL)
812     {
813 	if (cvs_casecmp (dp->d_name, fname) == 0)
814 	{
815 	    if (found_name != NULL)
816 		error (1, 0, "%s is ambiguous; could mean %s or %s",
817 		       fname, dp->d_name, found_name);
818 	    found_name = xstrdup (dp->d_name);
819 	}
820     }
821     if (errno != 0)
822 	error (1, errno, "cannot read directory %s", dir);
823     closedir (dirp);
824 
825     if (found_name == NULL)
826     {
827 	*fp = NULL;
828 	retval = ENOENT;
829     }
830     else
831     {
832 	char *p;
833 
834 	/* Copy the found name back into DIR.  We are assuming that
835 	   found_name is the same length as fname, which is true as
836 	   long as the above code is just ignoring case and not other
837 	   aspects of filename syntax.  */
838 	p = dir + strlen (dir);
839 	*p++ = '/';
840 	strcpy (p, found_name);
841 	*fp = fopen (dir, mode);
842 	if (*fp == NULL)
843 	    retval = errno;
844 	else
845 	    retval = 0;
846     }
847 
848     if (pathp == NULL)
849 	free (dir);
850     else if (retval != 0)
851 	free (dir);
852     else
853 	*pathp = dir;
854     free (found_name);
855  out:
856     return retval;
857 }
858 #endif /* SERVER_SUPPORT */
859