fix path handling

This commit is contained in:
Ryo Nakamura
2022-10-29 23:36:12 +09:00
parent 47f6983148
commit ff697aa514
4 changed files with 144 additions and 151 deletions

View File

@@ -3,6 +3,7 @@
#include <sys/stat.h>
#include <dirent.h>
#include <fcntl.h>
#include <libgen.h>
#include <ssh.h>
#include <util.h>
@@ -68,7 +69,7 @@ static char *file_find_path(char *path)
}
/* return 1 when path is directory, 0 is not directory, and -1 on error */
int file_is_directory(char *path, sftp_session sftp)
static int file_is_directory(char *path, sftp_session sftp, bool print_error)
{
int ret = 0;
@@ -79,8 +80,9 @@ int file_is_directory(char *path, sftp_session sftp)
char *p = *remote_path == '\0' ? "." : remote_path;
attr = sftp_stat(sftp, p);
if (!attr) {
pr_err("%s: %s\n", p,
ssh_get_error(sftp_ssh(sftp)));
if (print_error)
pr_err("sftp_stat %s: %s\n",
path, sftp_get_ssh_error(sftp));
ret = -1;
} else if (attr->type == SSH_FILEXFER_TYPE_DIRECTORY)
ret = 1;
@@ -88,9 +90,10 @@ int file_is_directory(char *path, sftp_session sftp)
} else {
struct stat statbuf;
if (stat(path, &statbuf) < 0) {
pr_err("%s: %s\n", path, strerrno());
if (print_error)
pr_err("stat %s: %s\n", path, strerrno());
ret = -1;
} else if ((statbuf.st_mode & S_IFMT) == S_IFDIR)
} else if (S_ISDIR(statbuf.st_mode))
ret = 1;
}
@@ -146,6 +149,7 @@ static struct file *file_alloc(char *path, size_t size, bool remote)
strncpy(f->path, path, PATH_MAX - 1);
f->size = size;
f->remote = remote;
f->dst_remote = !remote;
lock_init(&f->lock);
return f;
@@ -162,161 +166,160 @@ static bool file_should_skip(char *path)
}
static int file_fill_local_recursive(char *path, struct list_head *head)
/* return -1 when error, 0 when should skip, and 1 when should be copied */
static int check_file_tobe_copied(char *path, sftp_session sftp, size_t *size)
{
char child[PATH_MAX];
struct stat statbuf;
struct dirent *de;
DIR *dir;
sftp_attributes attr;
int ret = 0;
if (!sftp) {
/* local */
if (stat(path, &statbuf) < 0) {
pr_err("failed to get stat for %s: %s\n", path, strerrno());
return -1;
}
if (S_ISREG(statbuf.st_mode)) {
*size = statbuf.st_size;
return 1;
}
return 0;
}
/* remote */
attr = sftp_stat(sftp, path);
if (!attr) {
pr_err("failed to get stat for %s: %s\n",
path, ssh_get_error(sftp_ssh(sftp)));
return -1;
}
if (attr->type == SSH_FILEXFER_TYPE_REGULAR ||
attr->type == SSH_FILEXFER_TYPE_SYMLINK) {
*size = attr->size;
ret = 1;
}
sftp_attributes_free(attr);
return ret;
}
static int file_fill_recursive(struct list_head *file_list,
bool dst_is_remote, sftp_session sftp, char *src_path,
char *rel_path, char *dst_path, bool dst_should_dir)
{
char next_src_path[PATH_MAX], next_rel_path[PATH_MAX];
struct file *f;
size_t size;
int ret;
ret = file_is_directory(path, NULL);
ret = file_is_directory(src_path, dst_is_remote ? NULL : sftp, true);
if (ret < 0)
return -1;
if (ret == 1) {
if ((dir = opendir(path)) == NULL) {
pr_err("opend to open dir %s: %s\n", path, strerrno());
if (ret == 0) {
/* src_path is file */
ret = check_file_tobe_copied(src_path, dst_is_remote ? NULL : sftp,
&size);
if (ret <= 0)
return ret; /* error or skip */
if ((f = file_alloc(src_path, size, !dst_is_remote)) == NULL) {
pr_err("%s\n", strerrno());
return -1;
}
if (dst_should_dir)
snprintf(f->dst_path, PATH_MAX, "%s/%s%s",
dst_path, rel_path, basename(src_path));
else
snprintf(f->dst_path, PATH_MAX, "%s%s", rel_path, dst_path);
list_add_tail(&f->list, file_list);
pprint2("file %s %s -> %s %s\n",
f->path, dst_is_remote ? "(local)" : "(remote)",
f->dst_path, dst_is_remote ? "(remote)" : "(local)");
return 0;
}
/* src_path is directory */
if (dst_is_remote) {
/* src_path is local directory */
struct dirent *de;
DIR *dir;
if ((dir = opendir(src_path)) == NULL) {
pr_err("opendir '%s': %s\n", src_path, strerrno());
return -1;
}
while ((de = readdir(dir)) != NULL) {
if (file_should_skip(de->d_name))
continue;
snprintf(child, sizeof(child), "%s/%s", path, de->d_name);
ret = file_fill_local_recursive(child, head);
snprintf(next_src_path, sizeof(next_src_path),
"%s/%s", src_path, de->d_name);
snprintf(next_rel_path, sizeof(next_rel_path),
"%s%s/", rel_path, basename(src_path));
ret = file_fill_recursive(file_list, dst_is_remote, sftp,
next_src_path, next_rel_path,
dst_path, dst_should_dir);
if (ret < 0)
return ret;
}
} else {
/* path is file */
if (stat(path, &statbuf) < 0) {
pr_err("file %s: %s\n", path, strerrno());
/* src_path is remote directory */
sftp_attributes attr;
sftp_dir dir;
if ((dir = sftp_opendir(sftp, src_path)) == NULL) {
pr_err("sftp_opendir: '%s': %s\n", src_path,
sftp_get_ssh_error(sftp));
return -1;
}
if ((statbuf.st_mode & S_IFMT) == S_IFREG ||
(statbuf.st_mode & S_IFMT) == S_IFLNK) {
struct file *f = file_alloc(path, statbuf.st_size, false);
if (!f) {
pr_err("%s\n", strerrno());
return -1;
}
list_add_tail(&f->list, head);
}
}
return 0;
}
static int file_fill_remote_recursive(char *path, sftp_session sftp,
struct list_head *head)
{
char child[PATH_MAX];
sftp_attributes attr;
sftp_dir dir;
int ret;
ret = file_is_directory(path, sftp);
if (ret < 0)
return -1;
if (ret == 1) {
dir = sftp_opendir(sftp, path);
if (!dir) {
pr_err("failed to open dir %s: %s\n", path,
ssh_get_error(sftp_ssh(sftp)));
return -1;
}
while ((attr = sftp_readdir(sftp, dir)) != NULL) {
if (file_should_skip(attr->name))
continue;
snprintf(child, sizeof(child), "%s/%s", path, attr->name);
ret = file_fill_remote_recursive(child, sftp, head);
snprintf(next_src_path, sizeof(next_src_path),
"%s/%s", src_path, attr->name);
snprintf(next_rel_path, sizeof(next_rel_path),
"%s%s/", rel_path, basename(src_path));
ret = file_fill_recursive(file_list, dst_is_remote, sftp,
next_src_path, next_rel_path,
dst_path, dst_should_dir);
if (ret < 0)
return ret;
sftp_attributes_free(attr);
}
if (!sftp_dir_eof(dir)) {
pr_err("can't list directory %s: %s\n", path,
ssh_get_error(sftp_ssh(sftp)));
return -1;
}
if (sftp_closedir(dir) != SSH_OK) {
pr_err("can't close directory %s: %s\n", path,
ssh_get_error(sftp_ssh(sftp)));
return -1;
}
} else {
/* path is file */
attr = sftp_stat(sftp, path);
if (!attr) {
pr_err("failed to get stat for %s: %s\n",
path, ssh_get_error(sftp_ssh(sftp)));
return -1;
}
/* skip special and unknown files */
if (attr->type == SSH_FILEXFER_TYPE_REGULAR ||
attr->type == SSH_FILEXFER_TYPE_SYMLINK) {
struct file *f = file_alloc(path, attr->size, true);
if (!f) {
pr_err("%s\n", strerrno());
return -1;
}
list_add(&f->list, head);
sftp_attributes_free(attr);
}
}
return 0;
}
int file_fill(sftp_session sftp, struct list_head *file_list, char **src_array, int cnt)
int file_fill(sftp_session sftp, struct list_head *file_list, char **src_array, int cnt,
char *dst)
{
char *src, *path;
int ret, n;
bool dst_is_remote, dst_is_dir, dst_should_dir;
char *dst_path, *src_path;
int n, ret;
dst_path = file_find_path(dst);
dst_path = *dst_path == '\0' ? "." : dst_path;
dst_is_remote = file_find_hostname(dst) ? true : false;
if (file_is_directory(dst_path, dst_is_remote ? sftp : NULL, false) > 0)
dst_is_dir = true;
else
dst_is_dir = false;
for (n = 0; n < cnt; n++) {
src = *(src_array + n);
path = file_find_path(src);
path = *path == '\0' ? "." : path;
if (file_has_hostname(src))
ret = file_fill_remote_recursive(path, sftp, file_list);
src_path = file_find_path(src_array[n]);
if (file_is_directory(src_path, dst_is_remote ? NULL : sftp, false) > 0)
dst_should_dir = true;
else
ret = file_fill_local_recursive(path, file_list);
dst_should_dir = false;
ret = file_fill_recursive(file_list, dst_is_remote, sftp,
src_path, "",
dst_path, dst_is_dir | dst_should_dir);
if (ret < 0)
return -1;
}
return 0;
}
int file_fill_dst(char *target, struct list_head *file_list)
{
bool dst_remote = file_find_hostname(target) ? true : false;
char *dst_path = file_find_path(target);
int pref_len, path_len;
struct file *f;
dst_path = *dst_path == '\0' ? "." : dst_path;
list_for_each_entry(f, file_list, list) {
f->dst_remote = dst_remote;
pref_len = strlen(dst_path);
path_len = strlen(f->path);
if (pref_len + path_len + 2 > PATH_MAX) { /* +2 is '/' and '\0' */
pr_err("too long path: %s/%s\n", dst_path, f->path);
return -1;
}
memcpy(f->dst_path, dst_path, pref_len);
f->dst_path[pref_len] = '/';
memcpy(f->dst_path + pref_len + 1, f->path, path_len);
f->dst_path[pref_len + 1 + path_len] = '\0';
return ret;
}
return 0;

View File

@@ -60,10 +60,9 @@ struct chunk {
char *file_find_hostname(char *path);
bool file_has_hostname(char *path);
int file_is_directory(char *path, sftp_session sftp);
int file_fill(sftp_session sftp, struct list_head *file_list, char **src_array, int cnt);
int file_fill_dst(char *target, struct list_head *file_list);
int file_fill(sftp_session sftp, struct list_head *file_list, char **src_array, int cnt,
char *dst);
int chunk_fill(struct list_head *file_list, struct list_head *chunk_list,
int nr_conn, int min_chunk_sz, int max_chunk_sz);

View File

@@ -67,10 +67,10 @@ void stop_copy_threads(int sig)
void usage(bool print_help) {
printf("sscp: super scp, copy files over multiple ssh connections\n"
"\n"
"Usage: sscp [Cvqdh] [-n nr_conns] [-s min_chunk_sz] [-S max_chunk_sz]\n"
"Usage: sscp [CvqDdh] [-n nr_conns] [-s min_chunk_sz] [-S max_chunk_sz]\n"
" [-b sftp_buf_sz] [-B io_buf_sz]\n"
" [-l login_name] [-p port] [-i identity_file]\n"
" [-c cipher_spec] []source ... target_directory\n"
" [-c cipher_spec] source ... target\n"
"\n");
if (!print_help)
@@ -85,6 +85,7 @@ void usage(bool print_help) {
" qemu/block/ssh.c. need investigation...\n"
" -v increment verbose output level\n"
" -q disable output\n"
" -D dry run\n"
"\n"
" -l LOGIN_NAME login name\n"
" -p PORT port number\n"
@@ -94,12 +95,6 @@ void usage(bool print_help) {
" -d increment ssh debug output level\n"
" -h print this help\n"
"\n");
printf(" Note:\n"
" Not similar to scp and rsync, target in sscp must be directory\n"
" (at present). This means that sscp cannot change file names.\n"
" sscp copies file(s) into a directory.\n"
"\n");
}
char *find_hostname(int ind, int argc, char **argv)
@@ -152,6 +147,7 @@ int main(int argc, char **argv)
int min_chunk_sz = DEFAULT_MIN_CHUNK_SZ;
int max_chunk_sz = 0;
int verbose = 1;
bool dryrun = false;
int ret = 0, n;
char ch;
@@ -166,7 +162,7 @@ int main(int argc, char **argv)
nr_threads = (int)(nr_cpus() / 2);
nr_threads = nr_threads == 0 ? 1 : nr_threads;
while ((ch = getopt(argc, argv, "n:s:S:b:B:vql:p:i:c:Cdh")) != -1) {
while ((ch = getopt(argc, argv, "n:s:S:b:B:vqDl:p:i:c:Cdh")) != -1) {
switch (ch) {
case 'n':
nr_threads = atoi(optarg);
@@ -225,6 +221,9 @@ int main(int argc, char **argv)
case 'q':
verbose = -1;
break;
case 'D':
dryrun = true;
break;
case 'l':
opts.login_name = optarg;
break;
@@ -280,22 +279,10 @@ int main(int argc, char **argv)
return 1;
sscp.opts = &opts; /* save ssh-able ssh_opts */
/* check target is directory */
ret = file_is_directory(sscp.target,
file_find_hostname(sscp.target) ? sscp.ctrl : NULL);
if (ret < 0)
goto out;
if (ret == 0) {
pr_err("target must be directory\n");
goto out;
}
/* fill file list */
ret = file_fill(sscp.ctrl, &sscp.file_list, &argv[optind], argc - optind - 1);
if (ret < 0)
goto out;
ret = file_fill_dst(sscp.target, &sscp.file_list);
ret = file_fill(sscp.ctrl, &sscp.file_list, &argv[optind], argc - optind - 1,
sscp.target);
if (ret < 0)
goto out;
@@ -313,6 +300,9 @@ int main(int argc, char **argv)
chunk_dump(&sscp.chunk_list);
#endif
if (dryrun)
return 0;
/* register SIGINT to stop thrads */
if (signal(SIGINT, stop_copy_threads) == SIG_ERR) {
pr_err("cannot set signal: %s\n", strerrno());

View File

@@ -23,6 +23,7 @@ sftp_session ssh_make_sftp_session(char *sshdst, struct ssh_opts *opts);
void ssh_sftp_close(sftp_session sftp);
#define sftp_ssh(sftp) (sftp)->session
#define sftp_get_ssh_error(sftp) ssh_get_error(sftp_ssh(sftp))
/* wrapping multiple sftp_read|write */
int sftp_write2(sftp_file sf, const void *buf, size_t len, size_t sftp_buf_sz);