15825705 SUNBT7206929 all of the ftp test cases with "-f" are timeouted in krb5_r_apps s11u1-sru 0.175.1.18.0.1.0 S11.1SRU18.1
authorTomas Klacko <tomas.klacko@oracle.com>
Fri, 28 Feb 2014 11:15:50 +0100
branchs11u1-sru
changeset 2973 4192dd72f273
parent 2970 01b1a705eae1
child 2976 46cf3ccb4af4
15825705 SUNBT7206929 all of the ftp test cases with "-f" are timeouted in krb5_r_apps 17599705 proftpd NLST ../ returns ./
components/proftpd/patches/15825705.patch
components/proftpd/patches/17599705.patch
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/proftpd/patches/15825705.patch	Fri Feb 28 11:15:50 2014 +0100
@@ -0,0 +1,414 @@
+http://bugs.proftpd.org/show_bug.cgi?id=4014
+
+I addition, this patch backports the pr_str_get_nbytes() function.
+
+diff --git a/include/options.h b/include/options.h
+index 96fa35d..0b29bf7 100644
+--- a/include/options.h
++++ b/include/options.h
+@@ -110,6 +110,13 @@
+ # define PR_TUNABLE_XFER_BUFFER_SIZE	PR_TUNABLE_BUFFER_SIZE
+ #endif
+ 
++/* Maximum FTP command size.  For details on this size of 512KB, see
++ * the Bug#4014 discussion.
++ */
++#ifndef PR_TUNABLE_CMD_BUFFER_SIZE
++# define PR_TUNABLE_CMD_BUFFER_SIZE    (512 * 1024)
++#endif
++
+ /* Maximum path length.  GNU HURD (and some others) do not define
+  * MAXPATHLEN.  POSIX' PATH_MAX is mandated to be at least 256 
+  * (according to some), so 1K, in the absense of MAXPATHLEN, should be
+diff --git a/include/str.h b/include/str.h
+index b4fed7c..1cf8724 100644
+--- a/include/str.h
++++ b/include/str.h
+@@ -39,6 +39,7 @@ char *pstrndup(pool *, const char *, size_t);
+ 
+ char *pr_str_strip(pool *, char *);
+ char *pr_str_strip_end(char *, char *);
++int pr_str_get_nbytes(const char *, const char *, off_t *);
+ char *pr_str_get_word(char **, int);
+ 
+ #define PR_STR_FL_PRESERVE_COMMENTS		0x0001
+diff --git a/modules/mod_core.c b/modules/mod_core.c
+index 18a47c2..922f4d1 100644
+--- a/modules/mod_core.c
++++ b/modules/mod_core.c
+@@ -2240,18 +2240,44 @@ MODRET set_allowforeignaddress(cmd_rec *cmd) {
+ }
+ 
+ MODRET set_commandbuffersize(cmd_rec *cmd) {
+-  int size = 0;
++  int res;
++  size_t size = 0;
++  off_t nbytes = 0;
+   config_rec *c = NULL;
++  const char *units = NULL;
++
++  if (cmd->argc < 2 || cmd->argc > 3) {
++    CONF_ERROR(cmd, "wrong number of parameters")
++  }
+ 
+-  CHECK_ARGS(cmd, 1);
+   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
+ 
+-  /* NOTE: need to add checks for maximum possible sizes, negative sizes. */
+-  size = atoi(cmd->argv[1]);
++  if (cmd->argc == 3) {
++    units = cmd->argv[2];
++  }
++
++  if (pr_str_get_nbytes(cmd->argv[1], units, &nbytes) < 0) {
++    CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to parse: ",
++      cmd->argv[1], " ", units, ": ", strerror(errno), NULL));
++  }
++
++  if (nbytes > PR_TUNABLE_CMD_BUFFER_SIZE) {
++    char max[1024];
++
++    snprintf(max, sizeof(max)-1, "%lu", (unsigned long)
++      PR_TUNABLE_CMD_BUFFER_SIZE);
++    max[sizeof(max)-1] = '\0';
++
++    CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "size ", cmd->argv[1], units,
++      "exceeds max size ", max, NULL));
++  }
++
++  /* Possible truncation here, but only for an absurdly large size. */
++  size = (size_t) nbytes;
+ 
+   c = add_config_param(cmd->argv[0], 1, NULL);
+-  c->argv[0] = pcalloc(c->pool, sizeof(int));
+-  *((int *) c->argv[0]) = size;
++  c->argv[0] = pcalloc(c->pool, sizeof(size_t));
++  *((size_t *) c->argv[0]) = size;
+ 
+   return PR_HANDLED(cmd);
+ }
+diff --git a/src/main.c b/src/main.c
+index 3e6d637..660e14b 100644
+--- a/src/main.c
++++ b/src/main.c
+@@ -466,42 +466,21 @@ static int _dispatch(cmd_rec *cmd, int cmd_type, int validate, char *match) {
+   return success;
+ }
+ 
+-/* Returns the appropriate maximum buffer length to use for FTP commands
+- * from the client, taking the CommandBufferSize directive into account.
++/* Returns the appropriate maximum buffer size to use for FTP commands
++ * from the client.
+  */
+-static long get_max_cmd_len(size_t buflen) {
+-  long res;
+-  int *bufsz = NULL;
+-  size_t default_cmd_bufsz;
+-
+-  /* It's possible for the admin to select a PR_TUNABLE_BUFFER_SIZE which
+-   * is smaller than PR_DEFAULT_CMD_BUFSZ.  We need to handle such cases
+-   * properly.
+-   */
+-  default_cmd_bufsz = PR_DEFAULT_CMD_BUFSZ;
+-  if (default_cmd_bufsz > buflen) {
+-    default_cmd_bufsz = buflen;
+-  }
+- 
++static size_t get_max_cmd_sz(void) {
++  size_t res;
++  size_t *bufsz = NULL;
++
+   bufsz = get_param_ptr(main_server->conf, "CommandBufferSize", FALSE);
+   if (bufsz == NULL) {
+-    res = default_cmd_bufsz;
+-
+-  } else if (*bufsz <= 0) {
+-    pr_log_pri(PR_LOG_WARNING, "invalid CommandBufferSize size (%d) given, "
+-      "using default buffer size (%lu) instead", *bufsz,
+-      (unsigned long) default_cmd_bufsz);
+-    res = default_cmd_bufsz;
+-
+-  } else if (*bufsz + 1 > buflen) {
+-    pr_log_pri(PR_LOG_WARNING, "invalid CommandBufferSize size (%d) given, "
+-      "using default buffer size (%lu) instead", *bufsz,
+-      (unsigned long) default_cmd_bufsz);
+-    res = default_cmd_bufsz;
++    res = PR_DEFAULT_CMD_BUFSZ;
+ 
+   } else {
+-    pr_log_debug(DEBUG1, "setting CommandBufferSize to %d", *bufsz);
+-    res = (long) *bufsz;
++    pr_log_debug(DEBUG1, "setting CommandBufferSize to %lu",
++      (unsigned long) *bufsz);
++    res = *bufsz;
+   }
+ 
+   return res;
+@@ -509,21 +488,29 @@ static long get_max_cmd_len(size_t buflen) {
+ 
+ int pr_cmd_read(cmd_rec **res) {
+   static long cmd_bufsz = -1;
+-  char buf[PR_DEFAULT_CMD_BUFSZ+1] = {'\0'};
++  static char *cmd_buf = NULL;
+   char *cp;
+-  size_t buflen;
++  size_t cmd_buflen;
+ 
+   if (res == NULL) {
+     errno = EINVAL;
+     return -1;
+   }
+ 
++  if (cmd_bufsz == -1) {
++    cmd_bufsz = get_max_cmd_sz();
++  }
++
++  if (cmd_buf == NULL) {
++    cmd_buf = pcalloc(session.pool, cmd_bufsz + 1);
++  }
++
+   while (TRUE) {
+     pr_signals_handle();
+ 
+-    memset(buf, '\0', sizeof(buf));
++    memset(cmd_buf, '\0', cmd_bufsz);
+ 
+-    if (pr_netio_telnet_gets(buf, sizeof(buf)-1, session.c->instrm,
++    if (pr_netio_telnet_gets(cmd_buf, cmd_bufsz, session.c->instrm,
+         session.c->outstrm) == NULL) {
+ 
+       if (errno == E2BIG) {
+@@ -544,9 +531,6 @@ int pr_cmd_read(cmd_rec **res) {
+     break;
+   }
+ 
+-  if (cmd_bufsz == -1)
+-    cmd_bufsz = get_max_cmd_len(sizeof(buf));
+-
+   /* This strlen(3) is guaranteed to terminate; the last byte of buf is
+    * always NUL, since pr_netio_telnet_gets() is told that the buf size is
+    * one byte less than it really is.
+@@ -554,26 +538,28 @@ int pr_cmd_read(cmd_rec **res) {
+    * If the strlen(3) says that the length is less than the cmd_bufsz, then
+    * there is no need to truncate the buffer by inserting a NUL.
+    */
+-  buflen = strlen(buf);
+-  if (buflen > (cmd_bufsz - 1)) {
++  cmd_buflen = strlen(cmd_buf);
++  if (cmd_buflen > cmd_bufsz) {
+     pr_log_debug(DEBUG0, "truncating incoming command length (%lu bytes) to "
+       "CommandBufferSize %lu; use the CommandBufferSize directive to increase "
+-      "the allowed command length", (unsigned long) buflen,
++      "the allowed command length", (unsigned long) cmd_buflen,
+       (unsigned long) cmd_bufsz);
+-    buf[cmd_bufsz - 1] = '\0';
++    cmd_buf[cmd_bufsz-1] = '\0';
+   }
+ 
+-  if (buflen &&
+-      (buf[buflen-1] == '\n' || buf[buflen-1] == '\r')) {
+-    buf[buflen-1] = '\0';
+-    buflen--;
++  if (cmd_buflen &&
++      (cmd_buf[cmd_buflen-1] == '\n' || cmd_buf[cmd_buflen-1] == '\r')) {
++    cmd_buf[cmd_buflen-1] = '\0';
++    cmd_buflen--;
+ 
+-    if (buflen &&
+-        (buf[buflen-1] == '\n' || buf[buflen-1] =='\r'))
+-      buf[buflen-1] = '\0';
++    if (cmd_buflen &&
++        (cmd_buf[cmd_buflen-1] == '\n' || cmd_buf[cmd_buflen-1] =='\r')) {
++      cmd_buf[cmd_buflen-1] = '\0';
++      cmd_buflen--;
++    }
+   }
+ 
+-  cp = buf;
++  cp = cmd_buf;
+   if (*cp == '\r')
+     cp++;
+ 
+@@ -587,11 +573,11 @@ int pr_cmd_read(cmd_rec **res) {
+      * command handlers themselves, via cmd->arg.  This small hack
+      * reduces the burden on SITE module developers, however.
+      */
+-    if (strncasecmp(cp, C_SITE, 4) == 0)
++    if (strncasecmp(cp, C_SITE, 4) == 0) {
+       flags |= PR_STR_FL_PRESERVE_WHITESPACE;
++    }
+ 
+     cmd = make_ftp_cmd(session.pool, cp, flags);
+-
+     if (cmd) {
+       *res = cmd;
+     } 
+diff --git a/src/str.c b/src/str.c
+index d243a17..4f327bf 100644
+--- a/src/str.c
++++ b/src/str.c
+@@ -367,6 +367,81 @@ char *pr_str_strip_end(char *s, char *ch) {
+   return s;
+ }
+ 
++int pr_str_get_nbytes(const char *str, const char *units, off_t *nbytes) {
++  off_t sz;
++  char *ptr = NULL;
++  float factor = 0.0;
++
++  if (str == NULL) {
++    errno = EINVAL;
++    return -1;
++  }
++
++  /* No negative numbers. */
++  if (*str == '-') {
++    errno = EINVAL;
++    return -1;
++  }
++
++  if (units == NULL ||
++      *units == '\0') {
++    factor = 1.0;
++
++  } else if (strncasecmp(units, "KB", 3) == 0) {
++    factor = 1024.0;
++
++  } else if (strncasecmp(units, "MB", 3) == 0) {
++    factor = 1024.0 * 1024.0;
++
++  } else if (strncasecmp(units, "GB", 3) == 0) {
++    factor = 1024.0 * 1024.0 * 1024.0;
++
++  } else if (strncasecmp(units, "TB", 3) == 0) {
++    factor = 1024.0 * 1024.0 * 1024.0 * 1024.0;
++
++  } else if (strncasecmp(units, "B", 2) == 0) {
++    factor = 1.0;
++
++  } else {
++    errno = EINVAL;
++    return -1;
++  }
++
++  errno = 0;
++
++#ifdef HAVE_STRTOULL
++  sz = strtoull(str, &ptr, 10);
++#else
++  sz = strtoul(str, &ptr, 10);
++#endif /* !HAVE_STRTOULL */
++
++  if (errno == ERANGE) {
++    return -1;
++  }
++
++  if (ptr != NULL && *ptr) {
++    /* Error parsing the given string */
++    errno = EINVAL;
++    return -1;
++  }
++
++  /* Don't bother applying the factor if the result will overflow the result. */
++#ifdef ULLONG_MAX
++  if (sz > (ULLONG_MAX / factor)) {
++#else
++  if (sz > (ULONG_MAX / factor)) {
++#endif /* !ULLONG_MAX */
++    errno = ERANGE;
++    return -1;
++  }
++
++  if (nbytes != NULL) {
++    *nbytes = (off_t) (sz * factor);
++  }
++
++  return 0;
++}
++
+ char *pr_str_get_word(char **cp, int flags) {
+   char *res, *dst;
+   char quote_mode = 0;
+diff --git a/tests/t/lib/ProFTPD/Tests/Config/CommandBufferSize.pm b/tests/t/lib/ProFTPD/Tests/Config/CommandBufferSize.pm
+index ed4672a..a57c898 100644
+--- a/tests/t/lib/ProFTPD/Tests/Config/CommandBufferSize.pm
++++ b/tests/t/lib/ProFTPD/Tests/Config/CommandBufferSize.pm
+@@ -94,6 +94,8 @@ sub cmdbuffersz_small {
+     die("Can't open $test_path: $!");
+   }
+ 
++  my $idle_timeout = 3;
++
+   my $config = {
+     PidFile => $pid_file,
+     ScoreboardFile => $scoreboard_file,
+@@ -103,6 +105,7 @@ sub cmdbuffersz_small {
+     AuthGroupFile => $auth_group_file,
+ 
+     CommandBufferSize => $cmdbufsz,
++    TimeoutIdle => $idle_timeout,
+ 
+     IfModules => {
+       'mod_delay.c' => {
+@@ -128,44 +131,16 @@ sub cmdbuffersz_small {
+   defined(my $pid = fork()) or die("Can't fork: $!");
+   if ($pid) {
+     eval {
+-      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
++      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1);
+       $client->login($user, $passwd);
+ 
+-      my $conn = $client->list_raw($test_file);
+-      unless ($conn) {
+-        die("Failed to LIST $test_file: " . $client->response_code() . " " .
+-          $client->response_msg());
++      # Since our filename is longer than the CommandBufferSize, proftpd
++      # should simply ignore this.  It will fail because of the idle timeout
++      # first.
++      eval { $client->stat($test_file) };
++      unless ($@) {
++        die("STAT command succeeded unexpectedly");
+       }
+-
+-      my $buf;
+-      $conn->read($buf, 8192, 30);
+-      eval { $conn->close() };
+-
+-      my $resp_code = $client->response_code();
+-      my $resp_msg = $client->response_msg();
+-
+-      # CommandBufferSize works by truncating any input longer than the
+-      # configured length.  (It should arguably reject such longer input,
+-      # but that is a different consideration.)
+-      #
+-      # Since our file name is longer than the CommandBufferSize, it means
+-      # the path will be truncated, and the LIST should return 450.
+-
+-      my $expected;
+-
+-      $expected = 450;
+-      $self->assert($expected == $resp_code,
+-        test_msg("Expected $expected, got $resp_code"));
+-
+-      # This length is CommandBufferSize - "LIST"(4) - " "(1) - 1 for
+-      # the NUL reserved in the code.  Thus CommandBufferSize - 6.
+-      my $truncated_name = ("A" x ($cmdbufsz - 6));
+-
+-      $expected = "$truncated_name: No such file or directory";
+-      $self->assert($expected eq $resp_msg,
+-        test_msg("Expected '$expected', got '$resp_msg'"));
+-
+-      $client->quit();
+     };
+ 
+     if ($@) {
+@@ -176,7 +151,7 @@ sub cmdbuffersz_small {
+     $wfh->flush();
+ 
+   } else {
+-    eval { server_wait($config_file, $rfh) };
++    eval { server_wait($config_file, $rfh, 15) };
+     if ($@) {
+       warn($@);
+       exit 1;
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/proftpd/patches/17599705.patch	Fri Feb 28 11:15:50 2014 +0100
@@ -0,0 +1,315 @@
+http://bugs.proftpd.org/show_bug.cgi?id=4011
+
+diff --git a/include/fsio.h b/include/fsio.h
+index 07c18c1..a4c395e 100644
+--- a/include/fsio.h
++++ b/include/fsio.h
+@@ -189,6 +189,9 @@ struct fh_rec {
+   size_t fh_iosz;
+ };
+ 
++/* Maximum symlink count, for loop detection. */
++#define PR_FSIO_MAX_LINK_COUNT         32
++
+ /* Macros for that code that needs to get into the internals of pr_fs_t.
+  * (These will help keep the internals as opaque as possible).
+  */
+@@ -319,7 +322,11 @@ char *pr_fs_encode_path(pool *, const char *);
+ int pr_fs_use_encoding(int bool);
+ int pr_fs_valid_path(const char *);
+ void pr_fs_virtual_path(const char *, char *, size_t);
++
+ void pr_fs_clean_path(const char *, char *, size_t);
++int pr_fs_clean_path2(const char *, char *, size_t, int);
++#define PR_FSIO_CLEAN_PATH_FL_MAKE_ABS_PATH    0x001
++
+ int pr_fs_glob(const char *, int, int (*errfunc)(const char *, int), glob_t *);
+ void pr_fs_globfree(glob_t *);
+ void pr_resolve_fs_map(void);
+diff --git a/modules/mod_ls.c b/modules/mod_ls.c
+index 50e8035..58e81d7 100644
+--- a/modules/mod_ls.c
++++ b/modules/mod_ls.c
+@@ -2571,46 +2571,6 @@ MODRET ls_nlst(cmd_rec *cmd) {
+     }
+   }
+ 
+-  /* Clean the path. */
+-  if (*target != '/') {
+-    size_t cwdlen = strlen(pr_fs_getcwd());
+-
+-    pr_fs_clean_path(pdircat(cmd->tmp_pool, pr_fs_getcwd(), target, NULL),
+-      buf, sizeof(buf));
+-
+-    target = buf;
+-
+-    /* If the given target was not an absolute path, advance past the
+-     * current working directory prefix in the cleaned up target path.
+-     */
+-    target += cwdlen;
+-
+-    /* If the length of the current working directory (cwdlen) is one,
+-     * it means that the current working directory is the root ('/'),
+-     * and so we don't want to advance past that into the file name
+-     * portion of the path.
+-     */
+-    if (cwdlen > 1)
+-      target += 1;
+-
+-  } else {
+-    pr_fs_clean_path(target, buf, sizeof(buf));
+-    target = buf;
+-  }
+-
+-  /* Remove any trailing separators. */
+-  targetlen = strlen(target);
+-  while (targetlen >= 1 &&
+-         target[targetlen-1] == '/') {
+-
+-    if (strcmp(target, "/") == 0) {
+-      break;
+-    }
+-
+-    target[targetlen-1] = '\0';
+-    targetlen = strlen(target);
+-  }
+-
+   /* If the target is a glob, get the listing of files/dirs to send. */
+   if (use_globbing &&
+       pr_str_is_fnmatch(target)) {
+@@ -2715,12 +2675,36 @@ MODRET ls_nlst(cmd_rec *cmd) {
+     }
+ 
+   } else {
+-
+     /* A single target. If it's a directory, list the contents; if it's a
+      * file, just list the file.
+      */
+     struct stat st;
+ 
++    if (!is_dotdir(target)) {
++      /* Clean the path. */
++      if (*target != '/') {
++        pr_fs_clean_path2(target, buf, sizeof(buf), 0);
++
++      } else {
++        pr_fs_clean_path(target, buf, sizeof(buf));
++      }
++
++      target = buf;
++
++    } else {
++      /* Remove any trailing separators. */
++      targetlen = strlen(target);
++      while (targetlen >= 1 &&
++             target[targetlen-1] == '/') {
++        if (strncmp(target, "/", 2) == 0) {
++          break;
++        }
++
++        target[targetlen-1] = '\0';
++        targetlen = strlen(target);
++      }
++    }
++
+     if (!ls_perms_full(cmd->tmp_pool, cmd, target, &hidden)) {
+       int xerrno = errno;
+ 
+diff --git a/src/fsio.c b/src/fsio.c
+index 782168d..4d191fe 100644
+--- a/src/fsio.c
++++ b/src/fsio.c
+@@ -1627,8 +1627,8 @@ int pr_fs_resolve_partial(const char *path, char *buf, size_t buflen, int op) {
+ 
+   pr_fs_t *fs = NULL;
+   int len = 0, fini = 1, link_cnt = 0;
+-  ino_t last_inode = 0;
+-  dev_t last_device = 0;
++  ino_t prev_inode = 0;
++  dev_t prev_device = 0;
+   struct stat sbuf;
+ 
+   if (!path) {
+@@ -1740,16 +1740,16 @@ int pr_fs_resolve_partial(const char *path, char *buf, size_t buflen, int op) {
+         char linkpath[PR_TUNABLE_PATH_MAX + 1] = {'\0'};
+ 
+         /* Detect an obvious recursive symlink */
+-        if (sbuf.st_ino && (ino_t) sbuf.st_ino == last_inode &&
+-            sbuf.st_dev && (dev_t) sbuf.st_dev == last_device) {
++        if (sbuf.st_ino && (ino_t) sbuf.st_ino == prev_inode &&
++            sbuf.st_dev && (dev_t) sbuf.st_dev == prev_device) {
+           errno = ELOOP;
+           return -1;
+         }
+ 
+-        last_inode = (ino_t) sbuf.st_ino;
+-        last_device = (dev_t) sbuf.st_dev;
++        prev_inode = (ino_t) sbuf.st_ino;
++        prev_device = (dev_t) sbuf.st_dev;
+ 
+-        if (++link_cnt > 32) {
++        if (++link_cnt > PR_FSIO_MAX_LINK_COUNT) {
+           errno = ELOOP;
+           return -1;
+         }
+@@ -1820,8 +1820,8 @@ int pr_fs_resolve_path(const char *path, char *buf, size_t buflen, int op) {
+   pr_fs_t *fs = NULL;
+ 
+   int len = 0, fini = 1, link_cnt = 0;
+-  ino_t last_inode = 0;
+-  dev_t last_device = 0;
++  ino_t prev_inode = 0;
++  dev_t prev_device = 0;
+   struct stat sbuf;
+ 
+   if (!path) {
+@@ -1906,16 +1906,16 @@ int pr_fs_resolve_path(const char *path, char *buf, size_t buflen, int op) {
+         char linkpath[PR_TUNABLE_PATH_MAX + 1] = {'\0'};
+ 
+         /* Detect an obvious recursive symlink */
+-        if (sbuf.st_ino && (ino_t) sbuf.st_ino == last_inode &&
+-            sbuf.st_dev && (dev_t) sbuf.st_dev == last_device) {
++        if (sbuf.st_ino && (ino_t) sbuf.st_ino == prev_inode &&
++            sbuf.st_dev && (dev_t) sbuf.st_dev == prev_device) {
+           errno = ELOOP;
+           return -1;
+         }
+ 
+-        last_inode = (ino_t) sbuf.st_ino;
+-        last_device = (dev_t) sbuf.st_dev;
++        prev_inode = (ino_t) sbuf.st_ino;
++        prev_device = (dev_t) sbuf.st_dev;
+ 
+-        if (++link_cnt > 32) {
++        if (++link_cnt > PR_FSIO_MAX_LINK_COUNT) {
+           errno = ELOOP;
+           return -1;
+         }
+@@ -1977,22 +1977,33 @@ int pr_fs_resolve_path(const char *path, char *buf, size_t buflen, int op) {
+   return 0;
+ }
+ 
+-void pr_fs_clean_path(const char *path, char *buf, size_t buflen) {
++int pr_fs_clean_path2(const char *path, char *buf, size_t buflen, int flags) {
+   char workpath[PR_TUNABLE_PATH_MAX + 1] = {'\0'};
+   char curpath[PR_TUNABLE_PATH_MAX + 1]  = {'\0'};
+   char namebuf[PR_TUNABLE_PATH_MAX + 1]  = {'\0'};
+-  char *where = NULL, *ptr = NULL, *last = NULL;
+-  int fini = 1;
++  int fini = 1, have_abs_path = FALSE; 
+ 
+-  if (!path)
+-    return;
++  if (path == NULL ||
++      buf == NULL) {
++    errno = EINVAL;
++    return -1;
++  }
++
++  if (buflen == 0) {
++    return 0;
++  }
+ 
+   sstrncpy(curpath, path, sizeof(curpath));
+ 
++  if (*curpath == '/') {
++    have_abs_path = TRUE;
++  }
++
+   /* main loop */
+   while (fini--) {
+-    where = curpath;
++    char *where = NULL, *ptr = NULL, *last = NULL;
+ 
++    where = curpath;
+     while (*where != '\0') {
+       pr_signals_handle();
+ 
+@@ -2013,8 +2024,12 @@ void pr_fs_clean_path(const char *path, char *buf, size_t buflen) {
+         ptr = last = workpath;
+ 
+         while (*ptr) {
+-          if (*ptr == '/')
++          pr_signals_handle();
++
++          if (*ptr == '/') {
+             last = ptr;
++          }
++
+           ptr++;
+         }
+ 
+@@ -2028,34 +2043,46 @@ void pr_fs_clean_path(const char *path, char *buf, size_t buflen) {
+         ptr = last = workpath;
+ 
+         while (*ptr) {
+-          if (*ptr == '/')
++          pr_signals_handle();
++
++          if (*ptr == '/') {
+             last = ptr;
++          }
+           ptr++;
+         }
++
+         *last = '\0';
+         continue;
+       }
+-      ptr = strchr(where, '/');
+ 
+-      if (!ptr) {
++      ptr = strchr(where, '/');
++      if (ptr == NULL) {
+         size_t wherelen = strlen(where);
+ 
+         ptr = where;
+-        if (wherelen >= 1)
++        if (wherelen >= 1) {
+           ptr += (wherelen - 1);
++        }
+ 
+-      } else
++      } else {
+         *ptr = '\0';
++      }
+ 
+       sstrncpy(namebuf, workpath, sizeof(namebuf));
+ 
+       if (*namebuf) {
+         for (last = namebuf; *last; last++);
+-        if (*--last != '/')
++        if (*--last != '/') {
+           sstrcat(namebuf, "/", sizeof(namebuf)-1);
++        }
+ 
+-      } else
+-        sstrcat(namebuf, "/", sizeof(namebuf)-1);
++      } else {
++        if (have_abs_path ||
++            (flags & PR_FSIO_CLEAN_PATH_FL_MAKE_ABS_PATH)) {
++          sstrcat(namebuf, "/", sizeof(namebuf)-1);
++          have_abs_path = FALSE;
++        }
++      }
+ 
+       sstrcat(namebuf, where, sizeof(namebuf)-1);
+       namebuf[sizeof(namebuf)-1] = '\0';
+@@ -2066,10 +2093,16 @@ void pr_fs_clean_path(const char *path, char *buf, size_t buflen) {
+     }
+   }
+ 
+-  if (!workpath[0])
++  if (!workpath[0]) {
+     sstrncpy(workpath, "/", sizeof(workpath));
++  }
+ 
+   sstrncpy(buf, workpath, buflen);
++  return 0;
++}
++
++void pr_fs_clean_path(const char *path, char *buf, size_t buflen) {
++  pr_fs_clean_path2(path, buf, buflen, PR_FSIO_CLEAN_PATH_FL_MAKE_ABS_PATH);
+ }
+ 
+ int pr_fs_use_encoding(int bool) {
+