*
* Derived from GNU C's cccp.c.
*/
-static int full_write(int desc, char *ptr, size_t len)
+int full_write(int desc, char *ptr, size_t len)
{
int total_written;
/** Copy a file.
*
- * This is used in conjunction with the --temp-dir option */
+ * This is used in conjunction with the --temp-dir and --backup options */
int copy_file(char *source, char *dest, mode_t mode)
{
int ifd;
#endif
}
-/* Returns 0 on success, -1 on most errors, and -2 if we got an error
- * trying to copy the file across file systems. */
+/* Returns 0 on successful rename, 1 if we successfully copied the file
+ * across filesystems, -2 if copy_file() failed, and -1 on other errors. */
int robust_rename(char *from, char *to, int mode)
{
int tries = 4;
if (copy_file(from, to, mode) != 0)
return -2;
do_unlink(from);
- return 0;
+ return 1;
default:
return -1;
}
s = ".";
if (sanitize_paths)
- s = sanitize_path(NULL, s, NULL);
+ s = sanitize_path(NULL, s, "", 0);
else
s = strdup(s);
return ret;
}
-unsigned int clean_fname(char *name)
+int count_dir_elements(const char *p)
{
- char *limit = name, *t = name, *f = name;
+ int cnt = 0, new_component = 1;
+ while (*p) {
+ if (*p++ == '/')
+ new_component = 1;
+ else if (new_component) {
+ new_component = 0;
+ cnt++;
+ }
+ }
+ return cnt;
+}
+
+/* Turns multiple adjacent slashes into a single slash, gets rid of "./"
+ * elements (but not a trailing dot dir), removes a trailing slash, and
+ * optionally collapses ".." elements (except for those at the start of the
+ * string). If the resulting name would be empty, change it into a ".". */
+unsigned int clean_fname(char *name, BOOL collapse_dot_dot)
+{
+ char *limit = name - 1, *t = name, *f = name;
int anchored;
if (!name)
if (*f == '.') {
/* discard "." dirs (but NOT a trailing '.'!) */
if (f[1] == '/') {
- f++; /* not += 2! */
+ f += 2;
continue;
}
/* collapse ".." dirs */
- if (f[1] == '.' && (f[2] == '/' || !f[2])) {
+ if (collapse_dot_dot
+ && f[1] == '.' && (f[2] == '/' || !f[2])) {
char *s = t - 1;
if (s == name && anchored) {
f += 2;
continue;
}
while (s > limit && *--s != '/') {}
- if (s != t - 1 && *s == '/') {
+ if (s != t - 1 && (s < name || *s == '/')) {
t = s + 1;
f += 2;
continue;
}
- *t++ = *f++;
- *t++ = *f++;
- limit = t;
+ limit = t + 2;
}
}
while (*f && (*t++ = *f++) != '/') {}
* "/" (either removing it or expanding it) and any leading or embedded
* ".." components that attempt to escape past the module's top dir.
*
- * If dest is NULL, a buffer is allocated to hold the result. If dest is
- * the same buffer as p (the path) OR if reldir is NULL, a leading slash
- * is dropped instead of being expanded to be the module's top dir.
+ * If dest is NULL, a buffer is allocated to hold the result. It is legal
+ * to call with the dest and the path (p) pointing to the same buffer, but
+ * rootdir will be ignored to avoid expansion of the string.
*
- * If reldir is non-NULL (and non-empty), it is a sanitized directory that
- * the path will be relative to, so allow as many '..'s 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.
+ * The rootdir string contains a value to use in place of a leading slash.
+ * Specify NULL to get the default of lp_path(module_id).
*
- * 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 depth is >= 0, it is a count of how many '..'s to allow at the start
+ * of the path. Use -1 to allow unlimited depth.
*
- * If the resulting path would be empty, change it into ".".
- */
-char *sanitize_path(char *dest, const char *p, const char *reldir)
+ * We also clean the path in a manner similar to clean_fname() but with a
+ * few differences:
+ *
+ * Turns multiple adjacent slashes into a single slash, gets rid of "." dir
+ * elements (INCLUDING a trailing dot dir), PRESERVES a trailing slash, and
+ * ALWAYS collapses ".." elements (except for those at the start of the
+ * string up to "depth" deep). If the resulting name would be empty,
+ * change it into a ".". */
+char *sanitize_path(char *dest, const char *p, const char *rootdir, int depth)
{
char *start, *sanp;
- int depth = 0;
- int allowdotdot = 0;
int rlen = 0;
if (dest != p) {
int plen = strlen(p);
- if (*p == '/' && reldir) {
- rlen = strlen(lp_path(module_id));
- reldir = NULL;
+ if (*p == '/') {
+ if (!rootdir)
+ rootdir = lp_path(module_id);
+ rlen = strlen(rootdir);
+ depth = 0;
p++;
}
if (dest) {
} else if (!(dest = new_array(char, rlen + plen + 1)))
out_of_memory("sanitize_path");
if (rlen) {
- memcpy(dest, lp_path(module_id), rlen);
+ memcpy(dest, rootdir, rlen);
if (rlen > 1)
dest[rlen++] = '/';
}
}
- if (reldir) {
- int new_component = 1;
- while (*reldir) {
- if (*reldir++ == '/')
- new_component = 1;
- else if (new_component) {
- new_component = 0;
- depth++;
- }
- }
- }
-
start = sanp = dest + rlen;
- while (*p == '/') {
- /* remove leading slashes */
- p++;
- }
while (*p != '\0') {
+ /* discard leading or extra slashes */
+ if (*p == '/') {
+ p++;
+ continue;
+ }
/* 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 */
- ;
- }
+ p++;
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 {
+ if (depth <= 0 || sanp != start) {
p += 2;
- if (*p == '/')
- p++;
if (sanp != start) {
/* back up sanp one level */
--sanp; /* now pointing at slash */
}
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) {
+ /* allow depth levels of .. at the beginning */
+ depth--;
/* move the virtual beginning to leave the .. alone */
- start = sanp;
+ start = sanp + 3;
}
+ /* copy one component through next slash */
+ while (*p && (*sanp++ = *p++) != '/') {}
}
if (sanp == dest) {
/* ended up with nothing, so put in "." component */
curr_dir_len += len;
}
- curr_dir_len = clean_fname(curr_dir);
+ curr_dir_len = clean_fname(curr_dir, 1);
return 1;
}