+DEFINE_PRIVATE_STRING_TABLE_LOOKUP(coredump_storage, CoredumpStorage);
+static DEFINE_CONFIG_PARSE_ENUM(config_parse_coredump_storage, coredump_storage,
+ CoredumpStorage,
+ "Failed to parse storage setting");
+
+typedef enum CoredumpCompression {
+ COREDUMP_COMPRESSION_NONE,
+ COREDUMP_COMPRESSION_XZ,
+ _COREDUMP_COMPRESSION_MAX,
+ _COREDUMP_COMPRESSION_INVALID = -1
+} CoredumpCompression;
+
+static const char* const coredump_compression_table[_COREDUMP_COMPRESSION_MAX] = {
+ [COREDUMP_COMPRESSION_NONE] = "none",
+ [COREDUMP_COMPRESSION_XZ] = "xz",
+};
+
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP(coredump_compression, CoredumpCompression);
+static DEFINE_CONFIG_PARSE_ENUM(config_parse_coredump_compression, coredump_compression,
+ CoredumpCompression,
+ "Failed to parse compression setting");
+
+static CoredumpStorage arg_storage = COREDUMP_STORAGE_EXTERNAL;
+static CoredumpCompression arg_compression = COREDUMP_COMPRESSION_XZ;
+static unsigned arg_compression_level = LZMA_PRESET_DEFAULT;
+static off_t arg_process_size_max = PROCESS_SIZE_MAX;
+static off_t arg_external_size_max = EXTERNAL_SIZE_MAX;
+static size_t arg_journal_size_max = JOURNAL_SIZE_MAX;
+static off_t arg_keep_free = (off_t) -1;
+static off_t arg_max_use = (off_t) -1;
+
+static int parse_config(void) {
+
+ static const ConfigTableItem items[] = {
+ { "Coredump", "Storage", config_parse_coredump_storage, 0, &arg_storage },
+ { "Coredump", "Compression", config_parse_coredump_compression, 0, &arg_compression },
+ { "Coredump", "CompressionLevel", config_parse_unsigned, 0, &arg_compression_level },
+
+ { "Coredump", "ProcessSizeMax", config_parse_iec_off, 0, &arg_process_size_max },
+ { "Coredump", "ExternalSizeMax", config_parse_iec_off, 0, &arg_external_size_max },
+ { "Coredump", "JournalSizeMax", config_parse_iec_size, 0, &arg_journal_size_max },
+ { "Coredump", "KeepFree", config_parse_iec_off, 0, &arg_keep_free },
+ { "Coredump", "MaxUse", config_parse_iec_off, 0, &arg_max_use },
+ {}
+ };
+
+ return config_parse(
+ NULL,
+ "/etc/systemd/coredump.conf",
+ NULL,
+ "Coredump\0",
+ config_item_table_lookup,
+ (void*) items,
+ false,
+ false,
+ NULL);
+
+#ifdef HAVE_XZ
+ if (arg_compression_level > 9) {
+ log_warning("Invalid CompressionLevel %u, ignoring.", arg_compression_level);
+ arg_compression_level = LZMA_PRESET_DEFAULT;
+ }
+#endif
+}
+
+static int fix_acl(int fd, uid_t uid) {
+
+#ifdef HAVE_ACL
+ _cleanup_(acl_freep) acl_t acl = NULL;
+ acl_entry_t entry;
+ acl_permset_t permset;
+
+ if (uid <= SYSTEM_UID_MAX)
+ return 0;
+
+ /* Make sure normal users can read (but not write or delete)
+ * their own coredumps */
+
+ acl = acl_get_fd(fd);
+ if (!acl) {
+ log_error("Failed to get ACL: %m");
+ return -errno;
+ }
+
+ if (acl_create_entry(&acl, &entry) < 0 ||
+ acl_set_tag_type(entry, ACL_USER) < 0 ||
+ acl_set_qualifier(entry, &uid) < 0) {
+ log_error("Failed to patch ACL: %m");
+ return -errno;
+ }
+
+ if (acl_get_permset(entry, &permset) < 0 ||
+ acl_add_perm(permset, ACL_READ) < 0 ||
+ calc_acl_mask_if_needed(&acl) < 0) {
+ log_warning("Failed to patch ACL: %m");
+ return -errno;
+ }
+
+ if (acl_set_fd(fd, acl) < 0) {
+ log_error("Failed to apply ACL: %m");
+ return -errno;
+ }
+#endif
+
+ return 0;
+}
+
+static int fix_xattr(int fd, const char *info[_INFO_LEN]) {
+
+ static const char * const xattrs[_INFO_LEN] = {
+ [INFO_PID] = "user.coredump.pid",
+ [INFO_UID] = "user.coredump.uid",
+ [INFO_GID] = "user.coredump.gid",
+ [INFO_SIGNAL] = "user.coredump.signal",
+ [INFO_TIMESTAMP] = "user.coredump.timestamp",
+ [INFO_COMM] = "user.coredump.comm",
+ [INFO_EXE] = "user.coredump.exe",
+ };
+
+ int r = 0;
+ unsigned i;
+
+ /* Attach some metadata to coredumps via extended
+ * attributes. Just because we can. */
+
+ for (i = 0; i < _INFO_LEN; i++) {
+ int k;
+
+ if (isempty(info[i]) || !xattrs[i])
+ continue;
+
+ k = fsetxattr(fd, xattrs[i], info[i], strlen(info[i]), XATTR_CREATE);
+ if (k < 0 && r == 0)
+ r = -errno;
+ }
+
+ return r;
+}
+
+#define filename_escape(s) xescape((s), "./ ")
+
+static int fix_permissions(int fd, const char *filename, const char *target,
+ const char *info[_INFO_LEN], uid_t uid) {
+
+ /* Ignore errors on these */
+ fchmod(fd, 0640);
+ fix_acl(fd, uid);
+ fix_xattr(fd, info);
+
+ if (fsync(fd) < 0) {
+ log_error("Failed to sync coredump %s: %m", filename);
+ return -errno;
+ }
+
+ if (rename(filename, target) < 0) {
+ log_error("Failed to rename coredump %s -> %s: %m", filename, target);
+ return -errno;
+ }
+
+ return 0;
+}
+
+static int maybe_remove_external_coredump(const char *filename, off_t size) {
+
+ /* Returns 1 if might remove, 0 if will not remove, <0 on error. */
+
+ if (IN_SET(arg_storage, COREDUMP_STORAGE_EXTERNAL, COREDUMP_STORAGE_BOTH) &&
+ size <= arg_external_size_max)
+ return 0;
+
+ if (!filename)
+ return 1;
+
+ if (unlink(filename) < 0) {
+ log_error("Failed to unlink %s: %m", filename);
+ return -errno;
+ }
+
+ return 1;
+}
+
+static int save_external_coredump(
+ const char *info[_INFO_LEN],
+ uid_t uid,
+ char **ret_filename,
+ int *ret_fd,
+ off_t *ret_size) {
+
+ _cleanup_free_ char *p = NULL, *t = NULL, *c = NULL, *fn = NULL, *tmp = NULL, *u = NULL;
+ _cleanup_close_ int fd = -1;
+ sd_id128_t boot;
+ struct stat st;