From d991f23d451c0c862ba7d14144f4ea44e1fb2a88 Mon Sep 17 00:00:00 2001 From: "job@openbsd.org" Date: Mon, 7 Sep 2026 20:24:22 +0000 Subject: [PATCH] upstream: Add '-p' to sftp mkdir/lmkdir to create directories as required The -p option causes the mkdir and lmkdir commands to create any missing intermediate directories. If '-p' is specified, it is not considered an error if a directory already exists. With / OK djm@ OpenBSD-Commit-ID: 72e4d4cf2e254959af5a0885e3f3a176846f4345 --- misc.c | 49 ++++++++++++++++++++++++++++++++++++++++++++++++- misc.h | 3 ++- sftp-client.c | 35 ++++++++++++++++++++++++++++++++++- sftp-client.h | 5 ++++- sftp.1 | 22 ++++++++++++++++++---- sftp.c | 49 +++++++++++++++++++++++++++++++++++++++++++------ 6 files changed, 149 insertions(+), 14 deletions(-) diff --git a/misc.c b/misc.c index 517fa7a97..ca8aecc67 100644 --- a/misc.c +++ b/misc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: misc.c,v 1.215 2026/06/21 19:23:56 tb Exp $ */ +/* $OpenBSD: misc.c,v 1.216 2026/09/07 20:24:22 job Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * Copyright (c) 2005-2020 Damien Miller. All rights reserved. @@ -3227,3 +3227,50 @@ get_homedir(void) return NULL; } + +int +mkdir_path(const char *target, mode_t mode) +{ + char *dir, *odir = NULL, *next; + int fd = AT_FDCWD, fd2, subpath_len, ret = -1; + + dir = odir = xstrdup(target); + + if (*dir == '/' && + (fd = open("/", O_RDONLY|O_DIRECTORY)) == -1) { + error_f("open(\"/\"): %s", strerror(errno)); + return -1; + } + /* Work through the path, component-wise */ + for (; dir != NULL && *dir != '\0'; dir = next) { + if ((next = strchr(dir, '/')) != NULL) + *(next++) = '\0'; + if (*dir == '\0') + continue; + subpath_len = (next == NULL) ? INT_MAX : next - odir - 1; + if (mkdirat(fd, dir, mode) == 0) + debug_f("created directory %.*s", subpath_len, target); + else if (errno != EEXIST) { + error_f("mkdir(\"%.*s\"): %s", + subpath_len, target, strerror(errno)); + goto out; + } + + /* descend */ + if ((fd2 = openat(fd, dir, O_RDONLY|O_DIRECTORY)) == -1) { + error_f("open(\"%.*s\"): %s", + subpath_len, target, strerror(errno)); + goto out; + } + if (fd != AT_FDCWD) + close(fd); + fd = fd2; + } + /* success */ + ret = 0; + out: + free(odir); + if (fd != AT_FDCWD) + close(fd); + return ret; +} diff --git a/misc.h b/misc.h index 791876c1e..c9afe0eca 100644 --- a/misc.h +++ b/misc.h @@ -1,4 +1,4 @@ -/* $OpenBSD: misc.h,v 1.116 2026/03/11 09:10:59 dtucker Exp $ */ +/* $OpenBSD: misc.h,v 1.117 2026/09/07 20:24:22 job Exp $ */ /* * Author: Tatu Ylonen @@ -113,6 +113,7 @@ int path_absolute(const char *); int stdfd_devnull(int, int, int); int lib_contains_symbol(const char *, const char *); char *get_homedir(void); +int mkdir_path(const char *, mode_t); void sock_set_v6only(int); diff --git a/sftp-client.c b/sftp-client.c index 1f031128e..e001b53da 100644 --- a/sftp-client.c +++ b/sftp-client.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sftp-client.c,v 1.186 2026/06/29 01:53:21 djm Exp $ */ +/* $OpenBSD: sftp-client.c,v 1.187 2026/09/07 20:24:22 job Exp $ */ /* * Copyright (c) 2001-2004 Damien Miller * @@ -880,6 +880,39 @@ sftp_mkdir(struct sftp_conn *conn, const char *path, Attrib *a, int print_flag) return status == SSH2_FX_OK ? 0 : -1; } +int +sftp_mkpath(struct sftp_conn *conn, const char *path, Attrib *a, int print_flag) +{ + char *slash, *tmp_path; + int done; + + tmp_path = xstrdup(path); + slash = tmp_path; + + for (;;) { + slash += strspn(slash, "/"); + slash += strcspn(slash, "/"); + + done = (*slash == '\0'); + *slash = '\0'; + + if (!sftp_remote_is_dir(conn, tmp_path)) { + if (sftp_mkdir(conn, tmp_path, a, print_flag) != 0) { + free(tmp_path); + return -1; + } + } + + if (done) + break; + + *slash = '/'; + } + + free(tmp_path); + return 0; +} + int sftp_rmdir(struct sftp_conn *conn, const char *path) { diff --git a/sftp-client.h b/sftp-client.h index cc8e20298..dc7c28e6c 100644 --- a/sftp-client.h +++ b/sftp-client.h @@ -1,4 +1,4 @@ -/* $OpenBSD: sftp-client.h,v 1.41 2026/03/03 09:57:25 dtucker Exp $ */ +/* $OpenBSD: sftp-client.h,v 1.42 2026/09/07 20:24:22 job Exp $ */ /* * Copyright (c) 2001-2004 Damien Miller @@ -87,6 +87,9 @@ int sftp_rm(struct sftp_conn *, const char *); /* Create directory 'path' */ int sftp_mkdir(struct sftp_conn *, const char *, Attrib *, int); +/* Create directory 'path' and intermediate directories as required */ +int sftp_mkpath(struct sftp_conn *, const char *, Attrib *, int); + /* Remove directory 'path' */ int sftp_rmdir(struct sftp_conn *, const char *); diff --git a/sftp.1 b/sftp.1 index 651baaf85..a30338ba5 100644 --- a/sftp.1 +++ b/sftp.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: sftp.1,v 1.144 2024/12/06 15:12:56 djm Exp $ +.\" $OpenBSD: sftp.1,v 1.145 2026/09/07 20:24:22 job Exp $ .\" .\" Copyright (c) 2001 Damien Miller. All rights reserved. .\" @@ -22,7 +22,7 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.Dd $Mdocdate: December 6 2024 $ +.Dd $Mdocdate: September 7 2026 $ .Dt SFTP 1 .Os .Sh NAME @@ -564,9 +564,16 @@ command. may contain .Xr glob 7 characters and may match multiple files. -.It Ic lmkdir Ar path +.It Xo Ic lmkdir +.Op Fl p +.Ar path +.Xc Create local directory specified by .Ar path . +If the +.Fl p +flag is specified create intermediate directories as required. +Do not consider it an error if the argument directory already exists. .It Xo Ic ln .Op Fl s .Ar oldpath @@ -629,9 +636,16 @@ Sort the listing by last modification time. .It Ic lumask Ar umask Set local umask to .Ar umask . -.It Ic mkdir Ar path +.It Xo Ic mkdir +.Op Fl p +.Ar path +.Xc Create remote directory specified by .Ar path . +If the +.Fl p +flag is specified create intermediate directories as required. +Do not consider it an error if the argument directory already exists. .It Ic progress Toggle display of progress meter. .It Xo Ic put diff --git a/sftp.c b/sftp.c index 31d8fa494..eba4acfdd 100644 --- a/sftp.c +++ b/sftp.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sftp.c,v 1.257 2026/06/30 02:30:19 djm Exp $ */ +/* $OpenBSD: sftp.c,v 1.258 2026/09/07 20:24:22 job Exp $ */ /* * Copyright (c) 2001-2004 Damien Miller * @@ -288,12 +288,12 @@ help(void) "help Display this help text\n" "lcd path Change local directory to 'path'\n" "lls [ls-options [path]] Display local directory listing\n" - "lmkdir path Create local directory\n" + "lmkdir [-p] path Create local directory\n" "ln [-s] oldpath newpath Link remote file (-s for symlink)\n" "lpwd Print local working directory\n" "ls [-1afhlnrSt] [path] Display remote directory listing\n" "lumask umask Set local umask to 'umask'\n" - "mkdir path Create remote directory\n" + "mkdir [-p] path Create remote directory\n" "progress Toggle display of progress meter\n" "put [-afpR] local [remote] Upload file\n" "pwd Display remote working directory\n" @@ -417,6 +417,30 @@ parse_getput_flags(const char *cmd, char **argv, int argc, return optind; } +static int +parse_mkdir_flags(const char *cmd, char **argv, int argc, int *pflag) +{ + extern int opterr, optind, optopt, optreset; + int ch; + + optind = optreset = 1; + opterr = 0; + + *pflag = 0; + while ((ch = getopt(argc, argv, "p")) != -1) { + switch (ch) { + case 'p': + *pflag = 1; + break; + default: + error("%s: Invalid flag -%c", cmd, optopt); + return -1; + } + } + + return optind; +} + static int parse_link_flags(const char *cmd, char **argv, int argc, int *sflag) { @@ -1455,16 +1479,21 @@ parse_args(const char **cpp, int *ignore_errors, int *disable_echo, int *aflag, undo_glob_escape(*path1); undo_glob_escape(*path2); break; - case I_RM: case I_MKDIR: - case I_RMDIR: case I_LMKDIR: + if ((optidx = parse_mkdir_flags(cmd, argv, argc, pflag)) == -1) + return -1; + path1_mandatory = 1; + goto parse_one_path; + case I_RM: + case I_RMDIR: path1_mandatory = 1; /* FALLTHROUGH */ case I_CHDIR: case I_LCHDIR: if ((optidx = parse_no_flags(cmd, argv, argc)) == -1) return -1; + parse_one_path: /* Get pathname (mandatory) */ if (argc - optidx < 1) { if (!path1_mandatory) @@ -1639,7 +1668,10 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd, attrib_clear(&a); a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS; a.perm = 0777; - err = sftp_mkdir(conn, path1, &a, 1); + if (pflag) + err = sftp_mkpath(conn, path1, &a, 1); + else + err = sftp_mkdir(conn, path1, &a, 1); break; case I_RMDIR: path1 = sftp_make_absolute(path1, *pwd); @@ -1708,6 +1740,11 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd, } break; case I_LMKDIR: + if (pflag) { + if (mkdir_path(path1, 0777) != 0) + err = 1; + break; + } if (mkdir(path1, 0777) == -1) { error("Couldn't create local directory " "\"%s\": %s", path1, strerror(errno));