diff options
author | Ryo Nakamura <upa@haeena.net> | 2024-02-06 16:15:43 +0900 |
---|---|---|
committer | Ryo Nakamura <upa@haeena.net> | 2024-02-06 16:15:43 +0900 |
commit | a7f8ad948b38c450c0f9ccd52185771ae8f9754e (patch) | |
tree | c480252f0a26dca743d1c81818dec6f6a660f29e | |
parent | ff45d9d71b85a618aed6d3d5e5056bada6ff81f9 (diff) |
add -p option, preserving file timestamps
-rw-r--r-- | doc/mscp.1.in | 8 | ||||
-rw-r--r-- | include/mscp.h | 1 | ||||
-rw-r--r-- | src/fileops.c | 40 | ||||
-rw-r--r-- | src/fileops.h | 2 | ||||
-rw-r--r-- | src/main.c | 12 | ||||
-rw-r--r-- | src/mscp.c | 5 | ||||
-rw-r--r-- | src/path.c | 16 | ||||
-rw-r--r-- | src/path.h | 2 | ||||
-rw-r--r-- | src/platform.c | 36 | ||||
-rw-r--r-- | src/platform.h | 1 |
10 files changed, 100 insertions, 23 deletions
diff --git a/doc/mscp.1.in b/doc/mscp.1.in index b329c27..13e4347 100644 --- a/doc/mscp.1.in +++ b/doc/mscp.1.in @@ -6,7 +6,7 @@ mscp \- copy files over multiple SSH connections .SH SYNOPSIS .B mscp -.RB [ \-vqDHdNh ] +.RB [ \-vqDpHdNh ] [\c .BI \-n \ NR_CONNECTIONS\c ] @@ -35,7 +35,6 @@ mscp \- copy files over multiple SSH connections .BI \-l \ LOGIN_NAME\c ] [\c -.BR \-p |\c .BI \-P \ PORT\c ] [\c @@ -218,6 +217,11 @@ libssh features .UE . .TP +.B \-p +Preserves modification times and access times (file mode bits are +preserved by default). + +.TP .B \-H Disables hostkey checking. diff --git a/include/mscp.h b/include/mscp.h index f8ff0df..1c3528c 100644 --- a/include/mscp.h +++ b/include/mscp.h @@ -44,6 +44,7 @@ struct mscp_opts { char *coremask; /** hex to specifiy usable cpu cores */ int max_startups; /** sshd MaxStartups concurrent connections */ int interval; /** interval between SSH connection attempts */ + bool preserve_ts; /** preserve file timestamps */ int severity; /** messaging severity. set MSCP_SERVERITY_* */ }; diff --git a/src/fileops.c b/src/fileops.c index e4e8088..5dbbebd 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -4,10 +4,13 @@ #include <errno.h> #include <fcntl.h> #include <dirent.h> +#include <sys/times.h> +#include <utime.h> #include <fileops.h> #include <ssh.h> #include <message.h> +#include <platform.h> sftp_session __thread tls_sftp; @@ -167,6 +170,18 @@ static void sftp_attr_to_stat(sftp_attributes attr, struct stat *st) st->st_gid = attr->gid; st->st_mode = attr->permissions; +#if defined(__APPLE__) +#define st_atim st_atimespec +#define st_mtim st_mtimespec +#define st_ctim st_ctimespec +#endif + st->st_atim.tv_sec = attr->atime; + st->st_atim.tv_nsec = attr->atime_nseconds; + st->st_mtim.tv_sec = attr->mtime; + st->st_mtim.tv_nsec = attr->mtime_nseconds; + st->st_ctim.tv_sec = attr->createtime; + st->st_ctim.tv_nsec = attr->createtime_nseconds; + switch (attr->type) { case SSH_FILEXFER_TYPE_REGULAR: st->st_mode |= S_IFREG; @@ -186,8 +201,6 @@ static void sftp_attr_to_stat(sftp_attributes attr, struct stat *st) default: mpr_warn("unkown SSH_FILEXFER_TYPE %d", attr->type); } - - /* ToDo: convert atime, ctime, and mtime */ } @@ -196,6 +209,8 @@ int mscp_stat(const char *path, struct stat *st, sftp_session sftp) sftp_attributes attr; int ret = 0; + memset(st, 0, sizeof(*st)); + if (sftp) { attr = sftp_stat(sftp, path); sftp_err_to_errno(sftp); @@ -292,23 +307,34 @@ off_t mscp_lseek(mf *f, off_t off) return ret; } -int mscp_setstat(const char *path, mode_t mode, size_t size, sftp_session sftp) +int mscp_setstat(const char *path, struct stat *st, bool preserve_ts, sftp_session sftp) { int ret; if (sftp) { struct sftp_attributes_struct attr; memset(&attr, 0, sizeof(attr)); - attr.permissions = mode; - attr.size = size; + attr.permissions = st->st_mode; + attr.size = st->st_size; attr.flags = (SSH_FILEXFER_ATTR_PERMISSIONS|SSH_FILEXFER_ATTR_SIZE); + if (preserve_ts) { + attr.atime = st->st_atim.tv_sec; + attr.atime_nseconds = st->st_atim.tv_nsec; + attr.mtime = st->st_mtim.tv_sec; + attr.mtime_nseconds = st->st_mtim.tv_nsec; + attr.flags |= (SSH_FILEXFER_ATTR_ACCESSTIME | + SSH_FILEXFER_ATTR_MODIFYTIME | + SSH_FILEXFER_ATTR_SUBSECOND_TIMES); + } ret = sftp_setstat(sftp, path, &attr); sftp_err_to_errno(sftp); } else { - if ((ret = chmod(path, mode)) < 0) + if ((ret = chmod(path, st->st_mode)) < 0) return ret; - if ((ret = truncate(path, size)) < 0) + if ((ret = truncate(path, st->st_size)) < 0) return ret; + if (preserve_ts) + ret = setutimes(path, st->st_atim, st->st_mtim); } return ret; diff --git a/src/fileops.h b/src/fileops.h index 1c4d638..dc87814 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -50,7 +50,7 @@ off_t mscp_lseek(mf *f, off_t off); /* mscp_setstat() involves chmod and truncate. It executes both at * once via a single SFTP command (sftp_setstat()). */ -int mscp_setstat(const char *path, mode_t mode, size_t size, sftp_session sftp); +int mscp_setstat(const char *path, struct stat *st, bool preserve_ts, sftp_session sftp); /* remote glob */ int mscp_glob(const char *pattern, int flags, glob_t *pglob, sftp_session sftp); @@ -19,7 +19,7 @@ void usage(bool print_help) { printf("mscp " MSCP_BUILD_VERSION ": copy files over multiple ssh connections\n" "\n" - "Usage: mscp [vqDHdNh] [-n nr_conns] [-m coremask]\n" + "Usage: mscp [vqDpHdNh] [-n nr_conns] [-m coremask]\n" " [-u max_startups] [-I interval]\n" " [-s min_chunk_sz] [-S max_chunk_sz] [-a nr_ahead] [-b buf_sz]\n" " [-l login_name] [-p port] [-F ssh_config] [-i identity_file]\n" @@ -48,7 +48,7 @@ void usage(bool print_help) { " -r no effect\n" "\n" " -l LOGIN_NAME login name\n" - " -p/-P PORT port number\n" + " -P PORT port number\n" " -F CONFIG path to user ssh config (default ~/.ssh/config)\n" " -i IDENTITY identity file for public key authentication\n" " -c CIPHER cipher spec\n" @@ -56,6 +56,7 @@ void usage(bool print_help) { " -C COMPRESS enable compression: " "yes, no, zlib, zlib@openssh.com\n" " -g CONGESTION specify TCP congestion control algorithm\n" + " -p preserve timestamps of files\n" " -H disable hostkey check\n" " -d increment ssh debug output level\n" " -N enable Nagle's algorithm (default disabled)\n" @@ -257,7 +258,7 @@ int main(int argc, char **argv) o.severity = MSCP_SEVERITY_WARN; while ((ch = getopt(argc, argv, - "n:m:u:I:s:S:a:b:vqDrl:P:p:i:F:c:M:C:g:HdNh")) != -1) { + "n:m:u:I:s:S:a:b:vqDrl:P:i:F:c:M:C:g:pHdNh")) != -1) { switch (ch) { case 'n': o.nr_threads = atoi(optarg); @@ -304,8 +305,6 @@ int main(int argc, char **argv) s.login_name = optarg; break; case 'P': - /* fallthough for compatibility with scp */ - case 'p': s.port = optarg; break; case 'F': @@ -326,6 +325,9 @@ int main(int argc, char **argv) case 'g': s.ccalgo = optarg; break; + case 'p': + o.preserve_ts = true; + break; case 'H': s.no_hostkey_check = true; break; @@ -676,8 +676,9 @@ void *mscp_copy_thread(void *arg) if (!c) break; /* no more chunks */ - if ((t->ret = copy_chunk(c, src_sftp, dst_sftp, m->opts->nr_ahead, - m->opts->buf_sz, &t->done)) < 0) + if ((t->ret = copy_chunk(c, src_sftp, dst_sftp, + m->opts->nr_ahead, m->opts->buf_sz, + m->opts->preserve_ts, &t->done)) < 0) break; } @@ -556,7 +556,7 @@ static int _copy_chunk(struct chunk *c, mf *s, mf *d, } int copy_chunk(struct chunk *c, sftp_session src_sftp, sftp_session dst_sftp, - int nr_ahead, int buf_sz, size_t *counter) + int nr_ahead, int buf_sz, bool preserve_ts, size_t *counter) { mode_t mode; int flags; @@ -610,10 +610,18 @@ int copy_chunk(struct chunk *c, sftp_session src_sftp, sftp_session dst_sftp, return ret; if (refcnt_dec(&c->p->refcnt) == 0) { + struct stat st; c->p->state = FILE_STATE_DONE; - if (mscp_setstat(c->p->dst_path, c->p->mode, c->p->size, dst_sftp) < 0) - mpr_err("failed to chmod and truncate %s: %s", - c->p->path, strerrno()); + + /* sync stat */ + if (mscp_stat(c->p->path, &st, src_sftp) < 0) { + mpr_err("mscp_stat: %s: %s", c->p->path, strerrno()); + return -1; + } + if (mscp_setstat(c->p->dst_path, &st, preserve_ts, dst_sftp) < 0) { + mpr_err("mscp_setstat: %s: %s", c->p->path, strerrno()); + return -1; + } mpr_info("copy done: %s", c->p->path); } @@ -98,7 +98,7 @@ void free_path(struct path *p); /* copy a chunk. either src_sftp or dst_sftp is not null, and another is null */ int copy_chunk(struct chunk *c, sftp_session src_sftp, sftp_session dst_sftp, - int nr_ahead, int buf_sz, size_t *counter); + int nr_ahead, int buf_sz, bool preserve_ts, size_t *counter); /* just print contents. just for debugging */ void path_dump(struct list_head *path_list); diff --git a/src/platform.c b/src/platform.c index a2dfb86..668c571 100644 --- a/src/platform.c +++ b/src/platform.c @@ -2,14 +2,21 @@ #ifdef __APPLE__ #include <stdlib.h> #include <sys/types.h> +#include <sys/time.h> #include <sys/sysctl.h> #elif linux #define _GNU_SOURCE -#include <sched.h> #include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <sched.h> #elif __FreeBSD__ #include <stdlib.h> #include <unistd.h> +#include <sys/stat.h> +#include <fcntl.h> #include <pthread_np.h> #else #error unsupported platform @@ -40,6 +47,20 @@ int set_thread_affinity(pthread_t tid, int core) return 0; } +int setutimes(const char *path, struct timespec atime, struct timespec mtime) +{ + struct timeval tv[2] = { + { + .tv_sec = atime.tv_sec, + .tv_usec = atime.tv_nsec * 1000, + }, + { + .tv_sec = mtime.tv_sec, + .tv_usec = mtime.tv_nsec * 1000, + }, + }; + return utimes(path, tv); +} static void random_string(char *buf, size_t size) { @@ -108,6 +129,19 @@ int set_thread_affinity(pthread_t tid, int core) return ret; } +int setutimes(const char *path, struct timespec atime, struct timespec mtime) +{ + struct timespec ts[2] = { atime, mtime }; + int fd = open(path, O_WRONLY); + int ret; + + if (fd < 0) + return -1; + ret = futimens(fd, ts); + close(fd); + return ret; +} + sem_t *sem_create(int value) { sem_t *sem; diff --git a/src/platform.h b/src/platform.h index c0f6f66..d47d620 100644 --- a/src/platform.h +++ b/src/platform.h @@ -7,6 +7,7 @@ int nr_cpus(void); int set_thread_affinity(pthread_t tid, int core); +int setutimes(const char *path, struct timespec atime, struct timespec mtime); /* * macOS does not support sem_init(). macOS (seems to) releases the |