diff --git a/man/coredump.conf.xml b/man/coredump.conf.xml index d0f46cfe053..28e6017c7db 100644 --- a/man/coredump.conf.xml +++ b/man/coredump.conf.xml @@ -99,7 +99,7 @@ ExternalSizeMax= JournalSizeMax= - The maximum (uncompressed) size in bytes of a + The maximum (compressed or uncompressed) size in bytes of a core to be saved. Unit suffixes are allowed just as in . diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml index d55f3c8f9bd..1edaf157b9e 100644 --- a/man/org.freedesktop.systemd1.xml +++ b/man/org.freedesktop.systemd1.xml @@ -2401,6 +2401,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t MemoryCurrent = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t MemoryAvailable = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t CPUUsageNSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly ay EffectiveCPUs = [...]; @@ -3504,6 +3506,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + @@ -4063,6 +4067,11 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { MountImages ExtensionImages see systemd.exec(5) for their meaning. + + MemoryAvailable indicates how much unused memory is available to the unit before + the MemoryMax or MemoryHigh (whichever is lower) limit set by the cgroup + memory controller is reached. It will take into consideration limits on all parent slices, other than the + limits set on the unit itself. @@ -4196,6 +4205,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t MemoryCurrent = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t MemoryAvailable = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t CPUUsageNSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly ay EffectiveCPUs = [...]; @@ -5321,6 +5332,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { + + @@ -5915,6 +5928,8 @@ node /org/freedesktop/systemd1/unit/home_2emount { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t MemoryCurrent = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t MemoryAvailable = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t CPUUsageNSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly ay EffectiveCPUs = [...]; @@ -6886,6 +6901,8 @@ node /org/freedesktop/systemd1/unit/home_2emount { + + @@ -7601,6 +7618,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t MemoryCurrent = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t MemoryAvailable = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t CPUUsageNSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly ay EffectiveCPUs = [...]; @@ -8544,6 +8563,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { + + @@ -9112,6 +9133,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t MemoryCurrent = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t MemoryAvailable = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t CPUUsageNSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly ay EffectiveCPUs = [...]; @@ -9403,6 +9426,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice { + + @@ -9571,6 +9596,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t MemoryCurrent = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t MemoryAvailable = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t CPUUsageNSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly ay EffectiveCPUs = [...]; @@ -9904,6 +9931,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope { + + diff --git a/src/core/cgroup.c b/src/core/cgroup.c index 6a46e14556d..7fde1efce42 100644 --- a/src/core/cgroup.c +++ b/src/core/cgroup.c @@ -3402,6 +3402,77 @@ int manager_notify_cgroup_empty(Manager *m, const char *cgroup) { return 1; } +int unit_get_memory_available(Unit *u, uint64_t *ret) { + uint64_t unit_current, available = UINT64_MAX; + CGroupContext *unit_context; + const char *memory_file; + int r; + + assert(u); + assert(ret); + + /* If data from cgroups can be accessed, try to find out how much more memory a unit can + * claim before hitting the configured cgroup limits (if any). Consider both MemoryHigh + * and MemoryMax, and also any slice the unit might be nested below. */ + + if (!UNIT_CGROUP_BOOL(u, memory_accounting)) + return -ENODATA; + + if (!u->cgroup_path) + return -ENODATA; + + /* The root cgroup doesn't expose this information */ + if (unit_has_host_root_cgroup(u)) + return -ENODATA; + + if ((u->cgroup_realized_mask & CGROUP_MASK_MEMORY) == 0) + return -ENODATA; + + r = cg_all_unified(); + if (r < 0) + return r; + memory_file = r > 0 ? "memory.current" : "memory.usage_in_bytes"; + + r = cg_get_attribute_as_uint64("memory", u->cgroup_path, memory_file, &unit_current); + if (r < 0) + return r; + + assert_se(unit_context = unit_get_cgroup_context(u)); + + if (unit_context->memory_max != UINT64_MAX || unit_context->memory_high != UINT64_MAX) + available = LESS_BY(MIN(unit_context->memory_max, unit_context->memory_high), unit_current); + + for (Unit *slice = UNIT_GET_SLICE(u); slice; slice = UNIT_GET_SLICE(slice)) { + uint64_t slice_current, slice_available = UINT64_MAX; + CGroupContext *slice_context; + + /* No point in continuing if we can't go any lower */ + if (available == 0) + break; + + if (!slice->cgroup_path) + continue; + + slice_context = unit_get_cgroup_context(slice); + if (!slice_context) + continue; + + if (slice_context->memory_max == UINT64_MAX && slice_context->memory_high == UINT64_MAX) + continue; + + r = cg_get_attribute_as_uint64("memory", slice->cgroup_path, memory_file, &slice_current); + if (r < 0) + continue; + + slice_available = LESS_BY(MIN(slice_context->memory_max, slice_context->memory_high), slice_current); + available = MIN(slice_available, available); + } + + *ret = available; + + return 0; +} + int unit_get_memory_current(Unit *u, uint64_t *ret) { int r; diff --git a/src/core/cgroup.h b/src/core/cgroup.h index 1ad5dd38389..e6790eb0e83 100644 --- a/src/core/cgroup.h +++ b/src/core/cgroup.h @@ -282,6 +282,7 @@ int unit_watch_all_pids(Unit *u); int unit_synthesize_cgroup_empty_event(Unit *u); int unit_get_memory_current(Unit *u, uint64_t *ret); +int unit_get_memory_available(Unit *u, uint64_t *ret); int unit_get_tasks_current(Unit *u, uint64_t *ret); int unit_get_cpu_usage(Unit *u, nsec_t *ret); int unit_get_io_accounting(Unit *u, CGroupIOAccountingMetric metric, bool allow_cache, uint64_t *ret); diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c index 6c04c6e5db8..aa10939a041 100644 --- a/src/core/dbus-unit.c +++ b/src/core/dbus-unit.c @@ -1097,6 +1097,30 @@ static int property_get_current_memory( return sd_bus_message_append(reply, "t", sz); } +static int property_get_available_memory( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + uint64_t sz = UINT64_MAX; + Unit *u = userdata; + int r; + + assert(bus); + assert(reply); + assert(u); + + r = unit_get_memory_available(u, &sz); + if (r < 0 && r != -ENODATA) + log_unit_warning_errno(u, r, "Failed to get total available memory from cgroup: %m"); + + return sd_bus_message_append(reply, "t", sz); +} + static int property_get_current_tasks( sd_bus *bus, const char *path, @@ -1541,6 +1565,7 @@ const sd_bus_vtable bus_unit_cgroup_vtable[] = { SD_BUS_PROPERTY("Slice", "s", property_get_slice, 0, 0), SD_BUS_PROPERTY("ControlGroup", "s", property_get_cgroup, 0, 0), SD_BUS_PROPERTY("MemoryCurrent", "t", property_get_current_memory, 0, 0), + SD_BUS_PROPERTY("MemoryAvailable", "t", property_get_available_memory, 0, 0), SD_BUS_PROPERTY("CPUUsageNSec", "t", property_get_cpu_usage, 0, 0), SD_BUS_PROPERTY("EffectiveCPUs", "ay", property_get_cpuset_cpus, 0, 0), SD_BUS_PROPERTY("EffectiveMemoryNodes", "ay", property_get_cpuset_mems, 0, 0), diff --git a/src/coredump/coredump.c b/src/coredump/coredump.c index b75a7c39ce6..c6c232c7e73 100644 --- a/src/coredump/coredump.c +++ b/src/coredump/coredump.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -18,6 +19,7 @@ #include "acl-util.h" #include "alloc-util.h" +#include "bus-error.h" #include "capability-util.h" #include "cgroup-util.h" #include "compress.h" @@ -42,6 +44,7 @@ #include "socket-util.h" #include "special.h" #include "stacktrace.h" +#include "stat-util.h" #include "string-table.h" #include "string-util.h" #include "strv.h" @@ -63,6 +66,10 @@ #define JOURNAL_SIZE_MAX ((size_t) (10LU*1024LU*1024LU)) #endif +/* When checking for available memory and setting lower limits, don't + * go below 4MB for writing core files to storage. */ +#define PROCESS_SIZE_MIN (4U*1024U*1024U) + /* Make sure to not make this larger than the maximum journal entry * size. See DATA_SIZE_MAX in journal-importer.h. */ assert_cc(JOURNAL_SIZE_MAX <= DATA_SIZE_MAX); @@ -329,11 +336,14 @@ static int save_external_coredump( int *ret_node_fd, int *ret_data_fd, uint64_t *ret_size, + uint64_t *ret_compressed_size, bool *ret_truncated) { - _cleanup_free_ char *fn = NULL, *tmp = NULL; + _cleanup_(unlink_and_freep) char *tmp = NULL; + _cleanup_free_ char *fn = NULL; _cleanup_close_ int fd = -1; uint64_t rlimit, process_limit, max_size; + bool truncated, storage_on_tmpfs; struct stat st; uid_t uid; int r; @@ -343,6 +353,8 @@ static int save_external_coredump( assert(ret_node_fd); assert(ret_data_fd); assert(ret_size); + assert(ret_compressed_size); + assert(ret_truncated); r = parse_uid(context->meta[META_ARGV_UID], &uid); if (r < 0) @@ -379,92 +391,145 @@ static int save_external_coredump( 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->meta[META_ARGV_PID], context->meta[META_COMM]); - goto fail; + /* If storage is on tmpfs, the kernel oomd might kill us if there's MemoryMax set on + * the service or the slice it belongs to. This is common on low-resources systems, + * to avoid crashing processes to take away too many system resources. + * Check the cgroup settings, and set max_size to a bit less than half of the + * available memory left to the process. + * Then, attempt to write the core file uncompressed first - if the write gets + * interrupted, we know we won't be able to write it all, so instead compress what + * was written so far, delete the uncompressed truncated core, and then continue + * compressing from STDIN. Given the compressed core cannot be larger than the + * uncompressed one, and 1KB for metadata is accounted for in the calculation, we + * should be able to at least store the full compressed core file. */ + + storage_on_tmpfs = fd_is_temporary_fs(fd) > 0; + if (storage_on_tmpfs && arg_compress) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + uint64_t cgroup_limit = UINT64_MAX; + struct statvfs sv; + + /* If we can't get the cgroup limit, just ignore it, but don't fail, + * try anyway with the config settings. */ + r = sd_bus_default_system(&bus); + if (r < 0) + log_info_errno(r, "Failed to connect to system bus, skipping MemoryAvailable check: %m"); + else { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + + r = sd_bus_get_property_trivial( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1/unit/self", + "org.freedesktop.systemd1.Service", + "MemoryAvailable", + &error, + 't', &cgroup_limit); + if (r < 0) + log_warning_errno(r, + "Failed to query MemoryAvailable for current unit, " + "falling back to static config settings: %s", + bus_error_message(&error, r)); + } + + max_size = MIN(cgroup_limit, max_size); + max_size = LESS_BY(max_size, 1024U) / 2; /* Account for 1KB metadata overhead for compressing */ + max_size = MAX(PROCESS_SIZE_MIN, max_size); /* Impose a lower minimum */ + + /* tmpfs might get full quickly, so check the available space too. + * But don't worry about errors here, failing to access the storage + * location will be better logged when writing to it. */ + if (statvfs("/var/lib/systemd/coredump/", &sv) >= 0) + max_size = MIN((uint64_t)sv.f_frsize * (uint64_t)sv.f_bfree, max_size); + + log_debug("Limiting core file size to %" PRIu64 " bytes due to cgroup memory limits.", max_size); } - *ret_truncated = r == 1; - if (*ret_truncated) + + r = copy_bytes(input_fd, fd, max_size, 0); + if (r < 0) + return log_error_errno(r, "Cannot store coredump of %s (%s): %m", + context->meta[META_ARGV_PID], context->meta[META_COMM]); + truncated = r == 1; + +#if HAVE_COMPRESSION + if (arg_compress) { + _cleanup_(unlink_and_freep) char *tmp_compressed = NULL; + _cleanup_free_ char *fn_compressed = NULL; + _cleanup_close_ int fd_compressed = -1; + uint64_t uncompressed_size = 0; + + if (lseek(fd, 0, SEEK_SET) == (off_t) -1) + return log_error_errno(errno, "Failed to seek on coredump %s: %m", fn); + + fn_compressed = strjoin(fn, COMPRESSED_EXT); + if (!fn_compressed) + return log_oom(); + + fd_compressed = open_tmpfile_linkable(fn_compressed, O_RDWR|O_CLOEXEC, &tmp_compressed); + if (fd_compressed < 0) + return log_error_errno(fd_compressed, "Failed to create temporary file for coredump %s: %m", fn_compressed); + + r = compress_stream(fd, fd_compressed, max_size, &uncompressed_size); + if (r < 0) + return log_error_errno(r, "Failed to compress %s: %m", coredump_tmpfile_name(tmp_compressed)); + + if (truncated && storage_on_tmpfs) { + uint64_t partial_uncompressed_size = 0; + + /* Uncompressed write was truncated and we are writing to tmpfs: delete + * the uncompressed core, and compress the remaining part from STDIN. */ + + tmp = unlink_and_free(tmp); + fd = safe_close(fd); + + r = compress_stream(input_fd, fd_compressed, max_size, &partial_uncompressed_size); + if (r < 0) + return log_error_errno(r, "Failed to compress %s: %m", coredump_tmpfile_name(tmp_compressed)); + uncompressed_size += partial_uncompressed_size; + } + + r = fix_permissions(fd_compressed, tmp_compressed, fn_compressed, context, uid); + if (r < 0) + return r; + + if (fstat(fd_compressed, &st) < 0) + return log_error_errno(errno, + "Failed to fstat core file %s: %m", + coredump_tmpfile_name(tmp_compressed)); + + *ret_filename = TAKE_PTR(fn_compressed); /* compressed */ + *ret_node_fd = TAKE_FD(fd_compressed); /* compressed */ + *ret_compressed_size = (uint64_t) st.st_size; /* compressed */ + *ret_data_fd = TAKE_FD(fd); + *ret_size = uncompressed_size; + *ret_truncated = truncated; + tmp_compressed = mfree(tmp_compressed); + + return 0; + } +#endif + + if (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_COMPRESSION - /* 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 = strjoin(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; + return log_error_errno(r, "Failed to fix permissions and finalize coredump %s into %s: %m", coredump_tmpfile_name(tmp), fn); + + if (fstat(fd, &st) < 0) + return log_error_errno(errno, "Failed to fstat core file %s: %m", coredump_tmpfile_name(tmp)); + + if (lseek(fd, 0, SEEK_SET) == (off_t) -1) + return log_error_errno(errno, "Failed to seek on coredump %s: %m", fn); - *ret_filename = TAKE_PTR(fn); *ret_data_fd = TAKE_FD(fd); - *ret_node_fd = -1; *ret_size = (uint64_t) st.st_size; + *ret_truncated = truncated; return 0; - -fail: - if (tmp) - (void) unlink(tmp); - return r; } static int allocate_journal_field(int fd, size_t size, char **ret, size_t *ret_size) { @@ -709,7 +774,7 @@ static int submit_coredump( _cleanup_free_ char *stacktrace = NULL; char *core_message; const char *module_name; - uint64_t coredump_size = UINT64_MAX; + uint64_t coredump_size = UINT64_MAX, coredump_compressed_size = UINT64_MAX; bool truncated = false; JsonVariant *module_json; int r; @@ -722,7 +787,8 @@ static int submit_coredump( /* Always stream the coredump to disk, if that's possible */ r = save_external_coredump(context, input_fd, - &filename, &coredump_node_fd, &coredump_fd, &coredump_size, &truncated); + &filename, &coredump_node_fd, &coredump_fd, + &coredump_size, &coredump_compressed_size, &truncated); if (r < 0) /* Skip whole core dumping part */ goto log; @@ -730,7 +796,7 @@ static int submit_coredump( /* If we don't want to keep the coredump on disk, remove it now, as later on we * will lack the privileges for it. However, we keep the fd to it, so that we can * still process it and log it. */ - r = maybe_remove_external_coredump(filename, coredump_size); + r = maybe_remove_external_coredump(filename, coredump_node_fd >= 0 ? coredump_compressed_size : coredump_size); if (r < 0) return r; if (r == 0) { @@ -738,7 +804,7 @@ static int submit_coredump( } else if (arg_storage == COREDUMP_STORAGE_EXTERNAL) log_info("The core will not be stored: size %"PRIu64" is greater than %"PRIu64" (the configured maximum)", - coredump_size, arg_external_size_max); + coredump_node_fd >= 0 ? coredump_compressed_size : coredump_size, arg_external_size_max); /* Vacuum again, but exclude the coredump we just created */ (void) coredump_vacuum(coredump_node_fd >= 0 ? coredump_node_fd : coredump_fd, arg_keep_free, arg_max_use); @@ -758,7 +824,7 @@ static int submit_coredump( log_debug("Not generating stack trace: core size %"PRIu64" is greater " "than %"PRIu64" (the configured maximum)", coredump_size, arg_process_size_max); - } else + } else if (coredump_fd >= 0) coredump_parse_core(coredump_fd, context->meta[META_EXE], &stacktrace, &json_metadata); #endif @@ -812,7 +878,7 @@ log: } /* Optionally store the entire coredump in the journal */ - if (arg_storage == COREDUMP_STORAGE_JOURNAL) { + if (arg_storage == COREDUMP_STORAGE_JOURNAL && coredump_fd >= 0) { if (coredump_size <= arg_journal_size_max) { size_t sz = 0; diff --git a/src/libsystemd/sd-journal/compress.c b/src/libsystemd/sd-journal/compress.c index c788dd8caf9..837abab76c8 100644 --- a/src/libsystemd/sd-journal/compress.c +++ b/src/libsystemd/sd-journal/compress.c @@ -550,7 +550,7 @@ int decompress_startswith( return -EBADMSG; } -int compress_stream_xz(int fdf, int fdt, uint64_t max_bytes) { +int compress_stream_xz(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size) { #if HAVE_XZ _cleanup_(lzma_end) lzma_stream s = LZMA_STREAM_INIT; lzma_ret ret; @@ -611,6 +611,9 @@ int compress_stream_xz(int fdf, int fdt, uint64_t max_bytes) { return k; if (ret == LZMA_STREAM_END) { + if (ret_uncompressed_size) + *ret_uncompressed_size = s.total_in; + log_debug("XZ compression finished (%"PRIu64" -> %"PRIu64" bytes, %.1f%%)", s.total_in, s.total_out, (double) s.total_out / s.total_in * 100); @@ -626,14 +629,15 @@ int compress_stream_xz(int fdf, int fdt, uint64_t max_bytes) { #define LZ4_BUFSIZE (512*1024u) -int compress_stream_lz4(int fdf, int fdt, uint64_t max_bytes) { +int compress_stream_lz4(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size) { #if HAVE_LZ4 LZ4F_errorCode_t c; _cleanup_(LZ4F_freeCompressionContextp) LZ4F_compressionContext_t ctx = NULL; _cleanup_free_ void *in_buff = NULL; _cleanup_free_ char *out_buff = NULL; - size_t out_allocsize, n, total_in = 0, total_out, offset = 0, frame_size; + size_t out_allocsize, n, offset = 0, frame_size; + uint64_t total_in = 0, total_out; int r; static const LZ4F_preferences_t preferences = { .frameInfo.blockSizeID = 5, @@ -698,7 +702,10 @@ int compress_stream_lz4(int fdf, int fdt, uint64_t max_bytes) { if (r < 0) return r; - log_debug("LZ4 compression finished (%zu -> %zu bytes, %.1f%%)", + if (ret_uncompressed_size) + *ret_uncompressed_size = total_in; + + log_debug("LZ4 compression finished (%" PRIu64 " -> %" PRIu64 " bytes, %.1f%%)", total_in, total_out, (double) total_out / total_in * 100); @@ -844,7 +851,7 @@ int decompress_stream_lz4(int in, int out, uint64_t max_bytes) { #endif } -int compress_stream_zstd(int fdf, int fdt, uint64_t max_bytes) { +int compress_stream_zstd(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size) { #if HAVE_ZSTD _cleanup_(ZSTD_freeCCtxp) ZSTD_CCtx *cctx = NULL; _cleanup_free_ void *in_buff = NULL, *out_buff = NULL; @@ -933,6 +940,9 @@ int compress_stream_zstd(int fdf, int fdt, uint64_t max_bytes) { break; } + if (ret_uncompressed_size) + *ret_uncompressed_size = in_bytes; + if (in_bytes > 0) log_debug("ZSTD compression finished (%" PRIu64 " -> %" PRIu64 " bytes, %.1f%%)", in_bytes, max_bytes - left, (double) (max_bytes - left) / in_bytes * 100); diff --git a/src/libsystemd/sd-journal/compress.h b/src/libsystemd/sd-journal/compress.h index a82049edd8a..005e60e2e3e 100644 --- a/src/libsystemd/sd-journal/compress.h +++ b/src/libsystemd/sd-journal/compress.h @@ -64,9 +64,9 @@ int decompress_startswith(int compression, const void *prefix, size_t prefix_len, uint8_t extra); -int compress_stream_xz(int fdf, int fdt, uint64_t max_bytes); -int compress_stream_lz4(int fdf, int fdt, uint64_t max_bytes); -int compress_stream_zstd(int fdf, int fdt, uint64_t max_bytes); +int compress_stream_xz(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size); +int compress_stream_lz4(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size); +int compress_stream_zstd(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size); int decompress_stream_xz(int fdf, int fdt, uint64_t max_size); int decompress_stream_lz4(int fdf, int fdt, uint64_t max_size); @@ -82,7 +82,7 @@ int decompress_stream_zstd(int fdf, int fdt, uint64_t max_size); # define compress_stream compress_stream_xz # define COMPRESSED_EXT ".xz" #else -static inline int compress_stream(int fdf, int fdt, uint64_t max_size) { +static inline int compress_stream(int fdf, int fdt, uint64_t max_size, uint64_t *ret_uncompressed_size) { return -EOPNOTSUPP; } # define COMPRESSED_EXT "" diff --git a/src/libsystemd/sd-journal/test-compress.c b/src/libsystemd/sd-journal/test-compress.c index 0d5069d4772..54e0e738b3b 100644 --- a/src/libsystemd/sd-journal/test-compress.c +++ b/src/libsystemd/sd-journal/test-compress.c @@ -41,7 +41,7 @@ typedef int (decompress_sw_t)(const void *src, uint64_t src_size, const void *prefix, size_t prefix_len, uint8_t extra); -typedef int (compress_stream_t)(int fdf, int fdt, uint64_t max_bytes); +typedef int (compress_stream_t)(int fdf, int fdt, uint64_t max_bytes, uint64_t *uncompressed_size); typedef int (decompress_stream_t)(int fdf, int fdt, uint64_t max_size); #if HAVE_COMPRESSION @@ -176,6 +176,7 @@ _unused_ static void test_compress_stream(const char *compression, int r; _cleanup_free_ char *cmd = NULL, *cmd2 = NULL; struct stat st = {}; + uint64_t uncompressed_size; r = find_executable(cat, NULL); if (r < 0) { @@ -193,7 +194,7 @@ _unused_ static void test_compress_stream(const char *compression, assert_se((dst = mkostemp_safe(pattern)) >= 0); - assert_se(compress(src, dst, -1) == 0); + assert_se(compress(src, dst, -1, &uncompressed_size) == 0); if (cat) { assert_se(asprintf(&cmd, "%s %s | diff %s -", cat, pattern, srcfile) > 0); @@ -205,6 +206,7 @@ _unused_ static void test_compress_stream(const char *compression, assert_se((dst2 = mkostemp_safe(pattern2)) >= 0); assert_se(stat(srcfile, &st) == 0); + assert_se((uint64_t)st.st_size == uncompressed_size); assert_se(lseek(dst, 0, SEEK_SET) == 0); r = decompress(dst, dst2, st.st_size); diff --git a/src/shared/bus-print-properties.c b/src/shared/bus-print-properties.c index cbd5fb087e0..b45921943a8 100644 --- a/src/shared/bus-print-properties.c +++ b/src/shared/bus-print-properties.c @@ -165,7 +165,7 @@ static int bus_print_property(const char *name, const char *expected_value, sd_b bus_print_property_value(name, expected_value, flags, "[not set]"); - else if ((STR_IN_SET(name, "DefaultMemoryLow", "DefaultMemoryMin", "MemoryLow", "MemoryHigh", "MemoryMax", "MemorySwapMax", "MemoryLimit") && u == CGROUP_LIMIT_MAX) || + else if ((STR_IN_SET(name, "DefaultMemoryLow", "DefaultMemoryMin", "MemoryLow", "MemoryHigh", "MemoryMax", "MemorySwapMax", "MemoryLimit", "MemoryAvailable") && u == CGROUP_LIMIT_MAX) || (STR_IN_SET(name, "TasksMax", "DefaultTasksMax") && u == UINT64_MAX) || (startswith(name, "Limit") && u == UINT64_MAX) || (startswith(name, "DefaultLimit") && u == UINT64_MAX)) diff --git a/src/systemctl/systemctl-show.c b/src/systemctl/systemctl-show.c index 3686ac3c768..d4d5a2b427f 100644 --- a/src/systemctl/systemctl-show.c +++ b/src/systemctl/systemctl-show.c @@ -247,6 +247,7 @@ typedef struct UnitStatusInfo { uint64_t memory_max; uint64_t memory_swap_max; uint64_t memory_limit; + uint64_t memory_available; uint64_t cpu_usage_nsec; uint64_t tasks_current; uint64_t tasks_max; @@ -682,6 +683,7 @@ static void print_status_info( if (i->memory_min > 0 || i->memory_low > 0 || i->memory_high != CGROUP_LIMIT_MAX || i->memory_max != CGROUP_LIMIT_MAX || i->memory_swap_max != CGROUP_LIMIT_MAX || + i->memory_available != CGROUP_LIMIT_MAX || i->memory_limit != CGROUP_LIMIT_MAX) { const char *prefix = ""; @@ -710,6 +712,10 @@ static void print_status_info( printf("%slimit: %s", prefix, format_bytes(buf, sizeof(buf), i->memory_limit)); prefix = " "; } + if (i->memory_available != CGROUP_LIMIT_MAX) { + printf("%savailable: %s", prefix, format_bytes(buf, sizeof(buf), i->memory_available)); + prefix = " "; + } printf(")"); } printf("\n"); @@ -1827,6 +1833,7 @@ static int show_one( { "Where", "s", NULL, offsetof(UnitStatusInfo, where) }, { "What", "s", NULL, offsetof(UnitStatusInfo, what) }, { "MemoryCurrent", "t", NULL, offsetof(UnitStatusInfo, memory_current) }, + { "MemoryAvailable", "t", NULL, offsetof(UnitStatusInfo, memory_available) }, { "DefaultMemoryMin", "t", NULL, offsetof(UnitStatusInfo, default_memory_min) }, { "DefaultMemoryLow", "t", NULL, offsetof(UnitStatusInfo, default_memory_low) }, { "MemoryMin", "t", NULL, offsetof(UnitStatusInfo, memory_min) }, @@ -1869,6 +1876,7 @@ static int show_one( .memory_max = CGROUP_LIMIT_MAX, .memory_swap_max = CGROUP_LIMIT_MAX, .memory_limit = UINT64_MAX, + .memory_available = CGROUP_LIMIT_MAX, .cpu_usage_nsec = UINT64_MAX, .tasks_current = UINT64_MAX, .tasks_max = UINT64_MAX,