/*
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 <libtar.h>
#include "internal_libreport.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
// A sub-directory of a dump directory where the meta-data such as owner are
// stored. The meta-data directory must have same owner, group and mode as its
// parent dump directory. It is not a fatal error, if the meta-data directory
// does not exist (backward compatibility).
#define META_DATA_DIR_NAME ".libreport"
#define META_DATA_FILE_OWNER "owner"
enum {
/* Try to create meta-data dir if it does not exist */
DD_MD_GET_CREATE = 1 << 0,
};
// a little trick to copy read bits from file mode to exec bit of dir mode
// * mode of dump directory is in the form of 640 (no X) because we create
// files more often then we play with directories
// * so if we want to get real mode of the directory we have to copy the read
// bits
#define DD_MODE_TO_DIR_MODE(mode) ((mode) | (((mode) & 0444) >> 2))
/* Owner of trusted elements */
uid_t dd_g_super_user_uid = 0;
/* Group of new dump directories */
gid_t dd_g_fs_group_gid = (gid_t)-1;
char *load_text_file(const char *path, unsigned flags);
static char *load_text_file_at(int dir_fd, const char *name, unsigned flags);
static void copy_file_from_chroot(struct dump_dir* dd, const char *name,
const char *chroot_dir, const char *file_path);
static bool save_binary_file_at(int dir_fd, const char *name, const char* data,
unsigned size, uid_t uid, gid_t gid, mode_t mode);
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_at(int dir_fd, const char *name)
{
struct stat buf;
if (fstatat(dir_fd, name, &buf, AT_SYMLINK_NOFOLLOW) == 0)
{
if (S_ISDIR(buf.st_mode) || S_ISREG(buf.st_mode))
{
return true;
}
}
return false;
}
/* A valid dump dir element name is correct filename and is not a name of any
* internal file or directory.
*/
#define dd_validate_element_name(name) \
(str_is_correct_filename(name) && (strcmp(META_DATA_DIR_NAME, name) != 0))
/* nobody user should not own any file */
static int get_no_owner_uid(uid_t *uid)
{
struct passwd *pw = getpwnam("nobody");
if (pw == NULL)
{
perror_msg("can't get nobody's uid");
if (errno == 0)
return -ENOENT;
return -errno;
}
*uid = pw->pw_uid;
return 0;
}
/* Opens the file in the three following steps:
* 1. open the file with O_PATH (get a file descriptor for operations with
* inode) and O_NOFOLLOW (do not dereference symbolick links)
* 2. stat the resulting file descriptor and fail if the opened file is not a
* regular file or if the number of links is greater than 1 (that means that
* the inode has more names (hard links))
* 3. "re-open" the file descriptor retrieved in the first step with O_RDONLY
* by opening /proc/self/fd/$fd (then close the former file descriptor and
* return the new one).
*/
int secure_openat_read(int dir_fd, const char *filename)
{
if (strchr(filename, '/'))
{
error_msg("Path must be file name without directory: '%s'", filename);
return -EFAULT;
}
static char reopen_buf[sizeof("/proc/self/fd/") + 3*sizeof(int) + 1];
int path_fd = openat(dir_fd, filename, O_PATH | O_NOFOLLOW);
if (path_fd < 0)
return -errno;
struct stat path_sb;
int r = fstat(path_fd, &path_sb);
if (r < 0)
{
perror_msg("stat");
close(path_fd);
return -EINVAL;
}
if (!S_ISREG(path_sb.st_mode) || path_sb.st_nlink > 1)
{
log_notice("Path isn't a regular file or has more links (%lu)", (unsigned long)path_sb.st_nlink);
close(path_fd);
return -EINVAL;
}
if (snprintf(reopen_buf, sizeof(reopen_buf), "/proc/self/fd/%d", path_fd) >= sizeof(reopen_buf)) {
error_msg("BUG: too long path to a file descriptor");
abort();
}
const int fd = open(reopen_buf, O_RDONLY);
close(path_fd);
return fd;
}
static int read_number_from_file_at(int dir_fd, const char *filename, const char *typename,
size_t typesz, unsigned long long min, unsigned long long max, unsigned long long *value)
{
const int fd = secure_openat_read(dir_fd, filename);
if (fd < 0)
{
log_info("Can't open '%s'", filename);
return fd;
}
int ret = 0;
/* - xmalloc_read() does not count '\0' Byte
* - count on sign
* - count on '\n'
*/
const size_t max_size = typesz * 3 + 2;
/* allow to read one extra Byte to be able to identify longer text */
size_t total_read = max_size + 1;
char *const value_buf = xmalloc_read(fd, &total_read);
/* Just reading, so no need to check the returned value. */
if (value_buf == NULL)
{
log_info("Can't read from '%s'", filename);
ret = -EBADFD;
goto finito;
}
if (total_read >= max_size)
{
log_info("File '%s' is too long to be valid %s "
"(max size %u)", filename, typename, (int)sizeof(value_buf));
ret = -EMSGSIZE;
goto finito;
}
/* Our tools don't put trailing newline into one line files,
* but we allow such format too:
*/
if (value_buf[total_read - 1] == '\n')
--total_read;
value_buf[total_read] = '\0';
const int neg = (value_buf[0] == '-');
/* if min equals 0, then we shall be to converting an unsigned number */
if (neg && 0 == min)
{
log_info("File '%s' contains a negative number ('%s')", filename, value_buf);
ret = -ERANGE;
goto finito;
}
errno = 0; /* To distinguish success/failure after call */
char *endptr;
const unsigned long long res = strtoull(value_buf, &endptr, /* base */ 10);
/* Check for various possible errors */
if (errno == ERANGE && (res == 0 || res == ULLONG_MAX))
{
log_info("File '%s' contains a number out-of-range of %s "
"('%s')", filename, typename, value_buf);
ret = -ERANGE;
goto finito;
}
if ( (errno != 0 && res == 0)
|| (*endptr != '\0')
|| endptr == value_buf
) {
log_info("File '%s' doesn't contain valid %s "
"('%s')", filename, typename, value_buf);
ret = -EINVAL;
goto finito;
}
if ((neg ? res < min : res > max))
{
log_info("File '%s' contains a number ('%s') %s of %s",
filename, value_buf,
neg ? "lower than minimum" : "greater than maximum",
typename);
ret = -ERANGE;
goto finito;
}
*value = res;
finito:
close(fd);
free(value_buf);
/* If we got here, strtoll() successfully parsed a number */
return ret;
}
/* Returns value less than 0 if the file is not readable or
* if the file doesn't contain valid unixt time stamp.
*
* Any possible failure will be logged.
*/
static time_t parse_time_file_at(int dir_fd, const char *filename)
{
/* Note that on some architectures (x32) time_t is "long long" */
const long long MAX_TIME_T = (1ULL << (sizeof(time_t)*8 - 1)) - 1;
unsigned long long value = (time_t)-1;
read_number_from_file_at(dir_fd, filename, "unix time stamp", sizeof(time_t), 0, MAX_TIME_T, &value);
return (time_t)value;
}
/* 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
*/
int create_symlink_lockfile_at(int dir_fd, const char* lock_file, const char* pid)
{
while (symlinkat(pid, dir_fd, 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 = readlinkat(dir_fd, 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_warning("Lock file '%s' is already locked by us", lock_file);
errno = EALREADY;
return 0;
}
if (isdigit_str(pid_buf))
{
char pid_str[sizeof("/proc/") + sizeof(pid_buf)];
snprintf(pid_str, sizeof(pid_str), "/proc/%s", pid_buf);
if (access(pid_str, F_OK) == 0)
{
log_warning("Lock file '%s' is locked by process %s", lock_file, pid_buf);
return 0;
}
log_warning("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 (unlinkat(dir_fd, lock_file, /*only files*/0) != 0 && errno != ENOENT)
{
perror_msg("Can't remove stale lock file '%s'", lock_file);
errno = 0;
return -1;
}
}
log_info("Locked '%s'", lock_file);
return 1;
}
int create_symlink_lockfile(const char *filename, const char *pid_str)
{
return create_symlink_lockfile_at(AT_FDCWD, filename, pid_str);
}
static const char *dd_check(struct dump_dir *dd)
{
dd->dd_time = parse_time_file_at(dd->dd_fd, FILENAME_TIME);
if (dd->dd_time < 0)
{
log_debug("Missing file: "FILENAME_TIME);
return FILENAME_TIME;
}
/* Do not warn about missing 'type' file in non-verbose modes.
*
* Handling of FILENAME_TYPE should be consistent with handling of
* FILENAME_TIME in the function parse_time_file_at() where the missing
* file message is printed by log_info() (in a verbose mode).
*/
int load_flags = DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE;
if (g_verbose < 2) load_flags |= DD_FAIL_QUIETLY_ENOENT;
dd->dd_type = load_text_file_at(dd->dd_fd, FILENAME_TYPE, load_flags);
if (!dd->dd_type || (strlen(dd->dd_type) == 0))
{
log_debug("Missing or empty file: "FILENAME_TYPE);
return FILENAME_TYPE;
}
return NULL;
}
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];
snprintf(pid_buf, sizeof(pid_buf), "%lu", (long)getpid());
unsigned count = NO_TIME_FILE_COUNT;
retry:
while (1)
{
int r = create_symlink_lockfile_at(dd->dd_fd, ".lock", pid_buf);
if (r < 0)
return r; /* error */
if (r > 0 || errno == EALREADY)
break; /* locked successfully */
if (flags & DD_DONT_WAIT_FOR_LOCK)
{
errno = EAGAIN;
return -1;
}
/* Other process has the lock, wait for it to go away */
usleep(sleep_usec);
}
/* Reset errno to 0 only if errno is EALREADY (used by
* create_symlink_lockfile() to signal that the dump directory is already
* locked by us) */
if (!(dd->owns_lock = (errno != EALREADY)))
errno = 0;
/* Are we called by dd_opendir (as opposed to dd_create)? */
if (sleep_usec == WAIT_FOR_OTHER_PROCESS_USLEEP) /* yes */
{
const char *missing_file = dd_check(dd);
/* some of the required files don'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.
*/
if (missing_file)
{
if (dd->owns_lock)
xunlinkat(dd->dd_fd, ".lock", /*only files*/0);
log_notice("Unlocked '%s' (no or corrupted '%s' file)", dd->dd_dirname, missing_file);
if (--count == 0 || flags & DD_DONT_WAIT_FOR_LOCK)
{
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)
{
if (dd->owns_lock)
xunlinkat(dd->dd_fd, ".lock", /*only files*/0);
dd->owns_lock = 0;
dd->locked = 0;
log_info("Unlocked '%s/.lock'", dd->dd_dirname);
}
}
static inline struct dump_dir *dd_init(void)
{
struct dump_dir* dd = (struct dump_dir*)xzalloc(sizeof(struct dump_dir));
dd->dd_time = (time_t)-1;
dd->dd_fd = -1;
dd->dd_md_fd = -1;
return dd;
}
int dd_exist(const struct dump_dir *dd, const char *name)
{
if (!dd_validate_element_name(name))
error_msg_and_die("Cannot test existence. '%s' is not a valid file name", name);
const int ret = exist_file_dir_at(dd->dd_fd, name);
return ret;
}
static void dd_close_meta_data_dir(struct dump_dir *dd)
{
if (dd->dd_md_fd < 0)
return;
close(dd->dd_md_fd);
dd->dd_md_fd = -1;
}
void dd_close(struct dump_dir *dd)
{
if (!dd)
return;
dd_unlock(dd);
if (dd->dd_fd >= 0)
close(dd->dd_fd);
dd_close_meta_data_dir(dd);
dd_clear_next_file(dd);
free(dd->dd_type);
free(dd->dd_dirname);
free(dd);
}
static int dd_create_subdir(int dd_fd, const char *dirname, uid_t dd_uid, gid_t dd_gid, mode_t dd_mode)
{
if (mkdirat(dd_fd, dirname, dd_mode) < 0)
{
perror_msg("Can't create directory '%s'", dirname);
return -1;
}
int dd_md_fd = openat(dd_fd, dirname, O_DIRECTORY | O_NOFOLLOW);
if (dd_md_fd < 0)
{
perror_msg("Can't open newly created directory '%s'", dirname);
goto fail_open;
}
if (dd_uid != (uid_t)-1)
{
if (fchown(dd_md_fd, dd_uid, dd_gid) != 0)
{
perror_msg("Can't change owner and group of '%s'", dirname);
goto fail_modify;
}
}
/* mkdir's mode (above) can be affected by umask, fix it */
if (fchmod(dd_md_fd, dd_mode) == -1)
{
perror_msg("Can't change mode of '%s'", dirname);
goto fail_modify;
}
return dd_md_fd;
fail_modify:
close(dd_md_fd);
fail_open:
if (unlinkat(dd_fd, dirname, AT_REMOVEDIR) < 0)
perror_msg("Fialed to unlink '%s' while cleaning up after failure", dirname);
return -1;
}
/* Opens the meta-data directory, checks its file system attributes and returns
* its file descriptor.
*
* The meta-data directory must have the same file system attributes as the
* parent dump directory in order to avoid unexpected situations and detects
* program errors (it is an error to modify bits of the dump directory and
* forgot to update the meta-data directory).
*
* Keep on mind that the old dump directories might miss the meta-data directory
* so the return value -ENOENT does not necessarily need to be fatal.
*/
static int dd_open_meta_data_dir(struct dump_dir *dd)
{
int md_dir_fd = openat(dd->dd_fd, META_DATA_DIR_NAME, O_DIRECTORY | O_NOFOLLOW);
if (md_dir_fd < 0)
{
md_dir_fd = -errno;
/* ENOENT is not critical */
if (errno != ENOENT)
log_warning("Can't open meta-data '"META_DATA_DIR_NAME"'");
else
log_info("The dump dir doesn't contain '"META_DATA_DIR_NAME"'");
goto finito;
}
struct stat md_sb;
if (fstat(md_dir_fd, &md_sb) < 0)
{
log_debug("Can't stat '"META_DATA_DIR_NAME"'");
goto fail;
}
/* Test only permission bits, ignore SUID, SGID, etc. */
const mode_t md_mode = md_sb.st_mode & 0777;
const mode_t dd_mode = DD_MODE_TO_DIR_MODE(dd->mode);
if ( md_sb.st_uid != dd->dd_uid
|| md_sb.st_gid != dd->dd_gid
|| md_mode != dd_mode)
{
log_debug("'"META_DATA_DIR_NAME"' has different attributes than the dump dir, '%d'='%d', '%d'='%d', %o = %o",
md_sb.st_uid, dd->dd_uid, md_sb.st_gid, dd->dd_gid, md_mode, dd_mode);
goto fail;
}
finito:
return md_dir_fd;
fail:
close(md_dir_fd);
return -EINVAL;
}
/* Returns a file descriptor to the meta-data directory. Can be configured to
* create the directory if it does not exist.
*
* This function enables lazy initialization of the meta-data directory.
*/
static int dd_get_meta_data_dir_fd(struct dump_dir *dd, int flags)
{
if (dd->dd_md_fd < 0)
{
dd->dd_md_fd = dd_open_meta_data_dir(dd);
if ( dd->dd_md_fd == -ENOENT
&& (flags & DD_MD_GET_CREATE))
{
dd->dd_md_fd = dd_create_subdir(dd->dd_fd,
META_DATA_DIR_NAME,
dd->dd_uid,
dd->dd_gid,
DD_MODE_TO_DIR_MODE(dd->mode));
}
}
return dd->dd_md_fd;
}
/* Tries to safely overwrite the existing file.
*
* The functions writes the new value to a temporary file and if the temporary
* file is successfully created, then moves the tmp file to the old file name.
*
* If the meta-data directory does not exist, the function will try to create
* it.
*/
static int dd_meta_data_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 */
if (!str_is_correct_filename(name))
error_msg_and_die("Cannot save meta-data. '%s' is not a valid file name", name);
int dd_md_fd = dd_get_meta_data_dir_fd(dd, DD_MD_GET_CREATE);
if (dd_md_fd < 0)
{
error_msg("Can't save meta-data: '%s'", name);
return dd_md_fd;
}
char *tmp_name = xasprintf("~%s.tmp", name);
int ret = -1;
if (!save_binary_file_at(dd_md_fd, tmp_name, data, strlen(data), dd->dd_uid, dd->dd_gid, dd->mode))
goto finito;
/* man 2 rename
*
* If newpath already exists it will be atomically replaced (subject to a
* few conditions; see ERRORS below), so that there is no point at which
* another process attempting to access newpath will find it missing.
*/
if (renameat(dd_md_fd, tmp_name, dd_md_fd, name))
{
ret = -errno;
perror_msg("Failed to move temporary file '%s' to '%s'", tmp_name, name);
goto finito;
}
ret = 0;
finito:
free(tmp_name);
return ret;
}
int dd_set_owner(struct dump_dir *dd, uid_t owner)
{
/* I was tempted to use the keyword static, but we should have reentracy
* always on mind. Who knows! */
char long_str[sizeof(long) * 3 + 2];
if (owner == (uid_t)-1)
owner = dd->dd_uid;
snprintf(long_str, sizeof(long_str), "%li", (long)owner);
const int ret = dd_meta_data_save_text(dd, META_DATA_FILE_OWNER, long_str);
if (ret < 0)
error_msg("The dump dir owner wasn't set to '%s'", long_str);
return ret;
}
int dd_set_no_owner(struct dump_dir *dd)
{
uid_t no_owner_uid = (uid_t)-1;
int ret = get_no_owner_uid(&no_owner_uid);
if (ret < 0)
return ret;
return dd_set_owner(dd, no_owner_uid);
}
uid_t dd_get_owner(struct dump_dir *dd)
{
static const long long MAX_UID_T = (1ULL << (sizeof(uid_t)*8 - 1)) - 1;
int dd_md_fd = dd_get_meta_data_dir_fd(dd, /*no create*/0);
if (dd_md_fd < 0)
{
log_info("No meta-data, using fs owner.");
return dd->dd_uid;
}
unsigned long long owner = 0;
int ret = read_number_from_file_at(dd_md_fd, META_DATA_FILE_OWNER, "UID",
sizeof(uid_t), 0, MAX_UID_T, &owner);
if (ret < 0)
{
if (ret != -ENOENT)
return ret;
log_info("No meta-data 'owner', using fs owner.");
return dd->dd_uid;
}
return (uid_t)owner;
}
time_t dd_get_first_occurrence(struct dump_dir *dd)
{
if (dd->dd_time == (time_t)-1)
{
log_debug("The dump directory wasn't opened");
errno = ENODATA;
}
return dd->dd_time;
}
time_t dd_get_last_occurrence(struct dump_dir *dd)
{
const time_t first_occurrence = dd_get_first_occurrence(dd);
if (first_occurrence == (time_t)-1)
return -1;
/* Do not touch errno in case of errors, because this function claims
* to set errno only to ENODATA */
const int old_errno = errno;
time_t last_occurrence = parse_time_file_at(dd->dd_fd, FILENAME_LAST_OCCURRENCE);
if (last_occurrence < first_occurrence)
{
log_info("Adapting '%s' to monotonic time (+%lds)",
FILENAME_LAST_OCCURRENCE, first_occurrence - last_occurrence);
last_occurrence = first_occurrence;
}
else if (last_occurrence < 0)
last_occurrence = first_occurrence;
errno = old_errno;
return last_occurrence;
}
/* A helper function useful for traversing directories.
*
* DIR* d opendir(dir_fd); ... closedir(d); closes also dir_fd but we want to
* keep it opened.
*/
static int fdreopen(int dir_fd, DIR **d)
{
int opendir_fd = dup(dir_fd);
if (opendir_fd < 0)
{
perror_msg("dup(dir_fd)");
return -EBADFD;
}
lseek(opendir_fd, SEEK_SET, 0);
*d = fdopendir(opendir_fd);
if (!*d)
{
int ret = -errno;
close(opendir_fd);
perror_msg("fdopendir(dir_fd)");
return ret;
}
/* 'opendir_fd' will be closed with 'd' */
return 0;
}
/* A macro for going through the entries of a directory referenced as a file
* descriptor.
*
* Usage:
*
* FOREACH_REGULAR_FILE_AS_FD_AT_BEGIN(dir_fd)
* {
* printf("Short name '%s'", dent->d_name);
* printf("File descriptor %d", fd);
* }
* FOREACH_REGULAR_FILE_AS_FD_AT_END
*/
#define FOREACH_REGULAR_FILE_AS_FD_AT_BEGIN(dir_fd) \
DIR *d; \
struct dirent *dent; \
if (fdreopen(dir_fd, &d) < 0) return -1; \
while ((dent = readdir(d)) != NULL) \
{ \
if (dot_or_dotdot(dent->d_name)) continue; \
int fd = secure_openat_read(dirfd(d), dent->d_name); \
if (fd >= 0) \
{
#define FOREACH_REGULAR_FILE_AS_FD_AT_END \
close(fd); \
} \
} \
closedir(d);
/* Sets attributes of the meta-data directory and its contents to the same
* attributes of the parent dump directory.
*/
static int dd_sanitize_mode_meta_data(struct dump_dir *dd)
{
if (!dd->locked)
error_msg_and_die("%s: dump_dir is not opened", __func__); /* bug */
int dd_md_fd = dd_get_meta_data_dir_fd(dd, /*no create*/0);
if (dd_md_fd < 0)
return 0;
int res = fchmod(dd_md_fd, DD_MODE_TO_DIR_MODE(dd->mode));
if (res < 0)
{
perror_msg("Failed to chmod meta-data sub-dir");
return res;
}
FOREACH_REGULAR_FILE_AS_FD_AT_BEGIN(dd_md_fd)
{
log_debug("chmoding %s", dent->d_name);
res = fchmod(fd, dd->mode);
if (res)
{
perror_msg("fchmod('%s')", dent->d_name);
break;
}
}
FOREACH_REGULAR_FILE_AS_FD_AT_END
return 0;
}
/* Sets owner and group of the meta-data directory and its contents to the same
* attributes of the parent dump directory.
*/
static int dd_chown_meta_data(struct dump_dir *dd, uid_t uid, gid_t gid)
{
if (!dd->locked)
error_msg_and_die("%s: dump_dir is not opened", __func__); /* bug */
int dd_md_fd = dd_get_meta_data_dir_fd(dd, /*no create*/0);
if (dd_md_fd < 0)
return 0;
int res = fchown(dd_md_fd, uid, gid);
if (res < 0)
{
perror_msg("Failed to chown meta-data sub-dir");
return res;
}
FOREACH_REGULAR_FILE_AS_FD_AT_BEGIN(dd_md_fd)
{
log_debug("%s: chowning %s", __func__, dent->d_name);
res = fchown(fd, uid, gid);
if (res)
{
perror_msg("fchown('%s')", dent->d_name);
break;
}
}
FOREACH_REGULAR_FILE_AS_FD_AT_END
return res;
}
static char* rm_trailing_slashes(const char *dir)
{
unsigned len = strlen(dir);
while (len != 0 && dir[len-1] == '/')
len--;
return xstrndup(dir, len);
}
static struct dump_dir *dd_do_open(struct dump_dir *dd, const char *dir, int flags)
{
if (dir != NULL)
{
dd->dd_dirname = rm_trailing_slashes(dir);
/* dd_do_open validates dd_fd */
dd->dd_fd = open(dd->dd_dirname, O_DIRECTORY | O_NOFOLLOW);
struct stat stat_buf;
if (dd->dd_fd < 0)
goto cant_access;
if (fstat(dd->dd_fd, &stat_buf) != 0)
goto cant_access;
/* & 0666 should remove the executable bit */
dd->mode = (stat_buf.st_mode & 0666);
/* We want to have dd_uid and dd_gid always initialized. But we have to
* initialize it in the way which does not prevent non-privileged user
* from saving data in their dump directories.
*
* Non-privileged users are not allowed to change the group to
* 'abrt' so we have to use their GID.
*
* If the caller is super-user, we have to use dd's fs owner and fs
* group, because he can do everything and the data must be readable by
* the real owner.
*
* We always use fs uid, because non-privileged users must own the
* directory and super-user must use fs owner.
*/
dd->dd_uid = stat_buf.st_uid;
/* We use fs group only if the caller is super-user, because we want to
* make sure non-privileged users can modify elements (libreport call
* chown(dd_uid, dd_gid) after modifying an element) and the modified
* elements do not have super-user's group.
*/
dd->dd_gid = getegid();
if (geteuid() == 0)
dd->dd_gid = stat_buf.st_gid;
if ((flags & DD_OPEN_FD_ONLY))
{
dd->dd_md_fd = dd_open_meta_data_dir(dd);
return dd;
}
}
errno = 0;
if (dd_lock(dd, WAIT_FOR_OTHER_PROCESS_USLEEP, flags) < 0)
{
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 problem directory", dd->dd_dirname);
goto fail_with_close;
}
if (errno == EAGAIN && (flags & DD_DONT_WAIT_FOR_LOCK))
{
log_debug("Can't access locked directory '%s'", dd->dd_dirname);
goto fail_with_close;
}
if (!(flags & DD_OPEN_READONLY))
{
log_debug("'%s' can't be opened for writing", dd->dd_dirname);
goto fail_with_close;
}
if (errno != EACCES)
{
VERB3 perror_msg("failed to lock dump directory '%s'", dd->dd_dirname);
goto fail_with_close;
}
/* Directory is not writable. If it seems to be readable,
* return "read only" dd, not NULL
*
* Does the directory have 'r' flag?
*/
if (faccessat(dd->dd_fd, ".", R_OK, AT_SYMLINK_NOFOLLOW) != 0)
{
VERB3 perror_msg("failed to lock dump directory '%s'", dd->dd_dirname);
goto fail_with_close;
}
/* dd_check prints out good log messages */
if(dd_check(dd) != NULL)
goto fail_with_close;
/* The dd is opened in READONLY moded, continue.*/
}
return dd;
cant_access:
if (errno == ENOENT || errno == ENOTDIR)
{
if (!(flags & DD_FAIL_QUIETLY_ENOENT))
error_msg("'%s' does not exist", dd->dd_dirname);
}
else
{
if (!(flags & DD_FAIL_QUIETLY_EACCES))
perror_msg("Can't access '%s'", dd->dd_dirname);
}
fail_with_close:
dd_close(dd);
return NULL;
}
struct dump_dir *dd_fdopendir(struct dump_dir *dd, int flags)
{
if ((flags & DD_OPEN_FD_ONLY))
error_msg_and_die("the passed flags must not contain DD_OPEN_FD_ONLY");
if (dd->dd_fd < 0)
error_msg_and_die("the dump directory was not initialized yet");
if (dd->locked)
error_msg_and_die("the dump directory is already locked");
return dd_do_open(dd, NULL, flags);
}
struct dump_dir *dd_opendir(const char *dir, int flags)
{
struct dump_dir *dd = dd_init();
return dd_do_open(dd, dir, flags);
}
/* Create a fresh empty debug dump dir which is owned bu the calling user. If
* you want to create the directory with meaningful ownership you should
* consider using dd_create() function or you can modify the ownership
* afterwards by calling dd_reset_ownership() function.
*
* ABRT owns dump dir:
* We should not allow users to write new files or write into existing ones,
* but they should be able to read them.
*
* 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. We can't set dir's
* uid to crashed applications's user uid because owner can modify dir's
* mode and ownership.
*
* Advantages:
* Safeness
*
* Disadvantages:
* This approach leads to stealing of directories because events requires
* write access to a dump directory and events are run under non root (abrt)
* user while reporting.
*
* This approach allows group members to see crashes of other members.
* Institutions like schools uses one common group for all students.
*
* User owns dump dir:
* We grant ownership of dump directories to the user (read/write access).
*
* We set set dir's uid to crashed applications's user uid, and we set gid to
* abrt's group id. We allow write access to group because we want to allow
* abrt binaries to process dump directories.
*
* Advantages:
* No disadvantages from the previous approach
*
* Disadvantages:
* In order to protect the system dump directories must be saved on
* noncritical filesystem (e.g. /tmp or /var/tmp).
*
*
* @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
*
* create_dump_dir_from_problem_data() function:
* Currently known callers:
* abrt server: uid=uid of user's executable
* this runs under 0:0
* - clinets: python hook, ruby hook
* abrt dbus: uid=uid of user's executable
* this runs under 0:0
* - clients: setroubleshootd, abrt python
*/
struct dump_dir *dd_create_skeleton(const char *dir, uid_t uid, mode_t mode, int flags)
{
mode_t dir_mode = DD_MODE_TO_DIR_MODE(mode);
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);
goto fail;
}
/* 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")
*/
int r;
if ((flags & DD_CREATE_PARENTS))
r = g_mkdir_with_parents(dd->dd_dirname, dir_mode);
else
r = mkdir(dd->dd_dirname, dir_mode);
if (r != 0)
{
perror_msg("Can't create directory '%s'", dir);
goto fail;
}
dd->dd_fd = open(dd->dd_dirname, O_DIRECTORY | O_NOFOLLOW);
if (dd->dd_fd < 0)
{
perror_msg("Can't open newly created directory '%s'", dir);
goto fail;
}
struct stat stat_sb;
if (fstat(dd->dd_fd, &stat_sb) < 0)
{
perror_msg("stat(%s)", dd->dd_dirname);
goto fail;
}
if (dd_lock(dd, CREATE_LOCK_USLEEP, /*flags:*/ 0) < 0)
goto fail;
/* mkdir's mode (above) can be affected by umask, fix it */
if (fchmod(dd->dd_fd, dir_mode) == -1)
{
perror_msg("Can't change mode of '%s'", dir);
goto fail;
}
/* Initiliaze dd_uid and dd_gid to sane values which reflect the reality.
*/
dd->dd_uid = stat_sb.st_uid;
dd->dd_gid = stat_sb.st_gid;
/* Create META-DATA directory with real fs attributes which must be changed
* in dd_reset_ownership(), when populating of a new dump directory is
* done.
*
* It allows daemons to create a dump directory, populate the directory as
* root and then switch the ownership to the real user.
*/
dd->dd_md_fd = dd_create_subdir(dd->dd_fd, META_DATA_DIR_NAME, dd->dd_uid, dd->dd_gid, dir_mode);
if (dd->dd_md_fd < 0)
{
error_msg("Can't create meta-data directory");
goto fail;
}
if (dd_set_owner(dd, dd->dd_uid) < 0)
{
log_debug("Failed to initialized 'owner'");
goto fail;
}
if (uid != (uid_t)-1L)
{
dd->dd_uid = 0;
dd->dd_gid = 0;
#if DUMP_DIR_OWNED_BY_USER > 0
/* Check crashed application's uid */
struct passwd *pw = getpwuid(uid);
if (pw)
dd->dd_uid = pw->pw_uid;
else
error_msg("User %lu does not exist, using uid 0", (long)uid);
if (dd_g_fs_group_gid == (uid_t)-1)
{
/* Get ABRT's group gid */
struct group *gr = getgrnam("abrt");
if (gr)
dd->dd_gid = gr->gr_gid;
else
error_msg("Group 'abrt' does not exist, using gid 0");
}
else
dd->dd_gid = dd_g_fs_group_gid;
#else
/* Get ABRT's user uid */
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 gid */
pw = getpwuid(uid);
if (pw)
dd->dd_gid = pw->pw_gid;
else
error_msg("User %lu does not exist, using gid 0", (long)uid);
#endif
}
/* Initialize dd_time to some sane value */
dd->dd_time = time(NULL);
return dd;
fail:
dd_close(dd);
return NULL;
}
/* Resets ownership of the given directory to UID and GID according to values
* in dd_create_skeleton().
*/
int dd_reset_ownership(struct dump_dir *dd)
{
if (!dd->locked)
error_msg_and_die("dump_dir is not opened"); /* bug */
int r = fchown(dd->dd_fd, dd->dd_uid, dd->dd_gid);
if (r < 0)
{
perror_msg("Can't change '%s' ownership to %lu:%lu", dd->dd_dirname,
(long)dd->dd_uid, (long)dd->dd_gid);
}
if (dd_chown_meta_data(dd, dd->dd_uid, dd->dd_gid) != 0)
error_msg("Failed to reset ownership of meta-data");
/* We ignore failures above, so we will ignore failures here too.
* The meta-data owner already exist (created by dd_create_skeleton).
*/
dd_set_owner(dd, dd->dd_uid);
return r;
}
/* Calls dd_create_skeleton() and dd_reset_ownership().
*/
struct dump_dir *dd_create(const char *dir, uid_t uid, mode_t mode)
{
struct dump_dir *dd = dd_create_skeleton(dir, uid, mode, DD_CREATE_PARENTS);
if (dd == NULL)
return NULL;
/* ignore results */
dd_reset_ownership(dd);
return dd;
}
void dd_create_basic_files(struct dump_dir *dd, uid_t uid, const char *chroot_dir)
{
char long_str[sizeof(long) * 3 + 2];
const time_t t = parse_time_file_at(dd->dd_fd, FILENAME_TIME);
if (t < 0)
{
sprintf(long_str, "%lu", (long)dd->dd_time);
/* first occurrence */
dd_save_text(dd, FILENAME_TIME, long_str);
/* last occurrence */
dd_save_text(dd, FILENAME_LAST_OCCURRENCE, long_str);
}
else
{
dd->dd_time = t;
}
/* it doesn't make sense to create the uid file if uid == -1 */
/* and 'owner' is set since dd_create_skeleton */
if (uid != (uid_t)-1L)
{
/* Failure is not a problem here, because we still have the fs
* attributes and there is only a little chance that the old value
* gets lost. */
dd_set_owner(dd, uid);
snprintf(long_str, sizeof(long_str), "%li", (long)uid);
dd_save_text(dd, FILENAME_UID, long_str);
}
struct utsname buf;
uname(&buf); /* never fails */
/* Check if files already exist in dumpdir as they might have
* more relevant information about the problem
*/
if (!dd_exist(dd, FILENAME_KERNEL))
dd_save_text(dd, FILENAME_KERNEL, buf.release);
if (!dd_exist(dd, FILENAME_ARCHITECTURE))
dd_save_text(dd, FILENAME_ARCHITECTURE, buf.machine);
if (!dd_exist(dd, FILENAME_HOSTNAME))
dd_save_text(dd, FILENAME_HOSTNAME, buf.nodename);
char *release = load_text_file("/etc/os-release",
DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE | DD_OPEN_FOLLOW);
if (release)
{
dd_save_text(dd, FILENAME_OS_INFO, release);
free(release);
}
if (chroot_dir)
copy_file_from_chroot(dd, FILENAME_OS_INFO_IN_ROOTDIR, chroot_dir, "/etc/os-release");
/* if release exists in dumpdir don't create it, but don't warn
* if it doesn't
* i.e: anaconda doesn't have /etc/{fedora,redhat}-release and trying to load it
* results in errors: rhbz#725857
*/
release = dd_load_text_ext(dd, FILENAME_OS_RELEASE,
DD_FAIL_QUIETLY_ENOENT | DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE);
if (!release)
{
release = load_text_file("/etc/system-release",
DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE | DD_OPEN_FOLLOW);
if (!release)
release = load_text_file("/etc/redhat-release",
DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE | DD_OPEN_FOLLOW);
if (!release)
release = load_text_file("/etc/SuSE-release", DD_OPEN_FOLLOW);
char *newline = strchr(release, '\n');
if (newline)
*newline = '\0';
dd_save_text(dd, FILENAME_OS_RELEASE, release);
if (chroot_dir)
copy_file_from_chroot(dd, FILENAME_OS_RELEASE_IN_ROOTDIR, chroot_dir, "/etc/system-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 */
dd_init_next_file(dd);
char *short_name;
while (dd_get_next_file(dd, &short_name, /*full_name*/ NULL))
{
/* The current process has to have read access at least */
int fd = secure_openat_read(dd->dd_fd, short_name);
if (fd < 0)
goto next;
if (fchmod(fd, dd->mode) != 0)
perror_msg("Can't change '%s/%s' mode to 0%o", dd->dd_dirname, short_name,
(unsigned)dd->mode);
if (fchown(fd, dd->dd_uid, dd->dd_gid) != 0)
perror_msg("Can't change '%s/%s' ownership to %lu:%lu", dd->dd_dirname, short_name,
(long)dd->dd_uid, (long)dd->dd_gid);
close(fd);
next:
free(short_name);
}
/* No need to check return value, the functions print good messages.
* There are two approaches for handling errors in libreport:
* - print out a warning message and keep status quo
* - terminate the process
*/
dd_sanitize_mode_meta_data(dd);
dd_chown_meta_data(dd, dd->dd_uid, dd->dd_gid);
}
static int delete_file_dir(int dir_fd, bool skip_lock_file)
{
DIR *d;
int ret = fdreopen(dir_fd, &d);
if (ret < 0)
{
/* 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 (ret == -ENOENT || ret == -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;
}
if (unlinkat(dir_fd, dent->d_name, /*only files*/0) == -1 && errno != ENOENT)
{
int err = 0;
if (errno == EISDIR)
{
errno = 0;
int subdir_fd = openat(dir_fd, dent->d_name, O_DIRECTORY);
if (subdir_fd < 0)
{
perror_msg("Can't open sub-dir'%s'", dent->d_name);
closedir(d);
return -1;
}
else
{
err = delete_file_dir(subdir_fd, /*skip_lock_file:*/ false);
close(subdir_fd);
if (err == 0)
unlinkat(dir_fd, dent->d_name, AT_REMOVEDIR);
}
}
if (errno || err)
{
perror_msg("Can't remove '%s'", dent->d_name);
closedir(d);
return -1;
}
}
}
/* 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)
xunlinkat(dir_fd, ".lock", /*only files*/0);
closedir(d);
return 0;
}
static int dd_delete_meta_data(struct dump_dir *dd)
{
if (!dd->locked)
{
error_msg("Can't remove meta-data of unlocked problem directory %s", dd->dd_dirname);
return -1;
}
int dd_md_fd = dd_get_meta_data_dir_fd(dd, /*no create*/0);
if (dd_md_fd < 0)
return 0;
if (delete_file_dir(dd_md_fd, /*skip_lock_file:*/ true) != 0)
{
perror_msg("Can't remove meta-data from '"META_DATA_DIR_NAME"'");
return -2;
}
dd_close_meta_data_dir(dd);
if (unlinkat(dd->dd_fd, META_DATA_DIR_NAME, AT_REMOVEDIR))
{
perror_msg("Can't remove meta-data directory '"META_DATA_DIR_NAME"'");
return -3;
}
return 0;
}
int dd_delete(struct dump_dir *dd)
{
int retval;
retval = 0;
if (!dd->locked)
{
error_msg("unlocked problem directory %s cannot be deleted", dd->dd_dirname);
retval = -1;
goto close;
}
if (dd_delete_meta_data(dd) != 0)
{
retval = -2;
goto close;
}
if (delete_file_dir(dd->dd_fd, /*skip_lock_file:*/ true) != 0)
{
perror_msg("Can't remove contents of directory '%s'", dd->dd_dirname);
retval = -2;
goto close;
}
unsigned cnt = RMDIR_FAIL_COUNT;
do {
if (rmdir(dd->dd_dirname) == 0)
break;
/* 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);
if (cnt == 0)
{
perror_msg("Can't remove directory '%s'", dd->dd_dirname);
retval = -3;
}
dd->locked = 0; /* delete_file_dir already removed .lock */
close:
dd_close(dd);
return retval;
}
int dd_chown(struct dump_dir *dd, uid_t new_uid)
{
if (!dd->locked)
error_msg_and_die("dump_dir is not opened"); /* bug */
struct stat statbuf;
if (fstat(dd->dd_fd, &statbuf) != 0)
{
perror_msg("stat('%s')", dd->dd_dirname);
return 1;
}
struct passwd *pw = getpwuid(new_uid);
if (!pw)
{
error_msg("UID %ld is not found in user database", (long)new_uid);
return 1;
}
#if DUMP_DIR_OWNED_BY_USER > 0
uid_t owners_uid = pw->pw_uid;
gid_t groups_gid = statbuf.st_gid;
#else
uid_t owners_uid = statbuf.st_uid;
gid_t groups_gid = pw->pw_gid;
#endif
int chown_res = fchown(dd->dd_fd, owners_uid, groups_gid);
if (chown_res)
perror_msg("fchown('%s')", dd->dd_dirname);
else
{
dd_init_next_file(dd);
char *short_name;
while (chown_res == 0 && dd_get_next_file(dd, &short_name, /*full_name*/ NULL))
{
/* The current process has to have read access at least */
int fd = secure_openat_read(dd->dd_fd, short_name);
if (fd < 0)
goto next;
log_debug("chowning %s", short_name);
chown_res = fchown(fd, owners_uid, groups_gid);
if (chown_res)
{
perror_msg("fchownat('%s')", short_name);
break;
}
close(fd);
next:
free(short_name);
}
}
if (chown_res == 0)
chown_res = dd_chown_meta_data(dd, owners_uid, groups_gid);
if (chown_res == 0)
{
dd->dd_uid = owners_uid;
dd->dd_gid = groups_gid;
}
if (chown_res == 0)
chown_res = dd_set_owner(dd, (long)dd->dd_uid);
return chown_res;
}
static char *load_text_from_file_descriptor(int fd, const char *path, int flags)
{
if (fd == -1)
{
if (!(flags & DD_FAIL_QUIETLY_ENOENT))
perror_msg("Can't open file '%s' for reading", path);
return (flags & DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE ? NULL : xstrdup(""));
}
/* Why? Because half a million read syscalls of one byte each isn't fun.
* FILE-based IO buffers reads.
*/
FILE *fp = fdopen(fd, "r");
if (!fp)
die_out_of_memory();
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); /* this also closes fd */
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 char *load_text_file_at(int dir_fd, const char *name, unsigned flags)
{
assert(name[0] != '/');
const int fd = openat(dir_fd, name, O_RDONLY | ((flags & DD_OPEN_FOLLOW) ? 0 : O_NOFOLLOW));
return load_text_from_file_descriptor(fd, name, flags);
}
char *load_text_file(const char *path, unsigned flags)
{
const int fd = open(path, O_RDONLY | ((flags & DD_OPEN_FOLLOW) ? 0 : O_NOFOLLOW));
return load_text_from_file_descriptor(fd, path, flags);
}
static void copy_file_from_chroot(struct dump_dir* dd, const char *name, const char *chroot_dir, const char *file_path)
{
char *chrooted_name = concat_path_file(chroot_dir, file_path);
char *data = load_text_file(chrooted_name,
DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE | DD_OPEN_FOLLOW);
free(chrooted_name);
if (data)
{
dd_save_text(dd, name, data);
free(data);
}
}
static int create_new_file_at(int dir_fd, int omode, const char *name, uid_t uid, gid_t gid, mode_t mode)
{
assert(name[0] != '/');
assert(omode == O_WRONLY || omode == O_RDWR);
/* the mode is set by the caller, see dd_create() for security analysis */
unlinkat(dir_fd, name, /*remove only files*/0);
int fd = openat(dir_fd, name, omode | O_EXCL | O_CREAT | O_NOFOLLOW, mode);
if (fd < 0)
{
perror_msg("Can't open file '%s' for writing", name);
return -1;
}
if ((uid != (uid_t)-1L) && (fchown(fd, uid, gid) == -1))
{
perror_msg("Can't change '%s' ownership to %lu:%lu", name, (long)uid, (long)gid);
close(fd);
return -1;
}
/* O_CREAT in the open() call above causes that the permissions of the
* created file are (mode & ~umask)
*
* This is true only if we did create file. We are not sure we created it
* in this case - it may exist already.
*/
if (fchmod(fd, mode) == -1)
{
perror_msg("Can't change mode of '%s'", name);
close(fd);
return -1;
}
return fd;
}
static bool save_binary_file_at(int dir_fd, const char *name, const char* data, unsigned size, uid_t uid, gid_t gid, mode_t mode)
{
const int fd = create_new_file_at(dir_fd, O_WRONLY, name, uid, gid, mode);
if (fd < 0)
goto fail;
const unsigned r = full_write(fd, data, size);
close(fd);
if (r != size)
goto fail;
return true;
fail:
error_msg("Can't save file '%s'", name);
return false;
}
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 */
if (!dd_validate_element_name(name))
{
error_msg("Cannot load text. '%s' is not a valid file name", name);
if ((flags & DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE))
return NULL;
xfunc_die();
}
/* Compat with old abrt dumps. Remove in abrt-2.1 */
if (strcmp(name, "release") == 0)
name = FILENAME_OS_RELEASE;
return load_text_file_at(dd->dd_fd, name, flags);
}
char* dd_load_text(const struct dump_dir *dd, const char *name)
{
return dd_load_text_ext(dd, name, /*flags:*/ 0);
}
int dd_load_int32(const struct dump_dir *dd, const char *name, int32_t *value)
{
unsigned long long parsed = 0;
const int ret = read_number_from_file_at(dd->dd_fd, name, "int32_t",
sizeof(int32_t), INT32_MIN, INT32_MAX, &parsed);
if (ret == 0)
*value = (int32_t)parsed;
return ret;
}
int dd_load_uint32(const struct dump_dir *dd, const char *name, uint32_t *value)
{
unsigned long long parsed = 0;
const int ret = read_number_from_file_at(dd->dd_fd, name, "uint32_t",
sizeof(uint32_t), 0, UINT32_MAX, &parsed);
if (ret == 0)
*value = (uint32_t)parsed;
return ret;
}
int dd_load_int64(const struct dump_dir *dd, const char *name, int64_t *value)
{
unsigned long long parsed = 0;
const int ret = read_number_from_file_at(dd->dd_fd, name, "int64_t",
sizeof(int64_t), INT64_MIN, INT64_MAX, &parsed);
if (ret == 0)
*value = (int64_t)parsed;
return ret;
}
int dd_load_uint64(const struct dump_dir *dd, const char *name, uint64_t *value)
{
unsigned long long parsed = 0;
const int ret = read_number_from_file_at(dd->dd_fd, name, "uint64_t",
sizeof(uint64_t), 0, UINT64_MAX, &parsed);
if (ret == 0)
*value = (uint64_t)parsed;
return ret;
}
int dd_get_env_variable(struct dump_dir *dd, const char *name, char **value)
{
const int fd = openat(dd->dd_fd, FILENAME_ENVIRON, O_RDONLY | O_NOFOLLOW);
if (fd < 0)
return -errno;
const int r = get_env_variable_ext(fd, '\n', name, value);
close(fd);
return r;
}
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 */
if (!dd_validate_element_name(name))
error_msg_and_die("Cannot save text. '%s' is not a valid file name", name);
save_binary_file_at(dd->dd_fd, name, data, strlen(data), dd->dd_uid, dd->dd_gid, dd->mode);
}
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 */
if (!dd_validate_element_name(name))
error_msg_and_die("Cannot save binary. '%s' is not a valid file name", name);
save_binary_file_at(dd->dd_fd, name, data, size, dd->dd_uid, dd->dd_gid, dd->mode);
}
int dd_item_stat(struct dump_dir *dd, const char *name, struct stat *statbuf)
{
if (!dd_validate_element_name(name))
return -EINVAL;
int r = fstatat(dd->dd_fd, name, statbuf, AT_SYMLINK_NOFOLLOW);
if (r != 0)
return -errno;
if (!S_ISREG(statbuf->st_mode))
return -EMEDIUMTYPE;
return 0;
}
long dd_get_item_size(struct dump_dir *dd, const char *name)
{
long size = -1;
struct stat statbuf;
int r = dd_item_stat(dd, name, &statbuf);
const char *error = NULL;
if (r == 0)
size = statbuf.st_size;
else if (r == -ENOENT)
size = 0;
else if (r == -EINVAL)
error = "Is an invalid item name";
else if (r == -EMEDIUMTYPE)
error = "Is not a regular file";
else
error = strerror(errno);
if (error != NULL)
error_msg("Cannot get item size ('%s'): %s", name, error);
return size;
}
int dd_delete_item(struct dump_dir *dd, const char *name)
{
if (!dd->locked)
error_msg_and_die("dump_dir is not opened"); /* bug */
if (!dd_validate_element_name(name))
{
error_msg("Cannot delete item. '%s' is not a valid file name", name);
return -EINVAL;
}
int res = unlinkat(dd->dd_fd, name, /*only files*/0);
if (res < 0)
{
if (errno == ENOENT)
errno = res = 0;
else
perror_msg("Can't delete file '%s'", name);
}
return res;
}
int dd_open_item(struct dump_dir *dd, const char *name, int flag)
{
if (!dd_validate_element_name(name))
{
error_msg("Cannot open item as FD. '%s' is not a valid file name", name);
return -EINVAL;
}
if (flag == O_RDONLY)
return openat(dd->dd_fd, name, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
if (!dd->locked)
error_msg_and_die("dump_dir is not locked"); /* bug */
if (flag == O_RDWR)
return create_new_file_at(dd->dd_fd, O_RDWR, name, dd->dd_uid, dd->dd_gid, dd->mode);
error_msg("invalid open item flag");
return -ENOTSUP;
}
FILE *dd_open_item_file(struct dump_dir *dd, const char *name, int flag)
{
const int item_fd = dd_open_item(dd, name, flag);
if (item_fd < 0)
return NULL;
const char *mode = flag == O_RDONLY ? "r" : "w+";
return fdopen(item_fd, mode);
}
static int _dd_get_next_file_dent(struct dump_dir *dd, struct dirent **dent)
{
if (dd->next_dir == NULL)
return 0;
while ((*dent = readdir(dd->next_dir)) != NULL)
{
if (is_regular_file_at(*dent, dd->dd_fd))
return 1;
}
dd_clear_next_file(dd);
return 0;
}
int dd_get_items_count(struct dump_dir *dd)
{
int retval = 0;
if (dd_init_next_file(dd) == NULL)
return -EIO;
while (dd_get_next_file(dd, NULL, NULL))
{
++retval;
/* Check overflow */
if (retval < 0)
{
dd_clear_next_file(dd);
return -E2BIG;
}
}
return retval;
}
off_t dd_compute_size(struct dump_dir *dd, int flags)
{
off_t retval = 0;
if (dd_init_next_file(dd) == NULL)
return -EIO;
struct stat statbuf;
struct dirent *dent;
while (_dd_get_next_file_dent(dd, &dent))
{
if (fstatat(dd->dd_fd, dent->d_name, &statbuf, AT_SYMLINK_NOFOLLOW) != 0)
{
retval = -errno;
goto finito;
}
retval += statbuf.st_size;
/* Check overflow */
if (retval < 0)
{
retval = -E2BIG;
goto finito;
}
}
finito:
dd_clear_next_file(dd);
return retval;
}
DIR *dd_init_next_file(struct dump_dir *dd)
{
// if (!dd->locked)
// error_msg_and_die("dump_dir is not opened"); /* bug */
int opendir_fd = dup(dd->dd_fd);
if (opendir_fd < 0)
{
perror_msg("dd_init_next_file: dup(dd_fd)");
return NULL;
}
dd_clear_next_file(dd);
lseek(opendir_fd, SEEK_SET, 0);
dd->next_dir = fdopendir(opendir_fd);
if (!dd->next_dir)
{
error_msg("Can't open directory '%s'", dd->dd_dirname);
close(opendir_fd);
}
return dd->next_dir;
}
void dd_clear_next_file(struct dump_dir *dd)
{
if (dd->next_dir == NULL)
return;
closedir(dd->next_dir);
dd->next_dir = NULL;
}
int dd_get_next_file(struct dump_dir *dd, char **short_name, char **full_name)
{
struct dirent *dent;
if (0 == _dd_get_next_file_dent(dd, &dent))
return 0;
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;
}
/* reported_to handling */
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 (add_reported_to_data(&reported_to, line))
dd_save_text(dd, FILENAME_REPORTED_TO, reported_to);
free(reported_to);
}
void add_reported_to_entry(struct dump_dir *dd, struct report_result *result)
{
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 (add_reported_to_entry_data(&reported_to, result))
dd_save_text(dd, FILENAME_REPORTED_TO, reported_to);
free(reported_to);
}
report_result_t *find_in_reported_to(struct dump_dir *dd, const char *report_label)
{
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)
return NULL;
report_result_t *result = find_in_reported_to_data(reported_to, report_label);
free(reported_to);
return result;
}
GList *read_entire_reported_to(struct dump_dir *dd)
{
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)
return NULL;
GList *result = read_entire_reported_to_data(reported_to);
free(reported_to);
return result;
}
/* reported_to handling end */
int dd_rename(struct dump_dir *dd, const char *new_path)
{
if (!dd->locked)
{
error_msg("unlocked problem directory %s cannot be renamed", dd->dd_dirname);
return -1;
}
/* Keeps the opened file descriptor valid */
int res = rename(dd->dd_dirname, new_path);
if (res == 0)
{
free(dd->dd_dirname);
dd->dd_dirname = rm_trailing_slashes(new_path);
}
return res;
}
/* Utility function */
void delete_dump_dir(const char *dirname)
{
struct dump_dir *dd = dd_opendir(dirname, /*flags:*/ 0);
if (dd)
{
dd_delete(dd);
}
}
bool uid_in_group(uid_t uid, gid_t gid)
{
char **tmp;
struct passwd *pwd = getpwuid(uid);
if (!pwd)
return FALSE;
if (pwd->pw_gid == gid)
return TRUE;
struct group *grp = getgrgid(gid);
if (!(grp && grp->gr_mem))
return FALSE;
for (tmp = grp->gr_mem; *tmp != NULL; tmp++)
{
if (g_strcmp0(*tmp, pwd->pw_name) == 0)
{
log_debug("user %s belongs to group: %s", pwd->pw_name, grp->gr_name);
return TRUE;
}
}
log_info("user %s DOESN'T belong to group: %s", pwd->pw_name, grp->gr_name);
return FALSE;
}
int dd_stat_for_uid(struct dump_dir *dd, uid_t uid)
{
int ddstat = 0;
bool uid_test;
if (uid == dd_g_super_user_uid)
{
log_debug("directory accessible by super-user");
ddstat |= DD_STAT_ACCESSIBLE_BY_UID;
}
#define DD_OWNER_FLAGS (DD_STAT_ACCESSIBLE_BY_UID | DD_STAT_OWNED_BY_UID)
if (dd->dd_uid == dd_g_super_user_uid)
{
log_debug("directory owned by super-user: checking meta-data");
const uid_t owner = dd_get_owner(dd);
if (owner < 0)
goto fsattributes;
if (owner == uid)
{
log_debug("meta-data: %ld uid owns directory", (long)uid);
ddstat |= DD_OWNER_FLAGS;
goto finito;
}
uid_t no_owner_uid = (uid_t)-1;
int ret = get_no_owner_uid(&no_owner_uid);
if ( ret >= 0
&& owner == no_owner_uid)
{
log_debug("meta-data: directory is accessible by %ld uid", (long)uid);
ddstat |= DD_STAT_ACCESSIBLE_BY_UID;
ddstat |= DD_STAT_NO_OWNER;
}
goto finito;
}
fsattributes:
if (dd->mode & S_IROTH)
{
log_debug("directory is accessible by %ld uid", (long)uid);
ddstat |= DD_STAT_ACCESSIBLE_BY_UID;
}
#if DUMP_DIR_OWNED_BY_USER > 0
uid_test = uid == dd->dd_uid;
#else
uid_test = uid_in_group(uid, dd->dd_gid);
#endif
if (uid_test)
{
log_debug("fs attributes: %ld uid owns directory", (long)uid);
ddstat |= DD_OWNER_FLAGS;
}
#undef DD_OWNER_FLAGS
finito:
log_debug("UID=%d, %s: %o", uid, dd->dd_dirname, ddstat);
return ddstat;
}
int dump_dir_stat_for_uid(const char *dirname, uid_t uid)
{
struct dump_dir *dd = dd_opendir(dirname, DD_OPEN_FD_ONLY);
if (dd == NULL)
return -1;
int r = dd_stat_for_uid(dd, uid);
dd_close(dd);
return r;
}
int dd_accessible_by_uid(struct dump_dir *dd, uid_t uid)
{
int ddstat = dd_stat_for_uid(dd, uid);
if (ddstat >= 0)
return ddstat & DD_STAT_ACCESSIBLE_BY_UID;
VERB3 pwarn_msg("can't determine accessibility for %ld uid", (long)uid);
return 0;
}
int dump_dir_accessible_by_uid(const char *dirname, uid_t uid)
{
int ddstat = dump_dir_stat_for_uid(dirname, uid);
if (ddstat >= 0)
return ddstat & DD_STAT_ACCESSIBLE_BY_UID;
VERB3 pwarn_msg("can't determine accessibility of '%s' by %ld uid", dirname, (long)uid);
return 0;
}
int dd_mark_as_notreportable(struct dump_dir *dd, const char *reason)
{
if (!dd->locked)
{
error_msg("dump_dir is not locked for writing");
return -1;
}
dd_save_text(dd, FILENAME_NOT_REPORTABLE, reason);
return 0;
}
int dd_copy_file(struct dump_dir *dd, const char *name, const char *source_path)
{
if (!dd_validate_element_name(name))
error_msg_and_die("Cannot test existence. '%s' is not a valid file name", name);
log_debug("copying '%s' to '%s' at '%s'", source_path, name, dd->dd_dirname);
unlinkat(dd->dd_fd, name, /*remove only files*/0);
off_t copied = copy_file_ext_at(source_path, dd->dd_fd, name, DEFAULT_DUMP_DIR_MODE,
dd->dd_uid, dd->dd_gid, O_RDONLY, O_WRONLY | O_TRUNC | O_EXCL | O_CREAT);
if (copied < 0)
error_msg("Can't copy %s to %s at '%s'", source_path, name, dd->dd_dirname);
else
log_debug("copied %li bytes", (unsigned long)copied);
return copied < 0;
}
int dd_copy_file_at(struct dump_dir *dd, const char *name, int src_dir_fd, const char *src_name)
{
if (!dd_validate_element_name(name))
error_msg_and_die("Cannot test existence. '%s' is not a valid file name", name);
log_debug("copying file '%s' to element '%s' at '%s'", src_name, name, dd->dd_dirname);
unlinkat(dd->dd_fd, name, /*remove only files*/0);
off_t copied = copy_file_ext_2at(src_dir_fd, src_name, dd->dd_fd, name,
DEFAULT_DUMP_DIR_MODE,
dd->dd_uid, dd->dd_gid,
O_RDONLY,
O_WRONLY | O_TRUNC | O_EXCL | O_CREAT);
if (copied < 0)
error_msg("Can't copy file '%s' to element '%s' at '%s'", src_name, name, dd->dd_dirname);
else
log_debug("copied %li bytes", (unsigned long)copied);
return copied < 0;
}
int dd_copy_file_unpack(struct dump_dir *dd, const char *name, const char *source_path)
{
if (!dd_validate_element_name(name))
error_msg_and_die("Cannot test existence. '%s' is not a valid file name", name);
log_debug("unpacking '%s' to '%s' at '%s'", source_path, name, dd->dd_dirname);
unlinkat(dd->dd_fd, name, /*remove only files*/0);
off_t copied = decompress_file_ext_at(source_path, dd->dd_fd, name, DEFAULT_DUMP_DIR_MODE,
dd->dd_uid, dd->dd_gid, O_RDONLY, O_WRONLY | O_TRUNC | O_EXCL | O_CREAT);
if (copied != 0)
error_msg("Can't copy %s to %s at '%s'", source_path, name, dd->dd_dirname);
else
log_debug("unpackaged file '%s'", source_path);
return copied < 0;
}
/* flags - for future needs */
int dd_create_archive(struct dump_dir *dd, const char *archive_name,
const_string_vector_const_ptr_t exclude_elements, int flags)
{
if (suffixcmp(archive_name, ".tar.gz") != 0)
return -ENOSYS;
int result = 0;
pid_t child;
TAR* tar = NULL;
int pipe_from_parent_to_child[2];
xpipe(pipe_from_parent_to_child);
child = fork();
if (child < 0)
{
result = -errno;
/* Don't die, let the caller to execute his clean-up code. */
perror_msg("vfork");
return result;
}
if (child == 0)
{
/* child */
close(pipe_from_parent_to_child[1]);
xmove_fd(pipe_from_parent_to_child[0], 0);
int fd = open(archive_name, O_WRONLY | O_CREAT | O_EXCL, 0600);
if (fd < 0)
{
/* This r might interfer with exit status of gzip, but it is
* very unlikely (man 1 gzip):
* Exit status is normally 0; if an error occurs, exit status is
* 1. If a warning occurs, exit status is 2.
*/
result = errno == EEXIST ? 100 : 3;
perror_msg("Can't open '%s'", archive_name);
exit(result);
}
xmove_fd(fd, 1);
execlp("gzip", "gzip", NULL);
perror_msg_and_die("Can't execute '%s'", "gzip");
}
close(pipe_from_parent_to_child[0]);
/* If child died (say, in xopen), then parent might get SIGPIPE.
* We want to properly unlock dd, therefore we must not die on SIGPIPE:
*/
sighandler_t old_handler = signal(SIGPIPE, SIG_IGN);
/* Create tar writer object */
if (tar_fdopen(&tar, pipe_from_parent_to_child[1], archive_name,
/*fileops:(standard)*/ NULL, O_WRONLY | O_CREAT, 0644, TAR_GNU) != 0)
{
result = -errno;
log_warning(_("Failed to open TAR writer"));
goto finito;
}
/* Write data to the tarball */
dd_init_next_file(dd);
char *short_name, *full_name;
while (dd_get_next_file(dd, &short_name, &full_name))
{
if (!(exclude_elements && is_in_string_list(short_name, exclude_elements)))
{
if (tar_append_file(tar, full_name, short_name))
result = -errno;
}
free(short_name);
free(full_name);
if (result != 0)
goto finito;
}
/* Close tar writer... */
if (tar_append_eof(tar) != 0)
{
result = -errno;
log_warning(_("Failed to finalize TAR archive"));
goto finito;
}
finito:
signal(SIGPIPE, old_handler);
if (tar != NULL && tar_close(tar) != 0)
{
result = -errno;
log_warning(_("Failed to close TAR writer"));
}
/* ...and check that gzip child finished successfully */
int status;
safe_waitpid(child, &status, 0);
if (status != 0)
{
result = -ECHILD;
if (WIFSIGNALED(status))
log_warning(_("gzip killed with signal %d"), WTERMSIG(status));
else if (WIFEXITED(status))
{
if (WEXITSTATUS(status) == 100)
result = -EEXIST;
else
log_warning(_("gzip exited with %d"), WEXITSTATUS(status));
}
else
log_warning(_("gzip process failed"));
}
return result;
}
off_t dd_copy_fd(struct dump_dir *dd, const char *name, int fd, int copy_flags, off_t maxsize)
{
if (!dd_validate_element_name(name))
error_msg_and_die("Cannot test existence. '%s' is not a valid file name", name);
log_debug("Saving data from file descriptor %d to '%s' at '%s'", fd, name, dd->dd_dirname);
unlinkat(dd->dd_fd, name, /*remove only files*/0);
off_t read = copyfd_ext_at(fd, dd->dd_fd, name, DEFAULT_DUMP_DIR_MODE,
dd->dd_uid, dd->dd_gid, O_WRONLY | O_CREAT | O_EXCL, copy_flags, maxsize);
if (read < 0)
{
error_msg("Can't copy file descriptor %d to %s at '%s'", fd, name, dd->dd_dirname);
/* Destroy the file to get rid of empty files and files with invalid owners */
unlinkat(dd->dd_fd, name, /*remove only files*/0);
}
else if (read > maxsize)
log_debug("Saved %lu Bytes (read %lu Bytes)", (unsigned long)maxsize, (unsigned long)read);
else
log_debug("Saved %lu Bytes", (unsigned long)read);
return read;
}