diff options
Diffstat (limited to 'libreport/src/lib/dump_dir.c')
-rw-r--r-- | libreport/src/lib/dump_dir.c | 839 |
1 files changed, 839 insertions, 0 deletions
diff --git a/libreport/src/lib/dump_dir.c b/libreport/src/lib/dump_dir.c new file mode 100644 index 00000000..8891f911 --- /dev/null +++ b/libreport/src/lib/dump_dir.c @@ -0,0 +1,839 @@ +/* + Copyright (C) 2009 Zdenek Prikryl (zprikryl@redhat.com) + Copyright (C) 2009 RedHat inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include <sys/utsname.h> +#include "libreport.h" +#include "strbuf.h" + +// Locking logic: +// +// The directory is locked by creating a symlink named .lock inside it, +// whose value (where it "points to") is the pid of locking process. +// We use symlink, not an ordinary file, because symlink creation +// is an atomic operation. +// +// There are two cases where after .lock creation, we might discover +// that directory is not really free: +// * another process just created new directory, but didn't manage +// to lock it before us. +// * another process is deleting the directory, and we managed to sneak in +// and create .lock after it deleted all files (including .lock) +// but before it rmdir'ed the empty directory. +// +// Both these cases are detected by the fact that file named "time" +// is not present (it must be present in any valid dump dir). +// If after locking the dir we don't see time file, we remove the lock +// at once and back off. What happens in concurrent processes +// we interfered with? +// * "create new dump dir" process just re-tries locking. +// * "delete dump dir" process just retries rmdir. +// +// There is another case when we don't find time file: +// when the directory is not really a *dump* dir - user gave us +// an ordinary directory name by mistake. +// We detect it by bailing out of "lock, check time file; sleep +// and retry if it doesn't exist" loop using a counter. +// +// To make locking work reliably, it's important to set timeouts +// correctly. For example, dd_create should retry locking +// its newly-created directory much faster than dd_opendir +// tries to lock the directory it tries to open. + + +// How long to sleep between "symlink fails with EEXIST, +// readlink fails with ENOENT" tries. Someone just unlocked the dir. +// We never bail out in this case, we retry forever. +// The value can be really small: +#define SYMLINK_RETRY_USLEEP (10*1000) + +// How long to sleep when lock file with valid pid is seen by dd_opendir +// (we are waiting for other process to unlock or die): +#define WAIT_FOR_OTHER_PROCESS_USLEEP (500*1000) + +// How long to sleep when lock file with valid pid is seen by dd_create +// (some idiot jumped the gun and locked the dir we just created). +// Must not be the same as WAIT_FOR_OTHER_PROCESS_USLEEP (we depend on this) +// and should be small (we have the priority in locking, this is OUR dir): +#define CREATE_LOCK_USLEEP (10*1000) + +// How long to sleep after we locked a dir, found no time file +// (either we are racing with someone, or it's not a dump dir) +// and unlocked it; +// and after how many tries to give up and declare it's not a dump dir: +#define NO_TIME_FILE_USLEEP (50*1000) +#define NO_TIME_FILE_COUNT 10 + +// How long to sleep after we unlocked an empty dir, but then rmdir failed +// (some idiot jumped the gun and locked the dir we are deleting); +// and after how many tries to give up: +#define RMDIR_FAIL_USLEEP (10*1000) +#define RMDIR_FAIL_COUNT 50 + + +static char *load_text_file(const char *path, unsigned flags); + +static bool isdigit_str(const char *str) +{ + do + { + if (*str < '0' || *str > '9') return false; + str++; + } while (*str); + return true; +} + +static bool exist_file_dir(const char *path) +{ + struct stat buf; + if (stat(path, &buf) == 0) + { + if (S_ISDIR(buf.st_mode) || S_ISREG(buf.st_mode)) + { + return true; + } + } + return false; +} + +/* Return values: + * -1: error (in this case, errno is 0 if error message is already logged) + * 0: failed to lock (someone else has it locked) + * 1: success + */ +static int get_and_set_lock(const char* lock_file, const char* pid) +{ + while (symlink(pid, lock_file) != 0) + { + if (errno != EEXIST) + { + if (errno != ENOENT && errno != ENOTDIR && errno != EACCES) + { + perror_msg("Can't create lock file '%s'", lock_file); + errno = 0; + } + return -1; + } + + char pid_buf[sizeof(pid_t)*3 + 4]; + ssize_t r = readlink(lock_file, pid_buf, sizeof(pid_buf) - 1); + if (r < 0) + { + if (errno == ENOENT) + { + /* Looks like lock_file was deleted */ + usleep(SYMLINK_RETRY_USLEEP); /* avoid CPU eating loop */ + continue; + } + perror_msg("Can't read lock file '%s'", lock_file); + errno = 0; + return -1; + } + pid_buf[r] = '\0'; + + if (strcmp(pid_buf, pid) == 0) + { + log("Lock file '%s' is already locked by us", lock_file); + return 0; + } + if (isdigit_str(pid_buf)) + { + char pid_str[sizeof("/proc/") + sizeof(pid_buf)]; + sprintf(pid_str, "/proc/%s", pid_buf); + if (access(pid_str, F_OK) == 0) + { + log("Lock file '%s' is locked by process %s", lock_file, pid_buf); + return 0; + } + log("Lock file '%s' was locked by process %s, but it crashed?", lock_file, pid_buf); + } + /* The file may be deleted by now by other process. Ignore ENOENT */ + if (unlink(lock_file) != 0 && errno != ENOENT) + { + perror_msg("Can't remove stale lock file '%s'", lock_file); + errno = 0; + return -1; + } + } + + VERB1 log("Locked '%s'", lock_file); + return 1; +} + +static int dd_lock(struct dump_dir *dd, unsigned sleep_usec, int flags) +{ + if (dd->locked) + error_msg_and_die("Locking bug on '%s'", dd->dd_dirname); + + char pid_buf[sizeof(long)*3 + 2]; + sprintf(pid_buf, "%lu", (long)getpid()); + + unsigned dirname_len = strlen(dd->dd_dirname); + char lock_buf[dirname_len + sizeof("/.lock")]; + strcpy(lock_buf, dd->dd_dirname); + strcpy(lock_buf + dirname_len, "/.lock"); + + unsigned count = NO_TIME_FILE_COUNT; + retry: + while (1) + { + int r = get_and_set_lock(lock_buf, pid_buf); + if (r < 0) + return r; /* error */ + if (r > 0) + break; /* locked successfully */ + /* Other process has the lock, wait for it to go away */ + usleep(sleep_usec); + } + + /* Are we called by dd_opendir (as opposed to dd_create)? */ + if (sleep_usec == WAIT_FOR_OTHER_PROCESS_USLEEP) /* yes */ + { + strcpy(lock_buf + dirname_len, "/time"); + if (access(lock_buf, F_OK) != 0) + { + /* time file doesn't exist. We managed to lock the directory + * which was just created by somebody else, or is almost deleted + * by delete_file_dir. + * Unlock and back off. + */ + strcpy(lock_buf + dirname_len, "/.lock"); + xunlink(lock_buf); + VERB1 log("Unlocked '%s' (no time file)", lock_buf); + if (--count == 0) + { + errno = EISDIR; /* "this is an ordinary dir, not dump dir" */ + return -1; + } + usleep(NO_TIME_FILE_USLEEP); + goto retry; + } + } + + dd->locked = true; + return 0; +} + +static void dd_unlock(struct dump_dir *dd) +{ + if (dd->locked) + { + dd->locked = 0; + + unsigned dirname_len = strlen(dd->dd_dirname); + char lock_buf[dirname_len + sizeof("/.lock")]; + strcpy(lock_buf, dd->dd_dirname); + strcpy(lock_buf + dirname_len, "/.lock"); + xunlink(lock_buf); + + VERB1 log("Unlocked '%s'", lock_buf); + } +} + +static inline struct dump_dir *dd_init(void) +{ + return (struct dump_dir*)xzalloc(sizeof(struct dump_dir)); +} + +int dd_exist(struct dump_dir *dd, const char *path) +{ + char *full_path = concat_path_file(dd->dd_dirname, path); + int ret = exist_file_dir(full_path); + free(full_path); + return ret; +} + +void dd_close(struct dump_dir *dd) +{ + if (!dd) + return; + + dd_unlock(dd); + if (dd->next_dir) + { + closedir(dd->next_dir); + /* free(dd->next_dir); - WRONG! */ + } + + free(dd->dd_dirname); + free(dd); +} + +static char* rm_trailing_slashes(const char *dir) +{ + unsigned len = strlen(dir); + while (len != 0 && dir[len-1] == '/') + len--; + return xstrndup(dir, len); +} + +struct dump_dir *dd_opendir(const char *dir, int flags) +{ + struct dump_dir *dd = dd_init(); + + dir = dd->dd_dirname = rm_trailing_slashes(dir); + + struct stat stat_buf; + stat(dir, &stat_buf); + /* & 0666 should remove the executable bit */ + dd->mode = (stat_buf.st_mode & 0666); + + errno = 0; + if (dd_lock(dd, WAIT_FOR_OTHER_PROCESS_USLEEP, flags) < 0) + { + if ((flags & DD_OPEN_READONLY) && errno == EACCES) + { + /* Directory is not writable. If it seems to be readable, + * return "read only" dd, not NULL */ + if (stat(dir, &stat_buf) == 0 + && S_ISDIR(stat_buf.st_mode) + && access(dir, R_OK) == 0 + ) { + return dd; + } + } + if (errno == EISDIR) + { + /* EISDIR: dd_lock can lock the dir, but it sees no time file there, + * even after it retried many times. It must be an ordinary directory! + * + * Without this check, e.g. abrt-action-print happily prints any current + * directory when run without arguments, because its option -d DIR + * defaults to "."! + */ + error_msg("'%s' is not a dump directory", dir); + } + else if (errno == ENOENT || errno == ENOTDIR) + { + if (!(flags & DD_FAIL_QUIETLY_ENOENT)) + error_msg("'%s' does not exist", dir); + } + else + { + if (!(flags & DD_FAIL_QUIETLY_EACCES)) + perror_msg("Can't access '%s'", dir); + } + dd_close(dd); + return NULL; + } + + dd->dd_uid = (uid_t)-1L; + dd->dd_gid = (gid_t)-1L; + if (geteuid() == 0) + { + /* In case caller would want to create more files, he'll need uid:gid */ + struct stat stat_buf; + if (stat(dir, &stat_buf) != 0 || !S_ISDIR(stat_buf.st_mode)) + { + error_msg("Can't stat '%s', or it is not a directory", dir); + dd_close(dd); + return NULL; + } + dd->dd_uid = stat_buf.st_uid; + dd->dd_gid = stat_buf.st_gid; + } + + return dd; +} + +/* Create a fresh empty debug dump dir. + * + * Security: we should not allow users to write new files or write + * into existing ones, but they should be able to read them. + * + * @param uid + * Crashed application's User Id + * + * We currently have only three callers: + * kernel oops hook: uid -> not saved, so everyone can steal and work with it + * this hook runs under 0:0 + * ccpp hook: uid=uid of crashed user's binary + * this hook runs under 0:0 + * python hook: uid=uid of crashed user's script + * this hook runs under abrt:gid + * + * Currently, we set dir's gid to passwd(uid)->pw_gid parameter, and we set uid to + * abrt's user id. We do not allow write access to group. + */ +struct dump_dir *dd_create(const char *dir, uid_t uid, mode_t mode) +{ + /* a little trick to copy read bits from file mode to exec bit of dir mode*/ + mode_t dir_mode = mode | ((mode & 0444) >> 2); + struct dump_dir *dd = dd_init(); + + dd->mode = mode; + + /* Unlike dd_opendir, can't use realpath: the directory doesn't exist yet, + * realpath will always return NULL. We don't really have to: + * dd_opendir(".") makes sense, dd_create(".") does not. + */ + dir = dd->dd_dirname = rm_trailing_slashes(dir); + + const char *last_component = strrchr(dir, '/'); + if (last_component) + last_component++; + else + last_component = dir; + if (dot_or_dotdot(last_component)) + { + /* dd_create("."), dd_create(".."), dd_create("dir/."), + * dd_create("dir/..") and similar are madness, refuse them. + */ + error_msg("Bad dir name '%s'", dir); + dd_close(dd); + return NULL; + } + + bool created_parents = false; + try_again: + /* Was creating it with mode 0700 and user as the owner, but this allows + * the user to replace any file in the directory, changing security-sensitive data + * (e.g. "uid", "analyzer", "executable") + */ + if (mkdir(dir, dir_mode) == -1) + { + int err = errno; + if (!created_parents && errno == ENOENT) + { + char *p = dd->dd_dirname + 1; + while ((p = strchr(p, '/')) != NULL) + { + *p = '\0'; + int r = (mkdir(dd->dd_dirname, 0755) == 0 || errno == EEXIST); + *p++ = '/'; + if (!r) + goto report_err; + } + created_parents = true; + goto try_again; + } + report_err: + errno = err; + perror_msg("Can't create directory '%s'", dir); + dd_close(dd); + return NULL; + } + + if (dd_lock(dd, CREATE_LOCK_USLEEP, /*flags:*/ 0) < 0) + { + dd_close(dd); + return NULL; + } + + /* mkdir's mode (above) can be affected by umask, fix it */ + if (chmod(dir, dir_mode) == -1) + { + perror_msg("can't change mode of '%s'", dir); + dd_close(dd); + return NULL; + } + + dd->dd_uid = (uid_t)-1L; + dd->dd_gid = (gid_t)-1L; + if (uid != (uid_t)-1L) + { + /* Get ABRT's user id */ + dd->dd_uid = 0; + struct passwd *pw = getpwnam("abrt"); + if (pw) + dd->dd_uid = pw->pw_uid; + else + error_msg("user 'abrt' does not exist, using uid 0"); + + /* Get crashed application's group id */ + /*dd->dd_gid = 0; - dd_init did this already */ + pw = getpwuid(uid); + if (pw) + dd->dd_gid = pw->pw_gid; + else + error_msg("User %lu does not exist, using gid 0", (long)uid); + + if (chown(dir, dd->dd_uid, dd->dd_gid) == -1) + { + perror_msg("can't change '%s' ownership to %lu:%lu", dir, + (long)dd->dd_uid, (long)dd->dd_gid); + } + } + + return dd; +} + +void dd_create_basic_files(struct dump_dir *dd, uid_t uid) +{ + char long_str[sizeof(long) * 3 + 2]; + + time_t t = time(NULL); + sprintf(long_str, "%lu", (long)t); + dd_save_text(dd, FILENAME_TIME, long_str); + + /* it doesn't make sense to create the uid file if uid == -1 */ + if (uid != (uid_t)-1L) + { + sprintf(long_str, "%li", (long)uid); + dd_save_text(dd, FILENAME_UID, long_str); + } + + struct utsname buf; + uname(&buf); /* never fails */ + dd_save_text(dd, FILENAME_KERNEL, buf.release); + dd_save_text(dd, FILENAME_ARCHITECTURE, buf.machine); + dd_save_text(dd, FILENAME_HOSTNAME, buf.nodename); + + char *release = load_text_file("/etc/system-release", + DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE); + if (!release) + release = load_text_file("/etc/redhat-release", /*flags:*/ 0); + dd_save_text(dd, FILENAME_OS_RELEASE, release); + free(release); +} + +void dd_sanitize_mode_and_owner(struct dump_dir *dd) +{ + /* Don't sanitize if we aren't run under root: + * we assume that during file creation (by whatever means, + * even by "hostname >file" in abrt_event.conf) + * normal umask-based mode setting takes care of correct mode, + * and uid:gid is, of course, set to user's uid and gid. + * + * For root operating on /var/spool/abrt/USERS_PROBLEM, this isn't true: + * "hostname >file", for example, would create file OWNED BY ROOT! + * This routine resets mode and uid:gid for all such files. + */ + if (dd->dd_uid == (uid_t)-1) + return; + + if (!dd->locked) + error_msg_and_die("dump_dir is not opened"); /* bug */ + + DIR *d = opendir(dd->dd_dirname); + if (!d) + return; + + struct dirent *dent; + while ((dent = readdir(d)) != NULL) + { + if (dent->d_name[0] == '.') /* ".lock", ".", ".."? skip */ + continue; + char *full_path = concat_path_file(dd->dd_dirname, dent->d_name); + struct stat statbuf; + if (lstat(full_path, &statbuf) == 0 && S_ISREG(statbuf.st_mode)) + { + if ((statbuf.st_mode & 0777) != dd->mode) + chmod(full_path, dd->mode); + if (statbuf.st_uid != dd->dd_uid || statbuf.st_gid != dd->dd_gid) + { + if (chown(full_path, dd->dd_uid, dd->dd_gid) != 0) + { + perror_msg("can't change '%s' ownership to %lu:%lu", full_path, + (long)dd->dd_uid, (long)dd->dd_gid); + } + } + } + free(full_path); + } + closedir(d); +} + +static int delete_file_dir(const char *dir, bool skip_lock_file) +{ + DIR *d = opendir(dir); + if (!d) + { + /* The caller expects us to error out only if the directory + * still exists (not deleted). If directory + * *doesn't exist*, return 0 and clear errno. + */ + if (errno == ENOENT || errno == ENOTDIR) + { + errno = 0; + return 0; + } + return -1; + } + + bool unlink_lock_file = false; + struct dirent *dent; + while ((dent = readdir(d)) != NULL) + { + if (dot_or_dotdot(dent->d_name)) + continue; + if (skip_lock_file && strcmp(dent->d_name, ".lock") == 0) + { + unlink_lock_file = true; + continue; + } + char *full_path = concat_path_file(dir, dent->d_name); + if (unlink(full_path) == -1 && errno != ENOENT) + { + int err = 0; + if (errno == EISDIR) + { + errno = 0; + err = delete_file_dir(full_path, /*skip_lock_file:*/ false); + } + if (errno || err) + { + perror_msg("Can't remove '%s'", full_path); + free(full_path); + closedir(d); + return -1; + } + } + free(full_path); + } + closedir(d); + + /* Here we know for sure that all files/subdirs we found via readdir + * were deleted successfully. If rmdir below fails, we assume someone + * is racing with us and created a new file. + */ + + if (unlink_lock_file) + { + char *full_path = concat_path_file(dir, ".lock"); + xunlink(full_path); + free(full_path); + + unsigned cnt = RMDIR_FAIL_COUNT; + do { + if (rmdir(dir) == 0) + return 0; + /* Someone locked the dir after unlink, but before rmdir. + * This "someone" must be dd_lock(). + * It detects this (by seeing that there is no time file) + * and backs off at once. So we need to just retry rmdir, + * with minimal sleep. + */ + usleep(RMDIR_FAIL_USLEEP); + } while (--cnt != 0); + } + + int r = rmdir(dir); + if (r) + perror_msg("Can't remove directory '%s'", dir); + return r; +} + +int dd_delete(struct dump_dir *dd) +{ + int r = delete_file_dir(dd->dd_dirname, /*skip_lock_file:*/ true); + dd->locked = 0; /* delete_file_dir already removed .lock */ + dd_close(dd); + return r; +} + +static char *load_text_file(const char *path, unsigned flags) +{ + FILE *fp = fopen(path, "r"); + if (!fp) + { + if (!(flags & DD_FAIL_QUIETLY_ENOENT)) + perror_msg("Can't open file '%s'", path); + return (flags & DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE ? NULL : xstrdup("")); + } + + struct strbuf *buf_content = strbuf_new(); + int oneline = 0; + int ch; + while ((ch = fgetc(fp)) != EOF) + { +//TODO? \r -> \n? +//TODO? strip trailing spaces/tabs? + if (ch == '\n') + oneline = (oneline << 1) | 1; + if (ch == '\0') + ch = ' '; + if (isspace(ch) || ch >= ' ') /* used !iscntrl, but it failed on unicode */ + strbuf_append_char(buf_content, ch); + } + fclose(fp); + + char last = oneline != 0 ? buf_content->buf[buf_content->len - 1] : 0; + if (last == '\n') + { + /* If file contains exactly one '\n' and it is at the end, remove it. + * This enables users to use simple "echo blah >file" in order to create + * short string items in dump dirs. + */ + if (oneline == 1) + buf_content->buf[--buf_content->len] = '\0'; + } + else /* last != '\n' */ + { + /* Last line is unterminated, fix it */ + /* Cases: */ + /* oneline=0: "qwe" - DONT fix this! */ + /* oneline=1: "qwe\nrty" - two lines in fact */ + /* oneline>1: "qwe\nrty\uio" */ + if (oneline >= 1) + strbuf_append_char(buf_content, '\n'); + } + + return strbuf_free_nobuf(buf_content); +} + +static bool save_binary_file(const char *path, const char* data, unsigned size, uid_t uid, gid_t gid, mode_t mode) +{ + /* the mode is set by the caller, see dd_create() for security analysis */ + unlink(path); + int fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, mode); + if (fd < 0) + { + perror_msg("Can't open file '%s'", path); + return false; + } + + if (uid != (uid_t)-1L) + { + if (fchown(fd, uid, gid) == -1) + { + perror_msg("can't change '%s' ownership to %lu:%lu", path, (long)uid, (long)gid); + } + } + + unsigned r = full_write(fd, data, size); + close(fd); + if (r != size) + { + error_msg("Can't save file '%s'", path); + return false; + } + + return true; +} + +char* dd_load_text_ext(const struct dump_dir *dd, const char *name, unsigned flags) +{ +// if (!dd->locked) +// error_msg_and_die("dump_dir is not opened"); /* bug */ + + /* Compat with old abrt dumps. Remove in abrt-2.1 */ + if (strcmp(name, "release") == 0) + name = FILENAME_OS_RELEASE; + + char *full_path = concat_path_file(dd->dd_dirname, name); + char *ret = load_text_file(full_path, flags); + free(full_path); + + return ret; +} + +char* dd_load_text(const struct dump_dir *dd, const char *name) +{ + return dd_load_text_ext(dd, name, /*flags:*/ 0); +} + +void dd_save_text(struct dump_dir *dd, const char *name, const char *data) +{ + if (!dd->locked) + error_msg_and_die("dump_dir is not opened"); /* bug */ + + char *full_path = concat_path_file(dd->dd_dirname, name); + save_binary_file(full_path, data, strlen(data), dd->dd_uid, dd->dd_gid, dd->mode); + free(full_path); +} + +void dd_save_binary(struct dump_dir* dd, const char* name, const char* data, unsigned size) +{ + if (!dd->locked) + error_msg_and_die("dump_dir is not opened"); /* bug */ + + char *full_path = concat_path_file(dd->dd_dirname, name); + save_binary_file(full_path, data, size, dd->dd_uid, dd->dd_gid, dd->mode); + free(full_path); +} + +void add_reported_to(struct dump_dir *dd, const char *line) +{ + if (!dd->locked) + error_msg_and_die("dump_dir is not opened"); /* bug */ + + char *reported_to = dd_load_text_ext(dd, FILENAME_REPORTED_TO, DD_FAIL_QUIETLY_ENOENT | DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE); + if (reported_to) + { + unsigned len_line = strlen(line); + char *p = reported_to; + while (*p) + { + if (strncmp(p, line, len_line) == 0 && (p[len_line] == '\n' || p[len_line] == '\0')) + goto ret; + p = strchrnul(p, '\n'); + if (!*p) + break; + p++; + } + if (p != reported_to && p[-1] != '\n') + reported_to = append_to_malloced_string(reported_to, "\n"); + reported_to = append_to_malloced_string(reported_to, line); + reported_to = append_to_malloced_string(reported_to, "\n"); + } + else + reported_to = xasprintf("%s\n", line); + dd_save_text(dd, FILENAME_REPORTED_TO, reported_to); + ret: + free(reported_to); +} + +DIR *dd_init_next_file(struct dump_dir *dd) +{ +// if (!dd->locked) +// error_msg_and_die("dump_dir is not opened"); /* bug */ + + if (dd->next_dir) + closedir(dd->next_dir); + + dd->next_dir = opendir(dd->dd_dirname); + if (!dd->next_dir) + { + error_msg("Can't open directory '%s'", dd->dd_dirname); + } + + return dd->next_dir; +} + +int dd_get_next_file(struct dump_dir *dd, char **short_name, char **full_name) +{ + if (dd->next_dir == NULL) + return 0; + + struct dirent *dent; + while ((dent = readdir(dd->next_dir)) != NULL) + { + if (is_regular_file(dent, dd->dd_dirname)) + { + if (short_name) + *short_name = xstrdup(dent->d_name); + if (full_name) + *full_name = concat_path_file(dd->dd_dirname, dent->d_name); + return 1; + } + } + + closedir(dd->next_dir); + dd->next_dir = NULL; + return 0; +} + +/* Utility function */ +void delete_dump_dir(const char *dirname) +{ + struct dump_dir *dd = dd_opendir(dirname, /*flags:*/ 0); + if (dd) + { + dd_delete(dd); + } +} |