/** * Write up to size bytes to buf. Return negative on error, and number of * bytes written otherwise. The last case is a kind of an error too. */ static ssize_t write_entry(char *buf, size_t size, Uploader *u) { int r; size_t pos = 0; assert(size <= SSIZE_MAX); for (;;) { switch(u->entry_state) { case ENTRY_CURSOR: { u->current_cursor = mfree(u->current_cursor); r = sd_journal_get_cursor(u->journal, &u->current_cursor); if (r < 0) return log_error_errno(r, "Failed to get cursor: %m"); r = snprintf(buf + pos, size - pos, "__CURSOR=%s\n", u->current_cursor); assert(r >= 0); if ((size_t) r > size - pos) /* not enough space */ return pos; u->entry_state++; if (pos + r == size) { /* exactly one character short, but we don't need it */ buf[size - 1] = '\n'; return size; } pos += r; } _fallthrough_; case ENTRY_REALTIME: { usec_t realtime; r = sd_journal_get_realtime_usec(u->journal, &realtime); if (r < 0) return log_error_errno(r, "Failed to get realtime timestamp: %m"); r = snprintf(buf + pos, size - pos, "__REALTIME_TIMESTAMP="USEC_FMT"\n", realtime); assert(r >= 0); if ((size_t) r > size - pos) /* not enough space */ return pos; u->entry_state++; if (r + pos == size) { /* exactly one character short, but we don't need it */ buf[size - 1] = '\n'; return size; } pos += r; } _fallthrough_; case ENTRY_MONOTONIC: { usec_t monotonic; sd_id128_t boot_id; r = sd_journal_get_monotonic_usec(u->journal, &monotonic, &boot_id); if (r < 0) return log_error_errno(r, "Failed to get monotonic timestamp: %m"); r = snprintf(buf + pos, size - pos, "__MONOTONIC_TIMESTAMP="USEC_FMT"\n", monotonic); assert(r >= 0); if ((size_t) r > size - pos) /* not enough space */ return pos; u->entry_state++; if (r + pos == size) { /* exactly one character short, but we don't need it */ buf[size - 1] = '\n'; return size; } pos += r; } _fallthrough_; case ENTRY_BOOT_ID: { sd_id128_t boot_id; char sid[33]; r = sd_journal_get_monotonic_usec(u->journal, NULL, &boot_id); if (r < 0) return log_error_errno(r, "Failed to get monotonic timestamp: %m"); r = snprintf(buf + pos, size - pos, "_BOOT_ID=%s\n", sd_id128_to_string(boot_id, sid)); assert(r >= 0); if ((size_t) r > size - pos) /* not enough space */ return pos; u->entry_state++; if (r + pos == size) { /* exactly one character short, but we don't need it */ buf[size - 1] = '\n'; return size; } pos += r; } _fallthrough_; case ENTRY_NEW_FIELD: { u->field_pos = 0; r = sd_journal_enumerate_data(u->journal, &u->field_data, &u->field_length); if (r < 0) return log_error_errno(r, "Failed to move to next field in entry: %m"); else if (r == 0) { u->entry_state = ENTRY_OUTRO; continue; } /* We already printed the boot id from the data in * the header, hence let's suppress it here */ if (memory_startswith(u->field_data, u->field_length, "_BOOT_ID=")) continue; if (!utf8_is_printable_newline(u->field_data, u->field_length, false)) { u->entry_state = ENTRY_BINARY_FIELD_START; continue; } u->entry_state++; } _fallthrough_; case ENTRY_TEXT_FIELD: case ENTRY_BINARY_FIELD: { bool done; size_t tocopy; done = size - pos > u->field_length - u->field_pos; if (done) tocopy = u->field_length - u->field_pos; else tocopy = size - pos; memcpy(buf + pos, (char*) u->field_data + u->field_pos, tocopy); if (done) { buf[pos + tocopy] = '\n'; pos += tocopy + 1; u->entry_state = ENTRY_NEW_FIELD; continue; } else { u->field_pos += tocopy; return size; } } case ENTRY_BINARY_FIELD_START: { const char *c; size_t len; c = memchr(u->field_data, '=', u->field_length); if (!c || c == u->field_data) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid field."); len = c - (const char*)u->field_data; /* need space for label + '\n' */ if (size - pos < len + 1) return pos; memcpy(buf + pos, u->field_data, len); buf[pos + len] = '\n'; pos += len + 1; u->field_pos = len + 1; u->entry_state++; } _fallthrough_; case ENTRY_BINARY_FIELD_SIZE: { uint64_t le64; /* need space for uint64_t */ if (size - pos < 8) return pos; le64 = htole64(u->field_length - u->field_pos); memcpy(buf + pos, &le64, 8); pos += 8; u->entry_state++; continue; } case ENTRY_OUTRO: /* need space for '\n' */ if (size - pos < 1) return pos; buf[pos++] = '\n'; u->entry_state++; u->entries_sent++; return pos; default: assert_not_reached("WTF?"); } } assert_not_reached("WTF?"); }
static int parse_argv(int argc, char *argv[]) { enum { ARG_LOG_LEVEL = 0x100, ARG_LOG_TARGET, ARG_LOG_COLOR, ARG_LOG_LOCATION, ARG_EXIT_CODE, ARG_TIMEOUT, }; static const struct option options[] = { { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, { "log-target", required_argument, NULL, ARG_LOG_TARGET }, { "log-color", optional_argument, NULL, ARG_LOG_COLOR }, { "log-location", optional_argument, NULL, ARG_LOG_LOCATION }, { "exit-code", required_argument, NULL, ARG_EXIT_CODE }, { "timeout", required_argument, NULL, ARG_TIMEOUT }, {} }; int c, r; assert(argc >= 1); assert(argv); /* "-" prevents getopt from permuting argv[] and moving the verb away * from argv[1]. Our interface to initrd promises it'll be there. */ while ((c = getopt_long(argc, argv, "-", options, NULL)) >= 0) switch (c) { case ARG_LOG_LEVEL: r = log_set_max_level_from_string(optarg); if (r < 0) log_error_errno(r, "Failed to parse log level %s, ignoring: %m", optarg); break; case ARG_LOG_TARGET: r = log_set_target_from_string(optarg); if (r < 0) log_error_errno(r, "Failed to parse log target %s, ignoring: %m", optarg); break; case ARG_LOG_COLOR: if (optarg) { r = log_show_color_from_string(optarg); if (r < 0) log_error_errno(r, "Failed to parse log color setting %s, ignoring: %m", optarg); } else log_show_color(true); break; case ARG_LOG_LOCATION: if (optarg) { r = log_show_location_from_string(optarg); if (r < 0) log_error_errno(r, "Failed to parse log location setting %s, ignoring: %m", optarg); } else log_show_location(true); break; case ARG_EXIT_CODE: r = safe_atou8(optarg, &arg_exit_code); if (r < 0) log_error_errno(r, "Failed to parse exit code %s, ignoring: %m", optarg); break; case ARG_TIMEOUT: r = parse_sec(optarg, &arg_timeout); if (r < 0) log_error_errno(r, "Failed to parse shutdown timeout %s, ignoring: %m", optarg); break; case '\001': if (!arg_verb) arg_verb = optarg; else log_error("Excess arguments, ignoring"); break; case '?': return -EINVAL; default: assert_not_reached("Unhandled option code."); } if (!arg_verb) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Verb argument missing."); return 0; }
static int parse_argv(int argc, char *argv[]) { enum { ARG_VERSION = 0x100, ARG_STDERR_PRIORITY, ARG_LEVEL_PREFIX }; static const struct option options[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, ARG_VERSION }, { "identifier", required_argument, NULL, 't' }, { "priority", required_argument, NULL, 'p' }, { "stderr-priority", required_argument, NULL, ARG_STDERR_PRIORITY }, { "level-prefix", required_argument, NULL, ARG_LEVEL_PREFIX }, {} }; int c; assert(argc >= 0); assert(argv); while ((c = getopt_long(argc, argv, "+ht:p:", options, NULL)) >= 0) switch (c) { case 'h': help(); return 0; case ARG_VERSION: return version(); case 't': if (isempty(optarg)) arg_identifier = NULL; else arg_identifier = optarg; break; case 'p': arg_priority = log_level_from_string(optarg); if (arg_priority < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse priority value."); break; case ARG_STDERR_PRIORITY: arg_stderr_priority = log_level_from_string(optarg); if (arg_stderr_priority < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse stderr priority value."); break; case ARG_LEVEL_PREFIX: { int k; k = parse_boolean(optarg); if (k < 0) return log_error_errno(k, "Failed to parse level prefix value."); arg_level_prefix = k; break; } case '?': return -EINVAL; default: assert_not_reached("Unhandled option"); } return 1; }
static int run(int argc, char *argv[]) { VolatileMode m = _VOLATILE_MODE_INVALID; const char *path; dev_t devt; int r; log_setup_service(); if (argc > 3) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many arguments. Expected directory and mode."); r = query_volatile_mode(&m); if (r < 0) return log_error_errno(r, "Failed to determine volatile mode from kernel command line."); if (r == 0 && argc >= 2) { /* The kernel command line always wins. However if nothing was set there, the argument passed here wins instead. */ m = volatile_mode_from_string(argv[1]); if (m < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Couldn't parse volatile mode: %s", argv[1]); } if (argc < 3) path = "/sysroot"; else { path = argv[2]; if (isempty(path)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Directory name cannot be empty."); if (!path_is_absolute(path)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Directory must be specified as absolute path."); if (path_equal(path, "/")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Directory cannot be the root directory."); } if (!IN_SET(m, VOLATILE_YES, VOLATILE_OVERLAY)) return 0; r = path_is_mount_point(path, NULL, AT_SYMLINK_FOLLOW); if (r < 0) return log_error_errno(r, "Couldn't determine whether %s is a mount point: %m", path); if (r == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s is not a mount point.", path); r = path_is_temporary_fs(path); if (r < 0) return log_error_errno(r, "Couldn't determine whether %s is a temporary file system: %m", path); if (r > 0) { log_info("%s already is a temporary file system.", path); return 0; } /* We are about to replace the root directory with something else. Later code might want to know what we * replaced here, hence let's save that information as a symlink we can later use. (This is particularly * relevant for the overlayfs case where we'll fully obstruct the view onto the underlying device, hence * querying the backing device node from the file system directly is no longer possible. */ r = get_block_device_harder(path, &devt); if (r < 0) return log_error_errno(r, "Failed to determine device major/minor of %s: %m", path); else if (r > 0) { _cleanup_free_ char *dn = NULL; r = device_path_make_major_minor(S_IFBLK, devt, &dn); if (r < 0) return log_error_errno(r, "Failed to format device node path: %m"); if (symlink(dn, "/run/systemd/volatile-root") < 0) log_warning_errno(errno, "Failed to create symlink /run/systemd/volatile-root: %m"); } if (m == VOLATILE_YES) return make_volatile(path); else { assert(m == VOLATILE_OVERLAY); return make_overlay(path); } }
static int save_external_coredump( const char *context[_CONTEXT_MAX], int input_fd, char **ret_filename, int *ret_node_fd, int *ret_data_fd, uint64_t *ret_size, bool *ret_truncated) { _cleanup_free_ char *fn = NULL, *tmp = NULL; _cleanup_close_ int fd = -1; uint64_t rlimit, process_limit, max_size; struct stat st; uid_t uid; int r; assert(context); assert(ret_filename); assert(ret_node_fd); assert(ret_data_fd); assert(ret_size); r = parse_uid(context[CONTEXT_UID], &uid); if (r < 0) return log_error_errno(r, "Failed to parse UID: %m"); r = safe_atou64(context[CONTEXT_RLIMIT], &rlimit); if (r < 0) return log_error_errno(r, "Failed to parse resource limit '%s': %m", context[CONTEXT_RLIMIT]); if (rlimit < page_size()) { /* Is coredumping disabled? Then don't bother saving/processing the coredump. * Anything below PAGE_SIZE cannot give a readable coredump (the kernel uses * ELF_EXEC_PAGESIZE which is not easily accessible, but is usually the same as PAGE_SIZE. */ return log_info_errno(SYNTHETIC_ERRNO(EBADSLT), "Resource limits disable core dumping for process %s (%s).", context[CONTEXT_PID], context[CONTEXT_COMM]); } process_limit = MAX(arg_process_size_max, storage_size_max()); if (process_limit == 0) return log_debug_errno(SYNTHETIC_ERRNO(EBADSLT), "Limits for coredump processing and storage are both 0, not dumping core."); /* Never store more than the process configured, or than we actually shall keep or process */ max_size = MIN(rlimit, process_limit); r = make_filename(context, &fn); if (r < 0) return log_error_errno(r, "Failed to determine coredump file name: %m"); (void) mkdir_p_label("/var/lib/systemd/coredump", 0755); fd = open_tmpfile_linkable(fn, O_RDWR|O_CLOEXEC, &tmp); if (fd < 0) return log_error_errno(fd, "Failed to create temporary file for coredump %s: %m", fn); r = copy_bytes(input_fd, fd, max_size, 0); if (r < 0) { log_error_errno(r, "Cannot store coredump of %s (%s): %m", context[CONTEXT_PID], context[CONTEXT_COMM]); goto fail; } *ret_truncated = r == 1; if (*ret_truncated) log_struct(LOG_INFO, LOG_MESSAGE("Core file was truncated to %zu bytes.", max_size), "SIZE_LIMIT=%zu", max_size, "MESSAGE_ID=" SD_MESSAGE_TRUNCATED_CORE_STR); if (fstat(fd, &st) < 0) { log_error_errno(errno, "Failed to fstat core file %s: %m", coredump_tmpfile_name(tmp)); goto fail; } if (lseek(fd, 0, SEEK_SET) == (off_t) -1) { log_error_errno(errno, "Failed to seek on %s: %m", coredump_tmpfile_name(tmp)); goto fail; } #if HAVE_XZ || HAVE_LZ4 /* If we will remove the coredump anyway, do not compress. */ if (arg_compress && !maybe_remove_external_coredump(NULL, st.st_size)) { _cleanup_free_ char *fn_compressed = NULL, *tmp_compressed = NULL; _cleanup_close_ int fd_compressed = -1; fn_compressed = strappend(fn, COMPRESSED_EXT); if (!fn_compressed) { log_oom(); goto uncompressed; } fd_compressed = open_tmpfile_linkable(fn_compressed, O_RDWR|O_CLOEXEC, &tmp_compressed); if (fd_compressed < 0) { log_error_errno(fd_compressed, "Failed to create temporary file for coredump %s: %m", fn_compressed); goto uncompressed; } r = compress_stream(fd, fd_compressed, -1); if (r < 0) { log_error_errno(r, "Failed to compress %s: %m", coredump_tmpfile_name(tmp_compressed)); goto fail_compressed; } r = fix_permissions(fd_compressed, tmp_compressed, fn_compressed, context, uid); if (r < 0) goto fail_compressed; /* OK, this worked, we can get rid of the uncompressed version now */ if (tmp) unlink_noerrno(tmp); *ret_filename = TAKE_PTR(fn_compressed); /* compressed */ *ret_node_fd = TAKE_FD(fd_compressed); /* compressed */ *ret_data_fd = TAKE_FD(fd); /* uncompressed */ *ret_size = (uint64_t) st.st_size; /* uncompressed */ return 0; fail_compressed: if (tmp_compressed) (void) unlink(tmp_compressed); } uncompressed: #endif r = fix_permissions(fd, tmp, fn, context, uid); if (r < 0) goto fail; *ret_filename = TAKE_PTR(fn); *ret_data_fd = TAKE_FD(fd); *ret_node_fd = -1; *ret_size = (uint64_t) st.st_size; return 0; fail: if (tmp) (void) unlink(tmp); return r; }