mirror of
https://github.com/upa/mscp.git
synced 2026-05-13 05:17:29 +08:00
add -p option, preserving file timestamps
This commit is contained in:
@@ -6,7 +6,7 @@ mscp \- copy files over multiple SSH connections
|
|||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
|
|
||||||
.B mscp
|
.B mscp
|
||||||
.RB [ \-vqDHdNh ]
|
.RB [ \-vqDpHdNh ]
|
||||||
[\c
|
[\c
|
||||||
.BI \-n \ NR_CONNECTIONS\c
|
.BI \-n \ NR_CONNECTIONS\c
|
||||||
]
|
]
|
||||||
@@ -35,7 +35,6 @@ mscp \- copy files over multiple SSH connections
|
|||||||
.BI \-l \ LOGIN_NAME\c
|
.BI \-l \ LOGIN_NAME\c
|
||||||
]
|
]
|
||||||
[\c
|
[\c
|
||||||
.BR \-p |\c
|
|
||||||
.BI \-P \ PORT\c
|
.BI \-P \ PORT\c
|
||||||
]
|
]
|
||||||
[\c
|
[\c
|
||||||
@@ -217,6 +216,11 @@ none. See
|
|||||||
libssh features
|
libssh features
|
||||||
.UE .
|
.UE .
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B \-p
|
||||||
|
Preserves modification times and access times (file mode bits are
|
||||||
|
preserved by default).
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-H
|
.B \-H
|
||||||
Disables hostkey checking.
|
Disables hostkey checking.
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ struct mscp_opts {
|
|||||||
char *coremask; /** hex to specifiy usable cpu cores */
|
char *coremask; /** hex to specifiy usable cpu cores */
|
||||||
int max_startups; /** sshd MaxStartups concurrent connections */
|
int max_startups; /** sshd MaxStartups concurrent connections */
|
||||||
int interval; /** interval between SSH connection attempts */
|
int interval; /** interval between SSH connection attempts */
|
||||||
|
bool preserve_ts; /** preserve file timestamps */
|
||||||
|
|
||||||
int severity; /** messaging severity. set MSCP_SERVERITY_* */
|
int severity; /** messaging severity. set MSCP_SERVERITY_* */
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,10 +4,13 @@
|
|||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
|
#include <sys/times.h>
|
||||||
|
#include <utime.h>
|
||||||
|
|
||||||
#include <fileops.h>
|
#include <fileops.h>
|
||||||
#include <ssh.h>
|
#include <ssh.h>
|
||||||
#include <message.h>
|
#include <message.h>
|
||||||
|
#include <platform.h>
|
||||||
|
|
||||||
|
|
||||||
sftp_session __thread tls_sftp;
|
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_gid = attr->gid;
|
||||||
st->st_mode = attr->permissions;
|
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) {
|
switch (attr->type) {
|
||||||
case SSH_FILEXFER_TYPE_REGULAR:
|
case SSH_FILEXFER_TYPE_REGULAR:
|
||||||
st->st_mode |= S_IFREG;
|
st->st_mode |= S_IFREG;
|
||||||
@@ -186,8 +201,6 @@ static void sftp_attr_to_stat(sftp_attributes attr, struct stat *st)
|
|||||||
default:
|
default:
|
||||||
mpr_warn("unkown SSH_FILEXFER_TYPE %d", attr->type);
|
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;
|
sftp_attributes attr;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
|
memset(st, 0, sizeof(*st));
|
||||||
|
|
||||||
if (sftp) {
|
if (sftp) {
|
||||||
attr = sftp_stat(sftp, path);
|
attr = sftp_stat(sftp, path);
|
||||||
sftp_err_to_errno(sftp);
|
sftp_err_to_errno(sftp);
|
||||||
@@ -292,23 +307,34 @@ off_t mscp_lseek(mf *f, off_t off)
|
|||||||
return ret;
|
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;
|
int ret;
|
||||||
|
|
||||||
if (sftp) {
|
if (sftp) {
|
||||||
struct sftp_attributes_struct attr;
|
struct sftp_attributes_struct attr;
|
||||||
memset(&attr, 0, sizeof(attr));
|
memset(&attr, 0, sizeof(attr));
|
||||||
attr.permissions = mode;
|
attr.permissions = st->st_mode;
|
||||||
attr.size = size;
|
attr.size = st->st_size;
|
||||||
attr.flags = (SSH_FILEXFER_ATTR_PERMISSIONS|SSH_FILEXFER_ATTR_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);
|
ret = sftp_setstat(sftp, path, &attr);
|
||||||
sftp_err_to_errno(sftp);
|
sftp_err_to_errno(sftp);
|
||||||
} else {
|
} else {
|
||||||
if ((ret = chmod(path, mode)) < 0)
|
if ((ret = chmod(path, st->st_mode)) < 0)
|
||||||
return ret;
|
return ret;
|
||||||
if ((ret = truncate(path, size)) < 0)
|
if ((ret = truncate(path, st->st_size)) < 0)
|
||||||
return ret;
|
return ret;
|
||||||
|
if (preserve_ts)
|
||||||
|
ret = setutimes(path, st->st_atim, st->st_mtim);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ off_t mscp_lseek(mf *f, off_t off);
|
|||||||
/* mscp_setstat() involves chmod and truncate. It executes both at
|
/* mscp_setstat() involves chmod and truncate. It executes both at
|
||||||
* once via a single SFTP command (sftp_setstat()).
|
* 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 */
|
/* remote glob */
|
||||||
int mscp_glob(const char *pattern, int flags, glob_t *pglob, sftp_session sftp);
|
int mscp_glob(const char *pattern, int flags, glob_t *pglob, sftp_session sftp);
|
||||||
|
|||||||
12
src/main.c
12
src/main.c
@@ -19,7 +19,7 @@
|
|||||||
void usage(bool print_help) {
|
void usage(bool print_help) {
|
||||||
printf("mscp " MSCP_BUILD_VERSION ": copy files over multiple ssh connections\n"
|
printf("mscp " MSCP_BUILD_VERSION ": copy files over multiple ssh connections\n"
|
||||||
"\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"
|
" [-u max_startups] [-I interval]\n"
|
||||||
" [-s min_chunk_sz] [-S max_chunk_sz] [-a nr_ahead] [-b buf_sz]\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"
|
" [-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"
|
" -r no effect\n"
|
||||||
"\n"
|
"\n"
|
||||||
" -l LOGIN_NAME login name\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"
|
" -F CONFIG path to user ssh config (default ~/.ssh/config)\n"
|
||||||
" -i IDENTITY identity file for public key authentication\n"
|
" -i IDENTITY identity file for public key authentication\n"
|
||||||
" -c CIPHER cipher spec\n"
|
" -c CIPHER cipher spec\n"
|
||||||
@@ -56,6 +56,7 @@ void usage(bool print_help) {
|
|||||||
" -C COMPRESS enable compression: "
|
" -C COMPRESS enable compression: "
|
||||||
"yes, no, zlib, zlib@openssh.com\n"
|
"yes, no, zlib, zlib@openssh.com\n"
|
||||||
" -g CONGESTION specify TCP congestion control algorithm\n"
|
" -g CONGESTION specify TCP congestion control algorithm\n"
|
||||||
|
" -p preserve timestamps of files\n"
|
||||||
" -H disable hostkey check\n"
|
" -H disable hostkey check\n"
|
||||||
" -d increment ssh debug output level\n"
|
" -d increment ssh debug output level\n"
|
||||||
" -N enable Nagle's algorithm (default disabled)\n"
|
" -N enable Nagle's algorithm (default disabled)\n"
|
||||||
@@ -257,7 +258,7 @@ int main(int argc, char **argv)
|
|||||||
o.severity = MSCP_SEVERITY_WARN;
|
o.severity = MSCP_SEVERITY_WARN;
|
||||||
|
|
||||||
while ((ch = getopt(argc, argv,
|
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) {
|
switch (ch) {
|
||||||
case 'n':
|
case 'n':
|
||||||
o.nr_threads = atoi(optarg);
|
o.nr_threads = atoi(optarg);
|
||||||
@@ -304,8 +305,6 @@ int main(int argc, char **argv)
|
|||||||
s.login_name = optarg;
|
s.login_name = optarg;
|
||||||
break;
|
break;
|
||||||
case 'P':
|
case 'P':
|
||||||
/* fallthough for compatibility with scp */
|
|
||||||
case 'p':
|
|
||||||
s.port = optarg;
|
s.port = optarg;
|
||||||
break;
|
break;
|
||||||
case 'F':
|
case 'F':
|
||||||
@@ -326,6 +325,9 @@ int main(int argc, char **argv)
|
|||||||
case 'g':
|
case 'g':
|
||||||
s.ccalgo = optarg;
|
s.ccalgo = optarg;
|
||||||
break;
|
break;
|
||||||
|
case 'p':
|
||||||
|
o.preserve_ts = true;
|
||||||
|
break;
|
||||||
case 'H':
|
case 'H':
|
||||||
s.no_hostkey_check = true;
|
s.no_hostkey_check = true;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -676,8 +676,9 @@ void *mscp_copy_thread(void *arg)
|
|||||||
if (!c)
|
if (!c)
|
||||||
break; /* no more chunks */
|
break; /* no more chunks */
|
||||||
|
|
||||||
if ((t->ret = copy_chunk(c, src_sftp, dst_sftp, m->opts->nr_ahead,
|
if ((t->ret = copy_chunk(c, src_sftp, dst_sftp,
|
||||||
m->opts->buf_sz, &t->done)) < 0)
|
m->opts->nr_ahead, m->opts->buf_sz,
|
||||||
|
m->opts->preserve_ts, &t->done)) < 0)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
16
src/path.c
16
src/path.c
@@ -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 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;
|
mode_t mode;
|
||||||
int flags;
|
int flags;
|
||||||
@@ -610,10 +610,18 @@ int copy_chunk(struct chunk *c, sftp_session src_sftp, sftp_session dst_sftp,
|
|||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
if (refcnt_dec(&c->p->refcnt) == 0) {
|
if (refcnt_dec(&c->p->refcnt) == 0) {
|
||||||
|
struct stat st;
|
||||||
c->p->state = FILE_STATE_DONE;
|
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",
|
/* sync stat */
|
||||||
c->p->path, strerrno());
|
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);
|
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 */
|
/* 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 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 */
|
/* just print contents. just for debugging */
|
||||||
void path_dump(struct list_head *path_list);
|
void path_dump(struct list_head *path_list);
|
||||||
|
|||||||
@@ -2,14 +2,21 @@
|
|||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
#include <sys/time.h>
|
||||||
#include <sys/sysctl.h>
|
#include <sys/sysctl.h>
|
||||||
#elif linux
|
#elif linux
|
||||||
#define _GNU_SOURCE
|
#define _GNU_SOURCE
|
||||||
#include <sched.h>
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sched.h>
|
||||||
#elif __FreeBSD__
|
#elif __FreeBSD__
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <fcntl.h>
|
||||||
#include <pthread_np.h>
|
#include <pthread_np.h>
|
||||||
#else
|
#else
|
||||||
#error unsupported platform
|
#error unsupported platform
|
||||||
@@ -40,6 +47,20 @@ int set_thread_affinity(pthread_t tid, int core)
|
|||||||
return 0;
|
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)
|
static void random_string(char *buf, size_t size)
|
||||||
{
|
{
|
||||||
@@ -108,6 +129,19 @@ int set_thread_affinity(pthread_t tid, int core)
|
|||||||
return ret;
|
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_create(int value)
|
||||||
{
|
{
|
||||||
sem_t *sem;
|
sem_t *sem;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
int nr_cpus(void);
|
int nr_cpus(void);
|
||||||
int set_thread_affinity(pthread_t tid, int core);
|
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
|
* macOS does not support sem_init(). macOS (seems to) releases the
|
||||||
|
|||||||
Reference in New Issue
Block a user