changed strlcat() and strlcpy() to have the same semantics as the
[rsync/rsync.git] / util.c
diff --git a/util.c b/util.c
index 001694b..c5bb4a7 100644 (file)
--- a/util.c
+++ b/util.c
@@ -359,28 +359,34 @@ void kill_all(int sig)
 }
 
 /* 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)
+   terminates. bufsize is the size of the destination buffer */
+size_t strlcpy(char *d, const char *s, size_t bufsize)
 {
-       int len = strlen(s);
-       if (len > maxlen) len = maxlen;
+       size_t len = strlen(s);
+       size_t ret = len;
+       if (len >= bufsize) len = bufsize-1;
        memcpy(d, s, len);
        d[len] = 0;
+       return ret;
 }
 
 /* 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)
+   terminates. bufsize is the length of the buffer, which should
+   be one more than the maximum resulting string length */
+size_t strlcat(char *d, const char *s, size_t bufsize)
 {
-       int len1 = strlen(d);
-       int len2 = strlen(s);
-       if (len1+len2 > maxlen) {
-               len2 = maxlen-len1;
+       size_t len1 = strlen(d);
+       size_t len2 = strlen(s);
+       size_t ret = len1 + len2;
+
+       if (len1+len2 >= bufsize) {
+               len2 = bufsize - (len1+1);
        }
        if (len2 > 0) {
                memcpy(d+len1, s, len2);
                d[len1+len2] = 0;
        }
+       return ret;
 }
 
 /* turn a user name into a uid */
@@ -502,14 +508,13 @@ void strlower(char *s)
        }
 }
 
-/* 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)
 {
        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;
@@ -579,6 +584,75 @@ void clean_fname(char *name)
        }
 }
 
+/*
+ * Make path appear as if a chroot had occurred:
+ *    1. remove leading "/" (or replace with "." if at end)
+ *    2. remove leading ".." components
+ *    3. delete any other "<dir>/.." (recursively)
+ * 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.
+ * Return a malloc'ed copy.
+ * Contributed by Dave Dykstra <dwd@bell-labs.com>
+ */
+
+char *sanitize_path(char *p)
+{
+       char *copy, *copyp;
+
+       copy = (char *) malloc(strlen(p)+1);
+       copyp = copy;
+       while (*p == '/') {
+               /* remove leading slashes */
+               p++;
+       }
+       while (*p != '\0') {
+               /* this loop iterates once per filename component in p.
+                * both p (and copyp 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 */
+                               ;
+                       }
+               } else if ((*p == '.') && (*(p+1) == '.') &&
+                           ((*(p+2) == '/') || (*(p+2) == '\0'))) {
+                       /* skip ".." component followed by slash or end */
+                       p += 2;
+                       if (*p == '/')
+                               p++;
+                       if (copyp != copy) {
+                               /* back up the copy one level */
+                               --copyp; /* now pointing at slash */
+                               while ((copyp > copy) && (*(copyp - 1) != '/')) {
+                                       /* skip back up to slash */
+                                       copyp--;
+                               }
+                       }
+               } else {
+                       while (1) {
+                               /* copy one component through next slash */
+                               *copyp++ = *p++;
+                               if ((*p == '\0') || (*(p-1) == '/')) {
+                                       while (*p == '/') {
+                                               /* skip multiple slashes */
+                                               p++;
+                                       }
+                                       break;
+                               }
+                       }
+               }
+       }
+       if (copyp == copy) {
+               /* ended up with nothing, so put in "." component */
+               *copyp++ = '.';
+       }
+       *copyp = '\0';
+       return(copy);
+}
+
 
 static char curr_dir[MAXPATHLEN];
 
@@ -601,10 +675,10 @@ char *push_dir(char *dir, int 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);
@@ -623,7 +697,7 @@ int pop_dir(char *dir)
                return ret;
        }
 
-       strlcpy(curr_dir, dir, sizeof(curr_dir)-1);
+       strlcpy(curr_dir, dir, sizeof(curr_dir));
 
        free(dir);
 
@@ -716,52 +790,6 @@ int unsafe_symlink(char *dest, char *src)
        return (depth < 0);
 }
 
-/*
- * Make path appear as if a chroot had occurred:
- *    1. remove leading "/" (or replace with "." if at end)
- *    2. remove leading ".." components
- *    3. delete any other "<dir>/.." (recursively)
- * Return a malloc'ed copy.
- * Contributed by Dave Dykstra <dwd@bell-labs.com>
- */
-
-char *sanitize_path(char *p)
-{
-       char *copy, *copyp;
-
-       copy = (char *) malloc(strlen(p)+1);
-       copyp = copy;
-       while (*p != '\0') {
-               if ((*p == '/') && (copyp == copy)) {
-                       /* remove leading slash */
-                       p++;
-               }
-               else if ((*p == '.') && (*(p+1) == '.') &&
-                           ((*(p+2) == '/') || (*(p+2) == '\0'))) {
-                       /* remove .. followed by slash or end */
-                       p += 2;
-                       if (copyp != copy) {
-                               /* backup the copy one level */
-                               while ((--copyp != copy) && (*copyp == '/'))
-                                       /* skip trailing slashes */
-                                       ;
-                               while ((copyp != copy) && (*copyp != '/'))
-                                       /* skip back through slash */
-                                       copyp--;
-                       }
-               } else {
-                       /* copy one component */
-                       while (1) {
-                               *copyp++ = *p++;
-                               if ((*p == '\0') || (*(p-1) == '/'))
-                                       break;
-                       }
-               }
-       }
-       *copyp = '\0';
-       return(copy);
-}
-
 
 /****************************************************************************
   return the date and time as a string
@@ -774,7 +802,7 @@ char *timestring(time_t t)
 #ifdef HAVE_STRFTIME
        strftime(TimeBuf,sizeof(TimeBuf)-1,"%Y/%m/%d %T",tm);
 #else
-       strlcpy(TimeBuf, asctime(tm), sizeof(TimeBuf)-1);
+       strlcpy(TimeBuf, asctime(tm), sizeof(TimeBuf));
 #endif
 
        if (TimeBuf[strlen(TimeBuf)-1] == '\n') {