/* htop - CGroupUtils.c (C) 2021 htop dev team Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ #include "config.h" // IWYU pragma: keep #include "linux/CGroupUtils.h" #include #include #include #include #include "Macros.h" #include "XUtils.h" static const char* str_slice_suffix = ".slice"; static const char* str_system_slice = "system.slice"; static const char* str_user_slice = "user.slice"; static const char* str_machine_slice = "machine.slice"; static const char* str_user_slice_prefix = "/user-"; static const char* str_system_slice_prefix = "/system-"; static const char* str_lxc_monitor_legacy = "lxc.monitor"; static const char* str_lxc_payload_legacy = "lxc.payload"; static const char* str_lxc_monitor_prefix = "lxc.monitor."; static const char* str_lxc_payload_prefix = "lxc.payload."; static const char* str_nspawn_scope_prefix = "machine-"; static const char* str_nspawn_monitor_label = "/supervisor"; static const char* str_nspawn_payload_label = "/payload"; static const char* str_snap_scope_prefix = "snap."; static const char* str_pod_scope_prefix = "libpod-"; static const char* str_docker_scope_prefix = "docker-"; static const char* str_service_suffix = ".service"; static const char* str_scope_suffix = ".scope"; typedef struct StrBuf_state { char* buf; size_t size; size_t pos; } StrBuf_state; typedef bool (*StrBuf_putc_t)(StrBuf_state* p, char c); static bool StrBuf_putc_count(StrBuf_state* p, ATTR_UNUSED char c) { p->pos++; return true; } static bool StrBuf_putc_write(StrBuf_state* p, char c) { if (p->pos >= p->size) return false; p->buf[p->pos] = c; p->pos++; return true; } static bool StrBuf_putsn(StrBuf_state* p, StrBuf_putc_t w, const char* s, size_t count) { for (; count; count--) if (!w(p, *s++)) return false; return true; } static bool StrBuf_putsz(StrBuf_state* p, StrBuf_putc_t w, const char* s) { while (*s) if (!w(p, *s++)) return false; return true; } static bool Label_checkEqual(const char* labelStart, size_t labelLen, const char* expected) { return labelLen == strlen(expected) && String_startsWith(labelStart, expected); } static bool Label_checkPrefix(const char* labelStart, size_t labelLen, const char* expected) { return labelLen > strlen(expected) && String_startsWith(labelStart, expected); } static bool Label_checkSuffix(const char* labelStart, size_t labelLen, const char* expected) { return labelLen > strlen(expected) && String_startsWith(labelStart + labelLen - strlen(expected), expected); } static bool CGroup_filterName_internal(const char* cgroup, StrBuf_state* s, StrBuf_putc_t w) { while (*cgroup) { if ('/' == *cgroup) { while ('/' == *cgroup) cgroup++; if (!w(s, '/')) return false; continue; } const char* labelStart = cgroup; const char* nextSlash = String_strchrnul(labelStart, '/'); const size_t labelLen = nextSlash - labelStart; if (Label_checkEqual(labelStart, labelLen, str_system_slice)) { cgroup = nextSlash; if (!StrBuf_putsz(s, w, "[S]")) return false; if (String_startsWith(cgroup, str_system_slice_prefix)) { cgroup = String_strchrnul(cgroup + 1, '/'); continue; } continue; } if (Label_checkEqual(labelStart, labelLen, str_machine_slice)) { cgroup = nextSlash; if (!StrBuf_putsz(s, w, "[M]")) return false; continue; } if (Label_checkEqual(labelStart, labelLen, str_user_slice)) { cgroup = nextSlash; if (!StrBuf_putsz(s, w, "[U]")) return false; if (!String_startsWith(cgroup, str_user_slice_prefix)) continue; const char* userSliceSlash = String_strchrnul(cgroup + strlen(str_user_slice_prefix), '/'); const char* sliceSpec = userSliceSlash - strlen(str_slice_suffix); if (!String_startsWith(sliceSpec, str_slice_suffix)) continue; const size_t sliceNameLen = sliceSpec - (cgroup + strlen(str_user_slice_prefix)); s->pos--; if (!w(s, ':')) return false; if (!StrBuf_putsn(s, w, cgroup + strlen(str_user_slice_prefix), sliceNameLen)) return false; if (!w(s, ']')) return false; cgroup = userSliceSlash; continue; } if (Label_checkSuffix(labelStart, labelLen, str_slice_suffix)) { const size_t sliceNameLen = labelLen - strlen(str_slice_suffix); if (!w(s, '[')) return false; if (!StrBuf_putsn(s, w, cgroup, sliceNameLen)) return false; if (!w(s, ']')) return false; cgroup = nextSlash; continue; } if (Label_checkPrefix(labelStart, labelLen, str_lxc_payload_prefix)) { const size_t cgroupNameLen = labelLen - strlen(str_lxc_payload_prefix); if (!StrBuf_putsz(s, w, "[lxc:")) return false; if (!StrBuf_putsn(s, w, cgroup + strlen(str_lxc_payload_prefix), cgroupNameLen)) return false; if (!w(s, ']')) return false; cgroup = nextSlash; continue; } if (Label_checkPrefix(labelStart, labelLen, str_lxc_monitor_prefix)) { const size_t cgroupNameLen = labelLen - strlen(str_lxc_monitor_prefix); if (!StrBuf_putsz(s, w, "[LXC:")) return false; if (!StrBuf_putsn(s, w, cgroup + strlen(str_lxc_monitor_prefix), cgroupNameLen)) return false; if (!w(s, ']')) return false; cgroup = nextSlash; continue; } // LXC legacy cgroup naming if (Label_checkEqual(labelStart, labelLen, str_lxc_monitor_legacy) || Label_checkEqual(labelStart, labelLen, str_lxc_payload_legacy)) { bool isMonitor = Label_checkEqual(labelStart, labelLen, str_lxc_monitor_legacy); labelStart = nextSlash; while (*labelStart == '/') labelStart++; nextSlash = String_strchrnul(labelStart, '/'); if (nextSlash - labelStart > 0) { if (!StrBuf_putsz(s, w, isMonitor ? "[LXC:" : "[lxc:")) return false; if (!StrBuf_putsn(s, w, labelStart, nextSlash - labelStart)) return false; if (!w(s, ']')) return false; cgroup = nextSlash; continue; } labelStart = cgroup; nextSlash = labelStart + labelLen; } if (Label_checkSuffix(labelStart, labelLen, str_service_suffix)) { const size_t serviceNameLen = labelLen - strlen(str_service_suffix); if (String_startsWith(cgroup, "user@")) { cgroup = nextSlash; while (*cgroup == '/') cgroup++; continue; } if (!StrBuf_putsn(s, w, cgroup, serviceNameLen)) return false; cgroup = nextSlash; continue; } if (Label_checkSuffix(labelStart, labelLen, str_scope_suffix)) { const size_t scopeNameLen = labelLen - strlen(str_scope_suffix); if (Label_checkPrefix(labelStart, scopeNameLen, str_nspawn_scope_prefix)) { const size_t machineScopeNameLen = scopeNameLen - strlen(str_nspawn_scope_prefix); const bool is_monitor = String_startsWith(nextSlash, str_nspawn_monitor_label); if (!StrBuf_putsz(s, w, is_monitor ? "[SNC:" : "[snc:")) return false; if (!StrBuf_putsn(s, w, cgroup + strlen(str_nspawn_scope_prefix), machineScopeNameLen)) return false; if (!w(s, ']')) return false; cgroup = nextSlash; if (String_startsWith(nextSlash, str_nspawn_monitor_label)) cgroup += strlen(str_nspawn_monitor_label); else if (String_startsWith(nextSlash, str_nspawn_payload_label)) cgroup += strlen(str_nspawn_payload_label); continue; } else if (Label_checkPrefix(labelStart, scopeNameLen, str_snap_scope_prefix)) { const char* nextDot = String_strchrnul(labelStart + strlen(str_snap_scope_prefix), '.'); if (!StrBuf_putsz(s, w, "!snap:")) return false; if (nextDot >= labelStart + scopeNameLen) { nextDot = labelStart + scopeNameLen; } if (!StrBuf_putsn(s, w, labelStart + strlen(str_snap_scope_prefix), nextDot - (labelStart + strlen(str_snap_scope_prefix)))) return false; cgroup = nextSlash; continue; } else if (Label_checkPrefix(labelStart, scopeNameLen, str_pod_scope_prefix)) { const char* nextDot = String_strchrnul(labelStart + strlen(str_pod_scope_prefix), '.'); if (!StrBuf_putsz(s, w, "!pod:")) return false; if (nextDot >= labelStart + scopeNameLen) { nextDot = labelStart + scopeNameLen; } if (!StrBuf_putsn(s, w, labelStart + strlen(str_pod_scope_prefix), MINIMUM( nextDot - (labelStart + strlen(str_pod_scope_prefix)), 12))) return false; cgroup = nextSlash; continue; } else if (Label_checkPrefix(labelStart, scopeNameLen, str_docker_scope_prefix)) { const char* nextDot = String_strchrnul(labelStart + strlen(str_docker_scope_prefix), '.'); if (!StrBuf_putsz(s, w, "!docker:")) return false; if (nextDot >= labelStart + scopeNameLen) { nextDot = labelStart + scopeNameLen; } if (!StrBuf_putsn(s, w, labelStart + strlen(str_docker_scope_prefix), MINIMUM( nextDot - (labelStart + strlen(str_docker_scope_prefix)), 12))) return false; cgroup = nextSlash; continue; } if (!w(s, '!')) return false; if (!StrBuf_putsn(s, w, cgroup, scopeNameLen)) return false; cgroup = nextSlash; continue; } // Default behavior: Copy the full label cgroup = labelStart; if (!StrBuf_putsn(s, w, cgroup, labelLen)) return false; cgroup = nextSlash; } return true; } char* CGroup_filterName(const char* cgroup) { StrBuf_state s = { .buf = NULL, .size = 0, .pos = 0, }; if (!CGroup_filterName_internal(cgroup, &s, StrBuf_putc_count)) { return NULL; } s.buf = xCalloc(s.pos + 1, sizeof(char)); s.size = s.pos; s.pos = 0; if (!CGroup_filterName_internal(cgroup, &s, StrBuf_putc_write)) { free(s.buf); return NULL; } s.buf[s.size] = '\0'; return s.buf; } static bool CGroup_filterContainer_internal(const char* cgroup, StrBuf_state* s, StrBuf_putc_t w) { while (*cgroup) { if ('/' == *cgroup) { while ('/' == *cgroup) cgroup++; continue; } const char* labelStart = cgroup; const char* nextSlash = String_strchrnul(labelStart, '/'); const size_t labelLen = nextSlash - labelStart; if (Label_checkPrefix(labelStart, labelLen, str_lxc_payload_prefix)) { const size_t cgroupNameLen = labelLen - strlen(str_lxc_payload_prefix); if (!StrBuf_putsz(s, w, "/lxc:")) return false; if (!StrBuf_putsn(s, w, cgroup + strlen(str_lxc_payload_prefix), cgroupNameLen)) return false; cgroup = nextSlash; continue; } // LXC legacy cgroup naming if (Label_checkEqual(labelStart, labelLen, str_lxc_payload_legacy)) { labelStart = nextSlash; while (*labelStart == '/') labelStart++; nextSlash = String_strchrnul(labelStart, '/'); if (nextSlash - labelStart > 0) { if (!StrBuf_putsz(s, w, "/lxc:")) return false; if (!StrBuf_putsn(s, w, labelStart, nextSlash - labelStart)) return false; cgroup = nextSlash; continue; } labelStart = cgroup; nextSlash = labelStart + labelLen; } if (Label_checkSuffix(labelStart, labelLen, str_scope_suffix)) { const size_t scopeNameLen = labelLen - strlen(str_scope_suffix); if (Label_checkPrefix(labelStart, scopeNameLen, str_nspawn_scope_prefix)) { const size_t machineScopeNameLen = scopeNameLen - strlen(str_nspawn_scope_prefix); const bool is_monitor = String_startsWith(nextSlash, str_nspawn_monitor_label); if (!is_monitor) { if (!StrBuf_putsz(s, w, "/snc:")) return false; if (!StrBuf_putsn(s, w, cgroup + strlen(str_nspawn_scope_prefix), machineScopeNameLen)) return false; } cgroup = nextSlash; if (String_startsWith(nextSlash, str_nspawn_monitor_label)) cgroup += strlen(str_nspawn_monitor_label); else if (String_startsWith(nextSlash, str_nspawn_payload_label)) cgroup += strlen(str_nspawn_payload_label); continue; } else if (Label_checkPrefix(labelStart, scopeNameLen, str_pod_scope_prefix)) { const char* nextDot = String_strchrnul(labelStart + strlen(str_pod_scope_prefix), '.'); if (!StrBuf_putsz(s, w, "/pod:")) return false; if (nextDot >= labelStart + scopeNameLen) { nextDot = labelStart + scopeNameLen; } if (!StrBuf_putsn(s, w, labelStart + strlen(str_pod_scope_prefix), MINIMUM( nextDot - (labelStart + strlen(str_pod_scope_prefix)), 12))) return false; cgroup = nextSlash; continue; } else if (Label_checkPrefix(labelStart, scopeNameLen, str_docker_scope_prefix)) { const char* nextDot = String_strchrnul(labelStart + strlen(str_docker_scope_prefix), '.'); if (!StrBuf_putsz(s, w, "!docker:")) return false; if (nextDot >= labelStart + scopeNameLen) { nextDot = labelStart + scopeNameLen; } if (!StrBuf_putsn(s, w, labelStart + strlen(str_docker_scope_prefix), MINIMUM( nextDot - (labelStart + strlen(str_docker_scope_prefix)), 12))) return false; cgroup = nextSlash; continue; } cgroup = nextSlash; continue; } cgroup = nextSlash; } return true; } char* CGroup_filterContainer(const char* cgroup) { StrBuf_state s = { .buf = NULL, .size = 0, .pos = 0, }; if (!CGroup_filterContainer_internal(cgroup, &s, StrBuf_putc_count)) { return NULL; } if (!s.pos) { return xStrdup("/"); } s.buf = xCalloc(s.pos + 1, sizeof(char)); s.size = s.pos; s.pos = 0; if (!CGroup_filterContainer_internal(cgroup, &s, StrBuf_putc_write)) { free(s.buf); return NULL; } s.buf[s.size] = '\0'; return s.buf; }