add a --ignore-errors option
[rsync/rsync.git] / util.c
... / ...
CommitLineData
1/*
2 Copyright (C) Andrew Tridgell 1996
3 Copyright (C) Paul Mackerras 1996
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18*/
19
20/*
21 Utilities used in rsync
22
23 tridge, June 1996
24 */
25#include "rsync.h"
26
27extern int verbose;
28
29/* create a file descriptor - like pipe() but use socketpair if
30 possible (because of blocking issues on pipes */
31int fd_pair(int fd[2])
32{
33#if HAVE_SOCKETPAIR
34 return socketpair(AF_UNIX, SOCK_STREAM, 0, fd);
35#else
36 return pipe(fd);
37#endif
38}
39
40
41/* this is taken from CVS */
42int piped_child(char **command,int *f_in,int *f_out)
43{
44 int pid;
45 int to_child_pipe[2];
46 int from_child_pipe[2];
47
48 if (fd_pair(to_child_pipe) < 0 ||
49 fd_pair(from_child_pipe) < 0) {
50 rprintf(FERROR,"pipe: %s\n",strerror(errno));
51 exit_cleanup(RERR_IPC);
52 }
53
54
55 pid = do_fork();
56 if (pid < 0) {
57 rprintf(FERROR,"fork: %s\n",strerror(errno));
58 exit_cleanup(RERR_IPC);
59 }
60
61 if (pid == 0)
62 {
63 extern int orig_umask;
64 if (dup2(to_child_pipe[0], STDIN_FILENO) < 0 ||
65 close(to_child_pipe[1]) < 0 ||
66 close(from_child_pipe[0]) < 0 ||
67 dup2(from_child_pipe[1], STDOUT_FILENO) < 0) {
68 rprintf(FERROR,"Failed to dup/close : %s\n",strerror(errno));
69 exit_cleanup(RERR_IPC);
70 }
71 if (to_child_pipe[0] != STDIN_FILENO) close(to_child_pipe[0]);
72 if (from_child_pipe[1] != STDOUT_FILENO) close(from_child_pipe[1]);
73 umask(orig_umask);
74 execvp(command[0], command);
75 rprintf(FERROR,"Failed to exec %s : %s\n",
76 command[0],strerror(errno));
77 exit_cleanup(RERR_IPC);
78 }
79
80 if (close(from_child_pipe[1]) < 0 ||
81 close(to_child_pipe[0]) < 0) {
82 rprintf(FERROR,"Failed to close : %s\n",strerror(errno));
83 exit_cleanup(RERR_IPC);
84 }
85
86 *f_in = from_child_pipe[0];
87 *f_out = to_child_pipe[1];
88
89 return pid;
90}
91
92int local_child(int argc, char **argv,int *f_in,int *f_out)
93{
94 int pid;
95 int to_child_pipe[2];
96 int from_child_pipe[2];
97
98 if (fd_pair(to_child_pipe) < 0 ||
99 fd_pair(from_child_pipe) < 0) {
100 rprintf(FERROR,"pipe: %s\n",strerror(errno));
101 exit_cleanup(RERR_IPC);
102 }
103
104
105 pid = do_fork();
106 if (pid < 0) {
107 rprintf(FERROR,"fork: %s\n",strerror(errno));
108 exit_cleanup(RERR_IPC);
109 }
110
111 if (pid == 0) {
112 extern int am_sender;
113 extern int am_server;
114
115 am_sender = !am_sender;
116 am_server = 1;
117
118 if (dup2(to_child_pipe[0], STDIN_FILENO) < 0 ||
119 close(to_child_pipe[1]) < 0 ||
120 close(from_child_pipe[0]) < 0 ||
121 dup2(from_child_pipe[1], STDOUT_FILENO) < 0) {
122 rprintf(FERROR,"Failed to dup/close : %s\n",strerror(errno));
123 exit_cleanup(RERR_IPC);
124 }
125 if (to_child_pipe[0] != STDIN_FILENO) close(to_child_pipe[0]);
126 if (from_child_pipe[1] != STDOUT_FILENO) close(from_child_pipe[1]);
127 start_server(STDIN_FILENO, STDOUT_FILENO, argc, argv);
128 }
129
130 if (close(from_child_pipe[1]) < 0 ||
131 close(to_child_pipe[0]) < 0) {
132 rprintf(FERROR,"Failed to close : %s\n",strerror(errno));
133 exit_cleanup(RERR_IPC);
134 }
135
136 *f_in = from_child_pipe[0];
137 *f_out = to_child_pipe[1];
138
139 return pid;
140}
141
142
143
144void out_of_memory(char *str)
145{
146 rprintf(FERROR,"ERROR: out of memory in %s\n",str);
147 exit_cleanup(RERR_MALLOC);
148}
149
150void overflow(char *str)
151{
152 rprintf(FERROR,"ERROR: buffer overflow in %s\n",str);
153 exit_cleanup(RERR_MALLOC);
154}
155
156
157
158int set_modtime(char *fname,time_t modtime)
159{
160 extern int dry_run;
161 if (dry_run) return 0;
162 {
163#ifdef HAVE_UTIMBUF
164 struct utimbuf tbuf;
165 tbuf.actime = time(NULL);
166 tbuf.modtime = modtime;
167 return utime(fname,&tbuf);
168#elif defined(HAVE_UTIME)
169 time_t t[2];
170 t[0] = time(NULL);
171 t[1] = modtime;
172 return utime(fname,t);
173#else
174 struct timeval t[2];
175 t[0].tv_sec = time(NULL);
176 t[0].tv_usec = 0;
177 t[1].tv_sec = modtime;
178 t[1].tv_usec = 0;
179 return utimes(fname,t);
180#endif
181 }
182}
183
184
185/****************************************************************************
186create any necessary directories in fname. Unfortunately we don't know
187what perms to give the directory when this is called so we need to rely
188on the umask
189****************************************************************************/
190int create_directory_path(char *fname)
191{
192 extern int orig_umask;
193 char *p;
194
195 while (*fname == '/') fname++;
196 while (strncmp(fname,"./",2)==0) fname += 2;
197
198 p = fname;
199 while ((p=strchr(p,'/'))) {
200 *p = 0;
201 do_mkdir(fname,0777 & ~orig_umask);
202 *p = '/';
203 p++;
204 }
205 return 0;
206}
207
208
209/* Write LEN bytes at PTR to descriptor DESC, retrying if interrupted.
210 Return LEN upon success, write's (negative) error code otherwise.
211
212 derived from GNU C's cccp.c.
213*/
214static int full_write(int desc, char *ptr, int len)
215{
216 int total_written;
217
218 total_written = 0;
219 while (len > 0) {
220 int written = write (desc, ptr, len);
221 if (written < 0) {
222#ifdef EINTR
223 if (errno == EINTR)
224 continue;
225#endif
226 return written;
227 }
228 total_written += written;
229 ptr += written;
230 len -= written;
231 }
232 return total_written;
233}
234
235/* Read LEN bytes at PTR from descriptor DESC, retrying if interrupted.
236 Return the actual number of bytes read, zero for EOF, or negative
237 for an error.
238
239 derived from GNU C's cccp.c. */
240static int safe_read(int desc, char *ptr, int len)
241{
242 int n_chars;
243
244 if (len <= 0)
245 return len;
246
247#ifdef EINTR
248 do {
249 n_chars = read(desc, ptr, len);
250 } while (n_chars < 0 && errno == EINTR);
251#else
252 n_chars = read(desc, ptr, len);
253#endif
254
255 return n_chars;
256}
257
258
259/* copy a file - this is used in conjunction with the --temp-dir option */
260int copy_file(char *source, char *dest, mode_t mode)
261{
262 int ifd;
263 int ofd;
264 char buf[1024 * 8];
265 int len; /* Number of bytes read into `buf'. */
266
267 ifd = do_open(source, O_RDONLY, 0);
268 if (ifd == -1) {
269 rprintf(FERROR,"open %s: %s\n",
270 source,strerror(errno));
271 return -1;
272 }
273
274 if (robust_unlink(dest) && errno != ENOENT) {
275 rprintf(FERROR,"unlink %s: %s\n",
276 dest,strerror(errno));
277 return -1;
278 }
279
280 ofd = do_open(dest, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, mode);
281 if (ofd == -1) {
282 rprintf(FERROR,"open %s: %s\n",
283 dest,strerror(errno));
284 close(ifd);
285 return -1;
286 }
287
288 while ((len = safe_read(ifd, buf, sizeof(buf))) > 0) {
289 if (full_write(ofd, buf, len) < 0) {
290 rprintf(FERROR,"write %s: %s\n",
291 dest,strerror(errno));
292 close(ifd);
293 close(ofd);
294 return -1;
295 }
296 }
297
298 close(ifd);
299 close(ofd);
300
301 if (len < 0) {
302 rprintf(FERROR,"read %s: %s\n",
303 source,strerror(errno));
304 return -1;
305 }
306
307 return 0;
308}
309
310/*
311 Robust unlink: some OS'es (HPUX) refuse to unlink busy files, so
312 rename to <path>/.rsyncNNN instead. Note that successive rsync runs
313 will shuffle the filenames around a bit as long as the file is still
314 busy; this is because this function does not know if the unlink call
315 is due to a new file coming in, or --delete trying to remove old
316 .rsyncNNN files, hence it renames it each time.
317*/
318/* MAX_RENAMES should be 10**MAX_RENAMES_DIGITS */
319#define MAX_RENAMES_DIGITS 3
320#define MAX_RENAMES 1000
321
322int robust_unlink(char *fname)
323{
324#ifndef ETXTBSY
325 return do_unlink(fname);
326#else
327 static int counter = 1;
328 int rc, pos, start;
329 char path[MAXPATHLEN];
330
331 rc = do_unlink(fname);
332 if ((rc == 0) || (errno != ETXTBSY))
333 return rc;
334
335 strlcpy(path, fname, MAXPATHLEN);
336
337 pos = strlen(path);
338 while((path[--pos] != '/') && (pos >= 0))
339 ;
340 ++pos;
341 strlcpy(&path[pos], ".rsync", MAXPATHLEN-pos);
342 pos += sizeof(".rsync")-1;
343
344 if (pos > (MAXPATHLEN-MAX_RENAMES_DIGITS-1)) {
345 errno = ETXTBSY;
346 return -1;
347 }
348
349 /* start where the last one left off to reduce chance of clashes */
350 start = counter;
351 do {
352 sprintf(&path[pos], "%03d", counter);
353 if (++counter >= MAX_RENAMES)
354 counter = 1;
355 } while (((rc = access(path, 0)) == 0) && (counter != start));
356
357 if (verbose > 0)
358 rprintf(FINFO,"renaming %s to %s because of text busy\n",
359 fname, path);
360
361 /* maybe we should return rename()'s exit status? Nah. */
362 if (do_rename(fname, path) != 0) {
363 errno = ETXTBSY;
364 return -1;
365 }
366 return 0;
367#endif
368}
369
370int robust_rename(char *from, char *to)
371{
372#ifndef ETXTBSY
373 return do_rename(from, to);
374#else
375 int rc = do_rename(from, to);
376 if ((rc == 0) || (errno != ETXTBSY))
377 return rc;
378 if (robust_unlink(to) != 0)
379 return -1;
380 return do_rename(from, to);
381#endif
382}
383
384
385static pid_t all_pids[10];
386static int num_pids;
387
388/* fork and record the pid of the child */
389pid_t do_fork(void)
390{
391 pid_t newpid = fork();
392
393 if (newpid) {
394 all_pids[num_pids++] = newpid;
395 }
396 return newpid;
397}
398
399/* kill all children */
400void kill_all(int sig)
401{
402 int i;
403 for (i=0;i<num_pids;i++) {
404 if (all_pids[i] != getpid())
405 kill(all_pids[i], sig);
406 }
407}
408
409/* turn a user name into a uid */
410int name_to_uid(char *name, uid_t *uid)
411{
412 struct passwd *pass;
413 if (!name || !*name) return 0;
414 pass = getpwnam(name);
415 if (pass) {
416 *uid = pass->pw_uid;
417 return 1;
418 }
419 return 0;
420}
421
422/* turn a group name into a gid */
423int name_to_gid(char *name, gid_t *gid)
424{
425 struct group *grp;
426 if (!name || !*name) return 0;
427 grp = getgrnam(name);
428 if (grp) {
429 *gid = grp->gr_gid;
430 return 1;
431 }
432 return 0;
433}
434
435
436/* lock a byte range in a open file */
437int lock_range(int fd, int offset, int len)
438{
439 struct flock lock;
440
441 lock.l_type = F_WRLCK;
442 lock.l_whence = SEEK_SET;
443 lock.l_start = offset;
444 lock.l_len = len;
445 lock.l_pid = 0;
446
447 return fcntl(fd,F_SETLK,&lock) == 0;
448}
449
450
451static void glob_expand_one(char *s, char **argv, int *argc, int maxargs)
452{
453#if !(defined(HAVE_GLOB) && defined(HAVE_GLOB_H))
454 if (!*s) s = ".";
455 argv[*argc] = strdup(s);
456 (*argc)++;
457 return;
458#else
459 extern int sanitize_paths;
460 glob_t globbuf;
461 int i;
462
463 if (!*s) s = ".";
464
465 argv[*argc] = strdup(s);
466 if (sanitize_paths) {
467 sanitize_path(argv[*argc], NULL);
468 }
469
470 memset(&globbuf, 0, sizeof(globbuf));
471 glob(argv[*argc], 0, NULL, &globbuf);
472 if (globbuf.gl_pathc == 0) {
473 (*argc)++;
474 globfree(&globbuf);
475 return;
476 }
477 for (i=0; i<(maxargs - (*argc)) && i<globbuf.gl_pathc;i++) {
478 if (i == 0) free(argv[*argc]);
479 argv[(*argc) + i] = strdup(globbuf.gl_pathv[i]);
480 if (!argv[(*argc) + i]) out_of_memory("glob_expand");
481 }
482 globfree(&globbuf);
483 (*argc) += i;
484#endif
485}
486
487void glob_expand(char *base1, char **argv, int *argc, int maxargs)
488{
489 char *s = argv[*argc];
490 char *p, *q;
491 char *base = base1;
492
493 if (!s || !*s) return;
494
495 if (strncmp(s, base, strlen(base)) == 0) {
496 s += strlen(base);
497 }
498
499 s = strdup(s);
500 if (!s) out_of_memory("glob_expand");
501
502 base = (char *)malloc(strlen(base1)+3);
503 if (!base) out_of_memory("glob_expand");
504
505 sprintf(base," %s/", base1);
506
507 q = s;
508 while ((p = strstr(q,base)) && ((*argc) < maxargs)) {
509 /* split it at this point */
510 *p = 0;
511 glob_expand_one(q, argv, argc, maxargs);
512 q = p+strlen(base);
513 }
514
515 if (*q && (*argc < maxargs)) glob_expand_one(q, argv, argc, maxargs);
516
517 free(s);
518 free(base);
519}
520
521/*******************************************************************
522 convert a string to lower case
523********************************************************************/
524void strlower(char *s)
525{
526 while (*s) {
527 if (isupper(*s)) *s = tolower(*s);
528 s++;
529 }
530}
531
532/* this is like vsnprintf but it always null terminates, so you
533 can fit at most n-1 chars in */
534int vslprintf(char *str, int n, const char *format, va_list ap)
535{
536 int ret = vsnprintf(str, n, format, ap);
537 if (ret >= n || ret < 0) {
538 str[n-1] = 0;
539 return -1;
540 }
541 str[ret] = 0;
542 return ret;
543}
544
545
546/* like snprintf but always null terminates */
547int slprintf(char *str, int n, char *format, ...)
548{
549 va_list ap;
550 int ret;
551
552 va_start(ap, format);
553 ret = vslprintf(str,n,format,ap);
554 va_end(ap);
555 return ret;
556}
557
558
559void *Realloc(void *p, int size)
560{
561 if (!p) return (void *)malloc(size);
562 return (void *)realloc(p, size);
563}
564
565
566void clean_fname(char *name)
567{
568 char *p;
569 int l;
570 int modified = 1;
571
572 if (!name) return;
573
574 while (modified) {
575 modified = 0;
576
577 if ((p=strstr(name,"/./"))) {
578 modified = 1;
579 while (*p) {
580 p[0] = p[2];
581 p++;
582 }
583 }
584
585 if ((p=strstr(name,"//"))) {
586 modified = 1;
587 while (*p) {
588 p[0] = p[1];
589 p++;
590 }
591 }
592
593 if (strncmp(p=name,"./",2) == 0) {
594 modified = 1;
595 do {
596 p[0] = p[2];
597 } while (*p++);
598 }
599
600 l = strlen(p=name);
601 if (l > 1 && p[l-1] == '/') {
602 modified = 1;
603 p[l-1] = 0;
604 }
605 }
606}
607
608/*
609 * Make path appear as if a chroot had occurred:
610 * 1. remove leading "/" (or replace with "." if at end)
611 * 2. remove leading ".." components (except those allowed by "reldir")
612 * 3. delete any other "<dir>/.." (recursively)
613 * Can only shrink paths, so sanitizes in place.
614 * While we're at it, remove double slashes and "." components like
615 * clean_fname does(), but DON'T remove a trailing slash because that
616 * is sometimes significant on command line arguments.
617 * If "reldir" is non-null, it is a sanitized directory that the path will be
618 * relative to, so allow as many ".." at the beginning of the path as
619 * there are components in reldir. This is used for symbolic link targets.
620 * If reldir is non-null and the path began with "/", to be completely like
621 * a chroot we should add in depth levels of ".." at the beginning of the
622 * path, but that would blow the assumption that the path doesn't grow and
623 * it is not likely to end up being a valid symlink anyway, so just do
624 * the normal removal of the leading "/" instead.
625 * Contributed by Dave Dykstra <dwd@bell-labs.com>
626 */
627
628void sanitize_path(char *p, char *reldir)
629{
630 char *start, *sanp;
631 int depth = 0;
632 int allowdotdot = 0;
633
634 if (reldir) {
635 depth++;
636 while (*reldir) {
637 if (*reldir++ == '/') {
638 depth++;
639 }
640 }
641 }
642 start = p;
643 sanp = p;
644 while (*p == '/') {
645 /* remove leading slashes */
646 p++;
647 }
648 while (*p != '\0') {
649 /* this loop iterates once per filename component in p.
650 * both p (and sanp if the original had a slash) should
651 * always be left pointing after a slash
652 */
653 if ((*p == '.') && ((*(p+1) == '/') || (*(p+1) == '\0'))) {
654 /* skip "." component */
655 while (*++p == '/') {
656 /* skip following slashes */
657 ;
658 }
659 continue;
660 }
661 allowdotdot = 0;
662 if ((*p == '.') && (*(p+1) == '.') &&
663 ((*(p+2) == '/') || (*(p+2) == '\0'))) {
664 /* ".." component followed by slash or end */
665 if ((depth > 0) && (sanp == start)) {
666 /* allow depth levels of .. at the beginning */
667 --depth;
668 allowdotdot = 1;
669 } else {
670 p += 2;
671 if (*p == '/')
672 p++;
673 if (sanp != start) {
674 /* back up sanp one level */
675 --sanp; /* now pointing at slash */
676 while ((sanp > start) && (*(sanp - 1) != '/')) {
677 /* skip back up to slash */
678 sanp--;
679 }
680 }
681 continue;
682 }
683 }
684 while (1) {
685 /* copy one component through next slash */
686 *sanp++ = *p++;
687 if ((*p == '\0') || (*(p-1) == '/')) {
688 while (*p == '/') {
689 /* skip multiple slashes */
690 p++;
691 }
692 break;
693 }
694 }
695 if (allowdotdot) {
696 /* move the virtual beginning to leave the .. alone */
697 start = sanp;
698 }
699 }
700 if ((sanp == start) && !allowdotdot) {
701 /* ended up with nothing, so put in "." component */
702 /*
703 * note that the !allowdotdot doesn't prevent this from
704 * happening in all allowed ".." situations, but I didn't
705 * think it was worth putting in an extra variable to ensure
706 * it since an extra "." won't hurt in those situations.
707 */
708 *sanp++ = '.';
709 }
710 *sanp = '\0';
711}
712
713
714static char curr_dir[MAXPATHLEN];
715
716/* like chdir() but can be reversed with pop_dir() if save is set. It
717 is also much faster as it remembers where we have been */
718char *push_dir(char *dir, int save)
719{
720 char *ret = curr_dir;
721 static int initialised;
722
723 if (!initialised) {
724 initialised = 1;
725 getcwd(curr_dir, sizeof(curr_dir)-1);
726 }
727
728 if (!dir) return NULL; /* this call was probably just to initialize */
729
730 if (chdir(dir)) return NULL;
731
732 if (save) {
733 ret = strdup(curr_dir);
734 }
735
736 if (*dir == '/') {
737 strlcpy(curr_dir, dir, sizeof(curr_dir));
738 } else {
739 strlcat(curr_dir,"/", sizeof(curr_dir));
740 strlcat(curr_dir,dir, sizeof(curr_dir));
741 }
742
743 clean_fname(curr_dir);
744
745 return ret;
746}
747
748/* reverse a push_dir call */
749int pop_dir(char *dir)
750{
751 int ret;
752
753 ret = chdir(dir);
754 if (ret) {
755 free(dir);
756 return ret;
757 }
758
759 strlcpy(curr_dir, dir, sizeof(curr_dir));
760
761 free(dir);
762
763 return 0;
764}
765
766/* we need to supply our own strcmp function for file list comparisons
767 to ensure that signed/unsigned usage is consistent between machines. */
768int u_strcmp(const char *cs1, const char *cs2)
769{
770 const uchar *s1 = (const uchar *)cs1;
771 const uchar *s2 = (const uchar *)cs2;
772
773 while (*s1 && *s2 && (*s1 == *s2)) {
774 s1++; s2++;
775 }
776
777 return (int)*s1 - (int)*s2;
778}
779
780static OFF_T last_ofs;
781
782void end_progress(OFF_T size)
783{
784 extern int do_progress, am_server;
785
786 if (do_progress && !am_server) {
787 rprintf(FINFO,"%.0f (100%%)\n", (double)size);
788 }
789 last_ofs = 0;
790}
791
792void show_progress(OFF_T ofs, OFF_T size)
793{
794 extern int do_progress, am_server;
795
796 if (do_progress && !am_server) {
797 if (ofs > last_ofs + 1000) {
798 int pct = (int)((100.0*ofs)/size);
799 rprintf(FINFO,"%.0f (%d%%)\r", (double)ofs, pct);
800 last_ofs = ofs;
801 }
802 }
803}
804
805/* determine if a symlink points outside the current directory tree */
806int unsafe_symlink(char *dest, char *src)
807{
808 char *tok;
809 int depth = 0;
810
811 /* all absolute and null symlinks are unsafe */
812 if (!dest || !(*dest) || (*dest == '/')) return 1;
813
814 src = strdup(src);
815 if (!src) out_of_memory("unsafe_symlink");
816
817 /* find out what our safety margin is */
818 for (tok=strtok(src,"/"); tok; tok=strtok(NULL,"/")) {
819 if (strcmp(tok,"..") == 0) {
820 depth=0;
821 } else if (strcmp(tok,".") == 0) {
822 /* nothing */
823 } else {
824 depth++;
825 }
826 }
827 free(src);
828
829 /* drop by one to account for the filename portion */
830 depth--;
831
832 dest = strdup(dest);
833 if (!dest) out_of_memory("unsafe_symlink");
834
835 for (tok=strtok(dest,"/"); tok; tok=strtok(NULL,"/")) {
836 if (strcmp(tok,"..") == 0) {
837 depth--;
838 } else if (strcmp(tok,".") == 0) {
839 /* nothing */
840 } else {
841 depth++;
842 }
843 /* if at any point we go outside the current directory then
844 stop - it is unsafe */
845 if (depth < 0) break;
846 }
847
848 free(dest);
849 return (depth < 0);
850}
851
852
853/****************************************************************************
854 return the date and time as a string
855****************************************************************************/
856char *timestring(time_t t)
857{
858 static char TimeBuf[200];
859 struct tm *tm = localtime(&t);
860
861#ifdef HAVE_STRFTIME
862 strftime(TimeBuf,sizeof(TimeBuf)-1,"%Y/%m/%d %T",tm);
863#else
864 strlcpy(TimeBuf, asctime(tm), sizeof(TimeBuf));
865#endif
866
867 if (TimeBuf[strlen(TimeBuf)-1] == '\n') {
868 TimeBuf[strlen(TimeBuf)-1] = 0;
869 }
870
871 return(TimeBuf);
872}
873
874
875/****************************************************************************
876 like waitpid but does the WEXITSTATUS
877****************************************************************************/
878#ifndef WEXITSTATUS
879#define WEXITSTATUS(stat) ((int)(((stat)>>8)&0xFF))
880#endif
881void wait_process(pid_t pid, int *status)
882{
883 waitpid(pid, status, 0);
884 *status = WEXITSTATUS(*status);
885}