Add restart support for compresses I/O logs.
This commit is contained in:
@@ -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 */
|
||||
|
@@ -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.
|
||||
*/
|
||||
|
@@ -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 */
|
||||
|
@@ -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;
|
||||
|
Reference in New Issue
Block a user