Add restart support for compresses I/O logs.

This commit is contained in:
Todd C. Miller
2019-10-24 20:04:32 -06:00
parent b57054785f
commit 3394785f6d
4 changed files with 251 additions and 9 deletions

View File

@@ -118,8 +118,10 @@ struct passwd;
struct group;
bool iolog_close(struct iolog_file *iol, const char **errstr);
bool iolog_eof(struct iolog_file *iol);
bool iolog_mkdtemp(char *path);
bool iolog_nextid(char *iolog_dir, char sessid[7]);
bool iolog_open(struct iolog_file *iol, int dfd, int iofd, const char *mode);
bool iolog_rename(const char *from, const char *to);
bool iolog_set_compress(const char *str);
bool iolog_set_flush(const char *str);
bool iolog_set_group(const struct group *gr);
@@ -134,6 +136,7 @@ off_t iolog_seek(struct iolog_file *iol, off_t offset, int whence);
size_t mkdir_iopath(const char *iolog_path, char *pathbuf, size_t pathsize);
ssize_t iolog_read(struct iolog_file *iol, void *buf, size_t nbytes, const char **errstr);
ssize_t iolog_write(struct iolog_file *iol, const void *buf, size_t len, const char **errstr);
void iolog_rewind(struct iolog_file *iol);
void iolog_set_defaults(void);
#endif /* SUDO_IOLOG_H */

View File

@@ -123,12 +123,12 @@ io_swapids(bool restore)
* Create directory and any parent directories as needed.
*/
static bool
io_mkdirs(char *path)
iolog_mkdirs(char *path)
{
mode_t omask;
struct stat sb;
bool ok, uid_changed = false;
debug_decl(io_mkdirs, SUDO_DEBUG_UTIL)
debug_decl(iolog_mkdirs, SUDO_DEBUG_UTIL)
/* umask must not be more restrictive than the file modes. */
omask = umask(ACCESSPERMS & ~(iolog_filemode|iolog_dirmode));
@@ -207,11 +207,11 @@ done:
/*
* Create temporary directory and any parent directories as needed.
*/
static bool
io_mkdtemp(char *path)
bool
iolog_mkdtemp(char *path)
{
bool ok, uid_changed = false;
debug_decl(io_mkdtemp, SUDO_DEBUG_UTIL)
debug_decl(iolog_mkdtemp, SUDO_DEBUG_UTIL)
ok = sudo_mkdir_parents(path, iolog_uid, iolog_gid, iolog_dirmode, true);
if (!ok && errno == EACCES) {
@@ -245,6 +245,29 @@ io_mkdtemp(char *path)
debug_return_bool(ok);
}
/*
* Like rename(2) but changes UID as needed.
*/
bool
iolog_rename(const char *from, const char *to)
{
bool ok, uid_changed = false;
debug_decl(iolog_rename, SUDO_DEBUG_UTIL)
ok = rename(from, to) == 0;
if (!ok && errno == EACCES) {
uid_changed = io_swapids(false);
if (uid_changed)
ok = rename(from, to) == 0;
}
if (uid_changed) {
if (!io_swapids(true))
ok = false;
}
debug_return_bool(ok);
}
/*
* Reset I/O log settings to default values.
*/
@@ -437,7 +460,7 @@ iolog_nextid(char *iolog_dir, char sessid[7])
/*
* Create I/O log directory if it doesn't already exist.
*/
if (!io_mkdirs(iolog_dir))
if (!iolog_mkdirs(iolog_dir))
goto done;
/*
@@ -539,9 +562,9 @@ mkdir_iopath(const char *iolog_path, char *pathbuf, size_t pathsize)
* Sets iolog_gid (if it is not already set) as a side effect.
*/
if (len >= 6 && strcmp(&pathbuf[len - 6], "XXXXXX") == 0)
ok = io_mkdtemp(pathbuf);
ok = iolog_mkdtemp(pathbuf);
else
ok = io_mkdirs(pathbuf);
ok = iolog_mkdirs(pathbuf);
debug_return_size_t(ok ? len : (size_t)-1);
}
@@ -683,6 +706,24 @@ iolog_seek(struct iolog_file *iol, off_t offset, int whence)
return ret;
}
/*
* I/O log wrapper for rewind/gzrewind.
*/
void
iolog_rewind(struct iolog_file *iol)
{
debug_decl(iolog_rewind, SUDO_DEBUG_UTIL)
#ifdef HAVE_ZLIB_H
if (iol->compressed)
(void)gzrewind(iol->fd.g);
else
#endif
rewind(iol->fd.f);
debug_return;
}
/*
* Read from a (possibly compressed) I/O log file.
*/

