| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "debugfs_plugin.h" |
| 4 | |
| 5 | static char *user_config_dir = CONFIG_DIR; |
| 6 | static char *stock_config_dir = LIBCONFIG_DIR; |
| 7 | |
| 8 | static int update_every = 1; |
| 9 | |
| 10 | netdata_mutex_t stdout_mutex; |
| 11 | |
| 12 | static void __attribute__((constructor)) init_mutex(void) { |
| 13 | netdata_mutex_init(&stdout_mutex); |
| 14 | } |
| 15 | |
| 16 | static void __attribute__((destructor)) destroy_mutex(void) { |
| 17 | netdata_mutex_destroy(&stdout_mutex); |
| 18 | } |
| 19 | |
| 20 | static struct debugfs_module { |
| 21 | const char *name; |
| 22 | int enabled; |
| 23 | int (*func)(int update_every, const char *name); |
| 24 | } debugfs_modules[] = { |
| 25 | { |
| 26 | // Memory Fragmentation |
| 27 | .name = "/sys/kernel/debug/extfrag", |
| 28 | .enabled = CONFIG_BOOLEAN_YES, |
| 29 | .func = do_module_numa_extfrag |
| 30 | }, |
| 31 | { |
| 32 | .name = "/sys/kernel/debug/zswap", |
| 33 | .enabled = CONFIG_BOOLEAN_YES, |
| 34 | .func = do_module_zswap |
| 35 | }, |
| 36 | { |
| 37 | // Linux powercap metrics is here because it needs privilege to read each RAPL zone |
| 38 | .name = "/sys/devices/virtual/powercap", |
| 39 | .enabled = CONFIG_BOOLEAN_YES, |
| 40 | .func = do_module_devices_powercap |
| 41 | }, |
| 42 | { |
| 43 | .name = "libsensors", |
| 44 | .enabled = CONFIG_BOOLEAN_YES, |
| 45 | .func = do_module_libsensors |
| 46 | }, |
| 47 | { |
| 48 | // Linux audit subsystem status via netlink |
| 49 | .name = "audit", |
| 50 | .enabled = CONFIG_BOOLEAN_YES, |
| 51 | .func = do_module_audit |
| 52 | }, |
| 53 | |
| 54 | // The terminator |
| 55 | {.name = NULL, .enabled = CONFIG_BOOLEAN_NO, .func = NULL} |
| 56 | }; |
| 57 | |
| 58 | #ifdef HAVE_CAPABILITY |
| 59 | static int debugfs_check_capabilities() |
| 60 | { |
| 61 | cap_t caps = cap_get_proc(); |
| 62 | if (!caps) { |
| 63 | netdata_log_error("Cannot get current capabilities."); |
| 64 | return 0; |
| 65 | } |
| 66 | |
| 67 | int ret = 1; |
| 68 | cap_flag_value_t cfv = CAP_CLEAR; |
| 69 | if (cap_get_flag(caps, CAP_DAC_READ_SEARCH, CAP_EFFECTIVE, &cfv) == -1) { |
| 70 | netdata_log_error("Cannot find if CAP_DAC_READ_SEARCH is effective."); |
| 71 | ret = 0; |
| 72 | } else { |
| 73 | if (cfv != CAP_SET) { |
| 74 | netdata_log_error("debugfs.plugin should run with CAP_DAC_READ_SEARCH."); |
| 75 | ret = 0; |
| 76 | } |
| 77 | } |
| 78 | cap_free(caps); |
| 79 | |
| 80 | return ret; |
| 81 | } |
| 82 | #else |
| 83 | static int debugfs_check_capabilities() |
| 84 | { |
| 85 | return 0; |
| 86 | } |
| 87 | #endif |
| 88 | |
| 89 | // TODO: This is a function used by 3 different collector, we should do it global (next PR) |
| 90 | static int debugfs_am_i_running_as_root() |
| 91 | { |
| 92 | uid_t uid = getuid(), euid = geteuid(); |
| 93 | |
| 94 | if (uid == 0 || euid == 0) { |
| 95 | return 1; |
| 96 | } |
| 97 | |
| 98 | return 0; |
| 99 | } |
| 100 | |
| 101 | void debugfs2lower(char *name) |
| 102 | { |
| 103 | while (*name) { |
| 104 | *name = tolower(*name); |
| 105 | name++; |
| 106 | } |
| 107 | } |
| 108 | |
| 109 | // Consiidering our goal to redce binaries, I preferred to copy function, instead to force link with unecessary libs |
| 110 | const char *debugfs_rrdset_type_name(RRDSET_TYPE chart_type) { |
| 111 | switch(chart_type) { |
| 112 | case RRDSET_TYPE_LINE: |
| 113 | default: |
| 114 | return RRDSET_TYPE_LINE_NAME; |
| 115 | |
| 116 | case RRDSET_TYPE_AREA: |
| 117 | return RRDSET_TYPE_AREA_NAME; |
| 118 | |
| 119 | case RRDSET_TYPE_STACKED: |
| 120 | return RRDSET_TYPE_STACKED_NAME; |
| 121 | } |
| 122 | } |
| 123 | |
| 124 | const char *debugfs_rrd_algorithm_name(RRD_ALGORITHM algorithm) { |
| 125 | switch(algorithm) { |
| 126 | case RRD_ALGORITHM_ABSOLUTE: |
| 127 | default: |
| 128 | return RRD_ALGORITHM_ABSOLUTE_NAME; |
| 129 | |
| 130 | case RRD_ALGORITHM_INCREMENTAL: |
| 131 | return RRD_ALGORITHM_INCREMENTAL_NAME; |
| 132 | |
| 133 | case RRD_ALGORITHM_PCENT_OVER_ROW_TOTAL: |
| 134 | return RRD_ALGORITHM_PCENT_OVER_ROW_TOTAL_NAME; |
| 135 | |
| 136 | case RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL: |
| 137 | return RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL_NAME; |
| 138 | } |
| 139 | } |
| 140 | |
| 141 | int debugfs_check_sys_permission() { |
| 142 | int ret = 0; |
| 143 | |
| 144 | char filename[FILENAME_MAX + 1]; |
| 145 | |
| 146 | snprintfz(filename, FILENAME_MAX, "%s/sys/kernel/debug/extfrag/extfrag_index", netdata_configured_host_prefix); |
| 147 | |
| 148 | procfile *ff = procfile_open(filename, NULL, PROCFILE_FLAG_NO_ERROR_ON_FILE_IO); |
| 149 | if(!ff) goto dcsp_cleanup; |
| 150 | |
| 151 | ff = procfile_readall(ff); |
| 152 | if(!ff) goto dcsp_cleanup; |
| 153 | |
| 154 | ret = 1; |
| 155 | |
| 156 | dcsp_cleanup: |
| 157 | if (!ret) |
| 158 | perror("Cannot open /sys/kernel/debug/extfrag/extfrag_index file"); |
| 159 | procfile_close(ff); |
| 160 | return ret; |
| 161 | } |
| 162 | |
| 163 | static void debugfs_parse_args(int argc, char **argv) |
| 164 | { |
| 165 | int i, freq = 0; |
| 166 | for(i = 1; i < argc; i++) { |
| 167 | if(!freq) { |
| 168 | int n = (int)str2l(argv[i]); |
| 169 | if(n > 0) { |
| 170 | freq = n; |
| 171 | continue; |
| 172 | } |
| 173 | } |
| 174 | |
| 175 | if(strcmp("test-permissions", argv[i]) == 0 || strcmp("-t", argv[i]) == 0) { |
| 176 | if(!debugfs_check_sys_permission()) { |
| 177 | exit(2); |
| 178 | } |
| 179 | printf("OK\n"); |
| 180 | exit(0); |
| 181 | } |
| 182 | } |
| 183 | |
| 184 | if(freq > 0) update_every = freq; |
| 185 | } |
| 186 | |
| 187 | int main(int argc, char **argv) |
| 188 | { |
| 189 | nd_log_initialize_for_external_plugins("debugfs.plugin"); |
| 190 | netdata_threads_init_for_external_plugins(0); |
| 191 | |
| 192 | netdata_configured_host_prefix = getenv("NETDATA_HOST_PREFIX"); |
| 193 | if (verify_netdata_host_prefix(true) == -1) |
| 194 | exit(1); |
| 195 | |
| 196 | user_config_dir = getenv("NETDATA_USER_CONFIG_DIR"); |
| 197 | if (user_config_dir == NULL) { |
| 198 | user_config_dir = CONFIG_DIR; |
| 199 | } |
| 200 | |
| 201 | stock_config_dir = getenv("NETDATA_STOCK_CONFIG_DIR"); |
| 202 | if (stock_config_dir == NULL) { |
| 203 | // netdata_log_info("NETDATA_CONFIG_DIR is not passed from netdata"); |
| 204 | stock_config_dir = LIBCONFIG_DIR; |
| 205 | } |
| 206 | |
| 207 | // FIXME: should first check if /sys/kernel/debug is mounted |
| 208 | |
| 209 | // FIXME: remove debugfs_check_sys_permission() after https://github.com/netdata/netdata/issues/15048 is fixed |
| 210 | if (!debugfs_check_capabilities() && !debugfs_am_i_running_as_root() && !debugfs_check_sys_permission()) { |
| 211 | uid_t uid = getuid(), euid = geteuid(); |
| 212 | #ifdef HAVE_CAPABILITY |
| 213 | netdata_log_error( |
| 214 | "debugfs.plugin should either run as root (now running with uid %u, euid %u) or have special capabilities. " |
| 215 | "cap_dac_read_search is needed for /sys/kernel/debug access, cap_audit_control for audit subsystem monitoring. " |
| 216 | "To enable capabilities run: sudo setcap cap_dac_read_search,cap_audit_control+ep %s; " |
| 217 | "To enable setuid to root run: sudo chown root:netdata %s; sudo chmod 4750 %s; ", |
| 218 | uid, |
| 219 | euid, |
| 220 | argv[0], |
| 221 | argv[0], |
| 222 | argv[0]); |
| 223 | #else |
| 224 | netdata_log_error( |
| 225 | "debugfs.plugin should either run as root (now running with uid %u, euid %u) or have special capabilities. " |
| 226 | "Without these, debugfs.plugin cannot access /sys/kernel/debug." |
| 227 | "Your system does not support capabilities. " |
| 228 | "To enable setuid to root run: sudo chown root:netdata %s; sudo chmod 4750 %s; ", |
| 229 | uid, |
| 230 | euid, |
| 231 | argv[0], |
| 232 | argv[0]); |
| 233 | #endif |
| 234 | exit(1); |
| 235 | } |
| 236 | |
| 237 | debugfs_parse_args(argc, argv); |
| 238 | |
| 239 | size_t iteration; |
| 240 | heartbeat_t hb; |
| 241 | heartbeat_init(&hb, update_every * USEC_PER_SEC); |
| 242 | |
| 243 | for (iteration = 0; iteration < 86400; iteration++) { |
| 244 | heartbeat_next(&hb); |
| 245 | int enabled = 0; |
| 246 | |
| 247 | for (int i = 0; debugfs_modules[i].name; i++) { |
| 248 | struct debugfs_module *pm = &debugfs_modules[i]; |
| 249 | if (unlikely(!pm->enabled)) |
| 250 | continue; |
| 251 | |
| 252 | pm->enabled = !pm->func(update_every, pm->name); |
| 253 | if (likely(pm->enabled)) |
| 254 | enabled++; |
| 255 | } |
| 256 | |
| 257 | if (!enabled) { |
| 258 | netdata_log_info("all modules are disabled, exiting..."); |
| 259 | return 1; |
| 260 | } |
| 261 | |
| 262 | netdata_mutex_lock(&stdout_mutex); |
| 263 | fprintf(stdout, "\n"); |
| 264 | fflush(stdout); |
| 265 | netdata_mutex_unlock(&stdout_mutex); |
| 266 | |
| 267 | if (ferror(stdout) && errno == EPIPE) { |
| 268 | netdata_log_error("error writing to stdout: EPIPE. Exiting..."); |
| 269 | return 1; |
| 270 | } |
| 271 | } |
| 272 | |
| 273 | module_libsensors_cleanup(); |
| 274 | |
| 275 | netdata_mutex_lock(&stdout_mutex); |
| 276 | fprintf(stdout, "EXIT\n"); |
| 277 | fflush(stdout); |
| 278 | netdata_mutex_unlock(&stdout_mutex); |
| 279 | return 0; |
| 280 | } |