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