View File

@@ -649,12 +649,194 @@ read_timing_record(struct iolog_file *iol, struct timing_closure *timing)
debug_return_int(0);
}
/* XXX - compressed I/O logs cannot be restarted, must re-write them */
/*
* Copy len bytes from src to dst.
*/
static bool
iolog_copy(struct iolog_file *src, struct iolog_file *dst, off_t remainder,
const char **errstr)
{
char buf[64 * 1024];
ssize_t nread;
debug_decl(iolog_copy, SUDO_DEBUG_UTIL)
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
"copying %lld bytes", (long long)remainder);
while (remainder > 0) {
const ssize_t toread = MIN(remainder, ssizeof(buf));
nread = iolog_read(src, buf, toread, errstr);
if (nread == -1)
debug_return_bool(false);
remainder -= nread;
do {
ssize_t nwritten = iolog_write(dst, buf, nread, errstr);
if (nwritten == -1)
debug_return_bool(false);
nread -= nwritten;
} while (nread > 0);
}
debug_return_bool(true);
}
/* Compressed logs don't support random access, need to rewrite them. */
static bool
iolog_rewrite(const struct timespec *target, struct connection_closure *closure)
{
struct iolog_file new_iolog_files[IOFD_MAX];
off_t iolog_file_sizes[IOFD_MAX] = { 0 };
struct timing_closure timing;
int iofd, len, tmpdir_fd = -1;
const char *name, *errstr;
char tmpdir[PATH_MAX];
bool ret = false;
debug_decl(iolog_rewrite, SUDO_DEBUG_UTIL)
/* Parse timing file until we reach the target point. */
for (;;) {
/* Read next record from timing file. */
if (read_timing_record(&closure->iolog_files[IOFD_TIMING], &timing) != 0)
goto done;
sudo_timespecadd(&timing.delay, &closure->elapsed_time,
&closure->elapsed_time);
if (timing.event < IOFD_TIMING) {
if (!closure->iolog_files[timing.event].enabled) {
/* Missing log file. */
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"iofd %d referenced but not open", timing.event);
goto done;
}
iolog_file_sizes[timing.event] += timing.u.nbytes;
}
if (sudo_timespeccmp(&closure->elapsed_time, target, >=)) {
if (sudo_timespeccmp(&closure->elapsed_time, target, ==))
break;
/* Mismatch between resume point and stored log. */
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"resume point mismatch, target [%lld, %ld], have [%lld, %ld]",
(long long)target->tv_sec, target->tv_nsec,
(long long)closure->elapsed_time.tv_sec,
closure->elapsed_time.tv_nsec);
goto done;
}
}
iolog_file_sizes[IOFD_TIMING] =
iolog_seek(&closure->iolog_files[IOFD_TIMING], 0, SEEK_CUR);
iolog_rewind(&closure->iolog_files[IOFD_TIMING]);
/* Create new I/O log files in a temporary directory. */
len = snprintf(tmpdir, sizeof(tmpdir), "%s/restart.XXXXXX",
closure->details.iolog_path);
if (len < 0 || len >= ssizeof(tmpdir)) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"unable to format %s/restart.XXXXXX", closure->details.iolog_path);
goto done;
}
if (!iolog_mkdtemp(tmpdir)) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
"unable to mkdtemp %s", tmpdir);
goto done;
}
if ((tmpdir_fd = iolog_openat(AT_FDCWD, tmpdir, O_RDONLY)) == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
"unable to open %s", tmpdir);
goto done;
}
/* Create new copies of the existing iologs */
memset(new_iolog_files, 0, sizeof(new_iolog_files));
for (iofd = 0; iofd < IOFD_MAX; iofd++) {
if (!closure->iolog_files[iofd].enabled)
continue;
new_iolog_files[iofd].enabled = true;
if (!iolog_open(&new_iolog_files[iofd], tmpdir_fd, iofd, "w")) {
if (errno != ENOENT) {
sudo_debug_printf(
SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
"unable to open %s/%s", tmpdir, iolog_fd_to_name(iofd));
goto done;
}
}
}
for (iofd = 0; iofd < IOFD_MAX; iofd++) {
if (!closure->iolog_files[iofd].enabled)
continue;
if (!iolog_copy(&closure->iolog_files[iofd], &new_iolog_files[iofd],
iolog_file_sizes[iofd], &errstr)) {
name = iolog_fd_to_name(iofd);
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"unable to copy %s/%s to %s/%s: %s",
closure->details.iolog_path, name, tmpdir, name, errstr);
goto done;
}
}
/* Move copied log files into place. */
for (iofd = 0; iofd < IOFD_MAX; iofd++) {
char from[PATH_MAX], to[PATH_MAX];
if (!closure->iolog_files[iofd].enabled)
continue;
/* This would be easier with renameat(2), old systems are annoying. */
name = iolog_fd_to_name(iofd);
len = snprintf(from, sizeof(from), "%s/%s", tmpdir, name);
if (len < 0 || len >= ssizeof(from)) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"unable to format %s/%s", tmpdir, name);
goto done;
}
len = snprintf(to, sizeof(to), "%s/%s", closure->details.iolog_path,
name);
if (len < 0 || len >= ssizeof(from)) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"unable to format %s/%s", closure->details.iolog_path, name);
goto done;
}
if (!iolog_rename(from, to)) {
sudo_debug_printf(
SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
"unable to rename %s to %s", from, to);
goto done;
}
}
for (iofd = 0; iofd < IOFD_MAX; iofd++) {
if (!closure->iolog_files[iofd].enabled)
continue;
(void)iolog_close(&closure->iolog_files[iofd], &errstr);
closure->iolog_files[iofd] = new_iolog_files[iofd];
new_iolog_files[iofd].enabled = false;
}
/* Ready to log I/O buffers. */
ret = true;
done:
if (tmpdir_fd != -1) {
if (!ret) {
for (iofd = 0; iofd < IOFD_MAX; iofd++) {
if (!new_iolog_files[iofd].enabled)
continue;
(void)iolog_close(&new_iolog_files[iofd], &errstr);
(void)unlinkat(tmpdir_fd, iolog_fd_to_name(iofd), 0);
}
}
close(tmpdir_fd);
(void)rmdir(tmpdir);
}
debug_return_bool(ret);
}
bool
iolog_restart(RestartMessage *msg, struct connection_closure *closure)
{
struct timespec target;
struct timing_closure timing;
bool compressed = false;
off_t pos;
int iofd;
debug_decl(iolog_restart, SUDO_DEBUG_UTIL)
@@ -668,6 +850,15 @@ iolog_restart(RestartMessage *msg, struct connection_closure *closure)
goto bad;
}
/* We use iolog_dir_fd in calls to openat(2) */
closure->iolog_dir_fd =
iolog_openat(AT_FDCWD, closure->details.iolog_path, O_RDONLY);
if (closure->iolog_dir_fd == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
"%s", closure->details.iolog_path);
goto bad;
}
/* Open existing I/O log files. */
for (iofd = 0; iofd < IOFD_MAX; iofd++) {
closure->iolog_files[iofd].enabled = true;
@@ -681,12 +872,18 @@ iolog_restart(RestartMessage *msg, struct connection_closure *closure)
goto bad;
}
}
if (closure->iolog_files[iofd].compressed)
compressed = true;
}
if (!closure->iolog_files[IOFD_TIMING].enabled) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"missing timing file in %s", closure->details.iolog_path);
goto bad;
}
if (compressed) {
/* Compressed logs don't support random access, need to rewrite them. */
debug_return_bool(iolog_rewrite(&target, closure));
}
/* Parse timing file until we reach the target point. */
/* XXX - split up */

View File

@@ -787,6 +787,7 @@ connection_closure_alloc(int sock)
if ((closure = calloc(1, sizeof(*closure))) == NULL)
debug_return_ptr(NULL);
closure->iolog_dir_fd = -1;
closure->sock = sock;
closure->read_buf.size = 64 * 1024;