*/
#include "rsync.h"
-/****************************************************************************
-Set a fd into nonblocking mode. Uses POSIX O_NONBLOCK if available,
-else
-if SYSV use O_NDELAY
-if BSD use FNDELAY
-****************************************************************************/
-int set_nonblocking(int fd)
-{
- int val;
-#ifdef O_NONBLOCK
-#define FLAG_TO_SET O_NONBLOCK
-#else
-#ifdef SYSV
-#define FLAG_TO_SET O_NDELAY
-#else /* BSD */
-#define FLAG_TO_SET FNDELAY
-#endif
-#endif
-
- if((val = fcntl(fd, F_GETFL, 0)) == -1)
- return -1;
- val |= FLAG_TO_SET;
- return fcntl( fd, F_SETFL, val);
-#undef FLAG_TO_SET
-}
-
+extern int verbose;
/* this is taken from CVS */
int piped_child(char **command,int *f_in,int *f_out)
if (pipe(to_child_pipe) < 0 ||
pipe(from_child_pipe) < 0) {
rprintf(FERROR,"pipe: %s\n",strerror(errno));
- exit_cleanup(1);
+ exit_cleanup(RERR_IPC);
}
pid = do_fork();
if (pid < 0) {
rprintf(FERROR,"fork: %s\n",strerror(errno));
- exit_cleanup(1);
+ exit_cleanup(RERR_IPC);
}
if (pid == 0)
close(from_child_pipe[0]) < 0 ||
dup2(from_child_pipe[1], STDOUT_FILENO) < 0) {
rprintf(FERROR,"Failed to dup/close : %s\n",strerror(errno));
- exit_cleanup(1);
+ exit_cleanup(RERR_IPC);
}
if (to_child_pipe[0] != STDIN_FILENO) close(to_child_pipe[0]);
if (from_child_pipe[1] != STDOUT_FILENO) close(from_child_pipe[1]);
execvp(command[0], command);
rprintf(FERROR,"Failed to exec %s : %s\n",
command[0],strerror(errno));
- exit_cleanup(1);
+ exit_cleanup(RERR_IPC);
}
if (close(from_child_pipe[1]) < 0 ||
close(to_child_pipe[0]) < 0) {
rprintf(FERROR,"Failed to close : %s\n",strerror(errno));
- exit_cleanup(1);
+ exit_cleanup(RERR_IPC);
}
*f_in = from_child_pipe[0];
*f_out = to_child_pipe[1];
- set_nonblocking(*f_in);
- set_nonblocking(*f_out);
-
return pid;
}
if (pipe(to_child_pipe) < 0 ||
pipe(from_child_pipe) < 0) {
rprintf(FERROR,"pipe: %s\n",strerror(errno));
- exit_cleanup(1);
+ exit_cleanup(RERR_IPC);
}
pid = do_fork();
if (pid < 0) {
rprintf(FERROR,"fork: %s\n",strerror(errno));
- exit_cleanup(1);
+ exit_cleanup(RERR_IPC);
}
if (pid == 0) {
close(from_child_pipe[0]) < 0 ||
dup2(from_child_pipe[1], STDOUT_FILENO) < 0) {
rprintf(FERROR,"Failed to dup/close : %s\n",strerror(errno));
- exit_cleanup(1);
+ exit_cleanup(RERR_IPC);
}
if (to_child_pipe[0] != STDIN_FILENO) close(to_child_pipe[0]);
if (from_child_pipe[1] != STDOUT_FILENO) close(from_child_pipe[1]);
if (close(from_child_pipe[1]) < 0 ||
close(to_child_pipe[0]) < 0) {
rprintf(FERROR,"Failed to close : %s\n",strerror(errno));
- exit_cleanup(1);
+ exit_cleanup(RERR_IPC);
}
*f_in = from_child_pipe[0];
void out_of_memory(char *str)
{
rprintf(FERROR,"ERROR: out of memory in %s\n",str);
- exit_cleanup(1);
+ exit_cleanup(RERR_MALLOC);
}
void overflow(char *str)
{
rprintf(FERROR,"ERROR: buffer overflow in %s\n",str);
- exit_cleanup(1);
+ exit_cleanup(RERR_MALLOC);
}
for an error.
derived from GNU C's cccp.c. */
-int safe_read(int desc, char *ptr, int len)
+static int safe_read(int desc, char *ptr, int len)
{
int n_chars;
char buf[1024 * 8];
int len; /* Number of bytes read into `buf'. */
- ifd = open(source, O_RDONLY);
+ ifd = do_open(source, O_RDONLY, 0);
if (ifd == -1) {
rprintf(FERROR,"open %s: %s\n",
source,strerror(errno));
return -1;
}
- if (do_unlink(dest) && errno != ENOENT) {
+ if (robust_unlink(dest) && errno != ENOENT) {
rprintf(FERROR,"unlink %s: %s\n",
dest,strerror(errno));
return -1;
return 0;
}
-/* sleep for a while via select */
-void u_sleep(int usec)
+/*
+ Robust unlink: some OS'es (HPUX) refuse to unlink busy files, so
+ rename to <path>/.rsyncNNN instead. Note that successive rsync runs
+ will shuffle the filenames around a bit as long as the file is still
+ busy; this is because this function does not know if the unlink call
+ is due to a new file coming in, or --delete trying to remove old
+ .rsyncNNN files, hence it renames it each time.
+*/
+/* MAX_RENAMES should be 10**MAX_RENAMES_DIGITS */
+#define MAX_RENAMES_DIGITS 3
+#define MAX_RENAMES 1000
+
+int robust_unlink(char *fname)
{
- struct timeval tv;
+#ifndef ETXTBSY
+ return do_unlink(fname);
+#else
+ static int counter = 1;
+ int rc, pos, start;
+ char path[MAXPATHLEN];
+
+ rc = do_unlink(fname);
+ if ((rc == 0) || (errno != ETXTBSY))
+ return rc;
+
+ strlcpy(path, fname, MAXPATHLEN);
+
+ pos = strlen(path);
+ while((path[--pos] != '/') && (pos >= 0))
+ ;
+ ++pos;
+ strlcpy(&path[pos], ".rsync", MAXPATHLEN-pos);
+ pos += sizeof(".rsync")-1;
+
+ if (pos > (MAXPATHLEN-MAX_RENAMES_DIGITS-1)) {
+ errno = ETXTBSY;
+ return -1;
+ }
- tv.tv_sec = 0;
- tv.tv_usec = usec;
- select(0, NULL, NULL, NULL, &tv);
+ /* start where the last one left off to reduce chance of clashes */
+ start = counter;
+ do {
+ sprintf(&path[pos], "%03d", counter);
+ if (++counter >= MAX_RENAMES)
+ counter = 1;
+ } while (((rc = access(path, 0)) == 0) && (counter != start));
+
+ if (verbose > 0)
+ rprintf(FINFO,"renaming %s to %s because of text busy\n",
+ fname, path);
+
+ /* maybe we should return rename()'s exit status? Nah. */
+ if (do_rename(fname, path) != 0) {
+ errno = ETXTBSY;
+ return -1;
+ }
+ return 0;
+#endif
+}
+
+int robust_rename(char *from, char *to)
+{
+#ifndef ETXTBSY
+ return do_rename(from, to);
+#else
+ int rc = do_rename(from, to);
+ if ((rc == 0) || (errno != ETXTBSY))
+ return rc;
+ if (robust_unlink(to) != 0)
+ return -1;
+ return do_rename(from, to);
+#endif
}
}
}
-/* like strncpy but does not 0 fill the buffer and always null
- terminates (thus it can use maxlen+1 space in d) */
-void strlcpy(char *d, char *s, int maxlen)
-{
- int len = strlen(s);
- if (len > maxlen) len = maxlen;
- memcpy(d, s, len);
- d[len] = 0;
-}
-
-/* like strncat but does not 0 fill the buffer and always null
- terminates (thus it can use maxlen+1 space in d) */
-void strlcat(char *d, char *s, int maxlen)
-{
- int len1 = strlen(d);
- int len2 = strlen(s);
- if (len1+len2 > maxlen) {
- len2 = maxlen-len1;
- }
- if (len2 > 0) {
- memcpy(d+len1, s, len2);
- d[len1+len2] = 0;
- }
-}
-
/* turn a user name into a uid */
int name_to_uid(char *name, uid_t *uid)
{
}
-/****************************************************************************
-check if a process exists.
-****************************************************************************/
-int process_exists(int pid)
-{
- return(kill(pid,0) == 0 || errno != ESRCH);
-}
-
/* lock a byte range in a open file */
int lock_range(int fd, int offset, int len)
{
static void glob_expand_one(char *s, char **argv, int *argc, int maxargs)
{
-#ifndef HAVE_GLOB
+#if !(defined(HAVE_GLOB) && defined(HAVE_GLOB_H))
if (!*s) s = ".";
argv[*argc] = strdup(s);
(*argc)++;
return;
#else
+ extern int sanitize_paths;
glob_t globbuf;
int i;
if (!*s) s = ".";
argv[*argc] = strdup(s);
+ if (sanitize_paths) {
+ sanitize_path(argv[*argc], NULL);
+ }
memset(&globbuf, 0, sizeof(globbuf));
glob(argv[*argc], 0, NULL, &globbuf);
}
}
-/* this is like vsnprintf but the 'n' limit does not include
- the terminating null. So if you have a 1024 byte buffer then
- pass 1023 for n */
+/* this is like vsnprintf but it always null terminates, so you
+ can fit at most n-1 chars in */
int vslprintf(char *str, int n, const char *format, va_list ap)
{
-#ifdef HAVE_VSNPRINTF
int ret = vsnprintf(str, n, format, ap);
- if (ret > n || ret < 0) {
- str[n] = 0;
+ if (ret >= n || ret < 0) {
+ str[n-1] = 0;
return -1;
}
str[ret] = 0;
return ret;
-#else
- static char *buf;
- static int len=MAXPATHLEN*8;
- int ret;
-
- /* this code is NOT a proper vsnprintf() implementation. It
- relies on the fact that all calls to slprintf() in rsync
- pass strings which have already been checked to be less
- than MAXPATHLEN in length and never more than 2 strings are
- concatenated. This means the above buffer is absolutely
- ample and can never be overflowed.
-
- In the future we would like to replace this with a proper
- vsnprintf() implementation but right now we need a solution
- that is secure and portable. This is it. */
-
- if (!buf) {
- buf = malloc(len);
- if (!buf) {
- /* can't call debug or we would recurse */
- exit_cleanup(1);
- }
- }
-
- vsprintf(buf, format, ap);
- ret = strlen(buf);
- if (ret > n) {
- /* yikes! */
- exit_cleanup(1);
- }
- buf[ret] = 0;
-
- memcpy(str, buf, ret+1);
-
- return ret;
-#endif
}
}
}
+/*
+ * Make path appear as if a chroot had occurred:
+ * 1. remove leading "/" (or replace with "." if at end)
+ * 2. remove leading ".." components (except those allowed by "reldir")
+ * 3. delete any other "<dir>/.." (recursively)
+ * Can only shrink paths, so sanitizes in place.
+ * While we're at it, remove double slashes and "." components like
+ * clean_fname does(), but DON'T remove a trailing slash because that
+ * is sometimes significant on command line arguments.
+ * If "reldir" is non-null, it is a sanitized directory that the path will be
+ * relative to, so allow as many ".." at the beginning of the path as
+ * there are components in reldir. This is used for symbolic link targets.
+ * If reldir is non-null and the path began with "/", to be completely like
+ * a chroot we should add in depth levels of ".." at the beginning of the
+ * path, but that would blow the assumption that the path doesn't grow and
+ * it is not likely to end up being a valid symlink anyway, so just do
+ * the normal removal of the leading "/" instead.
+ * Contributed by Dave Dykstra <dwd@bell-labs.com>
+ */
+
+void sanitize_path(char *p, char *reldir)
+{
+ char *start, *sanp;
+ int depth = 0;
+ int allowdotdot = 0;
+
+ if (reldir) {
+ depth++;
+ while (*reldir) {
+ if (*reldir++ == '/') {
+ depth++;
+ }
+ }
+ }
+ start = p;
+ sanp = p;
+ while (*p == '/') {
+ /* remove leading slashes */
+ p++;
+ }
+ while (*p != '\0') {
+ /* this loop iterates once per filename component in p.
+ * both p (and sanp if the original had a slash) should
+ * always be left pointing after a slash
+ */
+ if ((*p == '.') && ((*(p+1) == '/') || (*(p+1) == '\0'))) {
+ /* skip "." component */
+ while (*++p == '/') {
+ /* skip following slashes */
+ ;
+ }
+ continue;
+ }
+ allowdotdot = 0;
+ if ((*p == '.') && (*(p+1) == '.') &&
+ ((*(p+2) == '/') || (*(p+2) == '\0'))) {
+ /* ".." component followed by slash or end */
+ if ((depth > 0) && (sanp == start)) {
+ /* allow depth levels of .. at the beginning */
+ --depth;
+ allowdotdot = 1;
+ } else {
+ p += 2;
+ if (*p == '/')
+ p++;
+ if (sanp != start) {
+ /* back up sanp one level */
+ --sanp; /* now pointing at slash */
+ while ((sanp > start) && (*(sanp - 1) != '/')) {
+ /* skip back up to slash */
+ sanp--;
+ }
+ }
+ continue;
+ }
+ }
+ while (1) {
+ /* copy one component through next slash */
+ *sanp++ = *p++;
+ if ((*p == '\0') || (*(p-1) == '/')) {
+ while (*p == '/') {
+ /* skip multiple slashes */
+ p++;
+ }
+ break;
+ }
+ }
+ if (allowdotdot) {
+ /* move the virtual beginning to leave the .. alone */
+ start = sanp;
+ }
+ }
+ if ((sanp == start) && !allowdotdot) {
+ /* ended up with nothing, so put in "." component */
+ /*
+ * note that the !allowdotdot doesn't prevent this from
+ * happening in all allowed ".." situations, but I didn't
+ * think it was worth putting in an extra variable to ensure
+ * it since an extra "." won't hurt in those situations.
+ */
+ *sanp++ = '.';
+ }
+ *sanp = '\0';
+}
+
static char curr_dir[MAXPATHLEN];
getcwd(curr_dir, sizeof(curr_dir)-1);
}
+ if (!dir) return NULL; /* this call was probably just to initialize */
+
if (chdir(dir)) return NULL;
if (save) {
}
if (*dir == '/') {
- strlcpy(curr_dir, dir, sizeof(curr_dir)-1);
+ strlcpy(curr_dir, dir, sizeof(curr_dir));
} else {
- strlcat(curr_dir,"/", sizeof(curr_dir)-1);
- strlcat(curr_dir,dir, sizeof(curr_dir)-1);
+ strlcat(curr_dir,"/", sizeof(curr_dir));
+ strlcat(curr_dir,dir, sizeof(curr_dir));
}
clean_fname(curr_dir);
return ret;
}
- strlcpy(curr_dir, dir, sizeof(curr_dir)-1);
+ strlcpy(curr_dir, dir, sizeof(curr_dir));
free(dir);
to ensure that signed/unsigned usage is consistent between machines. */
int u_strcmp(const char *cs1, const char *cs2)
{
- const uchar *s1 = (uchar *)cs1;
- const uchar *s2 = (uchar *)cs2;
+ const uchar *s1 = (const uchar *)cs1;
+ const uchar *s2 = (const uchar *)cs2;
while (*s1 && *s2 && (*s1 == *s2)) {
s1++; s2++;
static OFF_T last_ofs;
-void end_progress(void)
+void end_progress(OFF_T size)
{
extern int do_progress, am_server;
if (do_progress && !am_server) {
- rprintf(FINFO,"\n");
+ rprintf(FINFO,"%.0f (100%%)\n", (double)size);
}
last_ofs = 0;
}
free(dest);
return (depth < 0);
}
+
+
+/****************************************************************************
+ return the date and time as a string
+****************************************************************************/
+char *timestring(time_t t)
+{
+ static char TimeBuf[200];
+ struct tm *tm = localtime(&t);
+
+#ifdef HAVE_STRFTIME
+ strftime(TimeBuf,sizeof(TimeBuf)-1,"%Y/%m/%d %T",tm);
+#else
+ strlcpy(TimeBuf, asctime(tm), sizeof(TimeBuf));
+#endif
+
+ if (TimeBuf[strlen(TimeBuf)-1] == '\n') {
+ TimeBuf[strlen(TimeBuf)-1] = 0;
+ }
+
+ return(TimeBuf);
+}
+
+
+/****************************************************************************
+ like waitpid but does the WEXITSTATUS
+****************************************************************************/
+#ifndef WEXITSTATUS
+#define WEXITSTATUS(stat) ((int)(((stat)>>8)&0xFF))
+#endif
+void wait_process(pid_t pid, int *status)
+{
+ waitpid(pid, status, 0);
+ *status = WEXITSTATUS(*status);
+}