0001
0002
0003 #define _GNU_SOURCE
0004 #include <dirent.h>
0005 #include <fcntl.h>
0006 #include <getopt.h>
0007 #include <regex.h>
0008 #include <signal.h>
0009 #include <stdio.h>
0010 #include <stdlib.h>
0011 #include <string.h>
0012 #include <sys/stat.h>
0013 #include <sys/signalfd.h>
0014 #include <sys/timerfd.h>
0015 #include <sys/types.h>
0016 #include <sys/wait.h>
0017 #include <time.h>
0018 #include <unistd.h>
0019 #include <linux/thermal.h>
0020
0021 #include <libconfig.h>
0022 #include "thermal-tools.h"
0023
0024 #define CLASS_THERMAL "/sys/class/thermal"
0025
0026 enum {
0027 THERMOMETER_SUCCESS = 0,
0028 THERMOMETER_OPTION_ERROR,
0029 THERMOMETER_LOG_ERROR,
0030 THERMOMETER_CONFIG_ERROR,
0031 THERMOMETER_TIME_ERROR,
0032 THERMOMETER_INIT_ERROR,
0033 THERMOMETER_RUNTIME_ERROR
0034 };
0035
0036 struct options {
0037 int loglvl;
0038 int logopt;
0039 int overwrite;
0040 int duration;
0041 const char *config;
0042 char postfix[PATH_MAX];
0043 char output[PATH_MAX];
0044 };
0045
0046 struct tz_regex {
0047 regex_t regex;
0048 int polling;
0049 };
0050
0051 struct configuration {
0052 struct tz_regex *tz_regex;
0053 int nr_tz_regex;
0054
0055 };
0056
0057 struct tz {
0058 FILE *file_out;
0059 int fd_temp;
0060 int fd_timer;
0061 int polling;
0062 const char *name;
0063 };
0064
0065 struct thermometer {
0066 struct tz *tz;
0067 int nr_tz;
0068 };
0069
0070 static struct tz_regex *configuration_tz_match(const char *expr,
0071 struct configuration *config)
0072 {
0073 int i;
0074
0075 for (i = 0; i < config->nr_tz_regex; i++) {
0076
0077 if (!regexec(&config->tz_regex[i].regex, expr, 0, NULL, 0))
0078 return &config->tz_regex[i];
0079 }
0080
0081 return NULL;
0082 }
0083
0084 static int configuration_default_init(struct configuration *config)
0085 {
0086 config->tz_regex = realloc(config->tz_regex, sizeof(*config->tz_regex) *
0087 (config->nr_tz_regex + 1));
0088
0089 if (regcomp(&config->tz_regex[config->nr_tz_regex].regex, ".*",
0090 REG_NOSUB | REG_EXTENDED)) {
0091 ERROR("Invalid regular expression\n");
0092 return -1;
0093 }
0094
0095 config->tz_regex[config->nr_tz_regex].polling = 250;
0096 config->nr_tz_regex = 1;
0097
0098 return 0;
0099 }
0100
0101 static int configuration_init(const char *path, struct configuration *config)
0102 {
0103 config_t cfg;
0104
0105 config_setting_t *tz;
0106 int i, length;
0107
0108 if (path && access(path, F_OK)) {
0109 ERROR("'%s' is not accessible\n", path);
0110 return -1;
0111 }
0112
0113 if (!path && !config->nr_tz_regex) {
0114 INFO("No thermal zones configured, using wildcard for all of them\n");
0115 return configuration_default_init(config);
0116 }
0117
0118 config_init(&cfg);
0119
0120 if (!config_read_file(&cfg, path)) {
0121 ERROR("Failed to parse %s:%d - %s\n", config_error_file(&cfg),
0122 config_error_line(&cfg), config_error_text(&cfg));
0123
0124 return -1;
0125 }
0126
0127 tz = config_lookup(&cfg, "thermal-zones");
0128 if (!tz) {
0129 ERROR("No thermal zone configured to be monitored\n");
0130 return -1;
0131 }
0132
0133 length = config_setting_length(tz);
0134
0135 INFO("Found %d thermal zone(s) regular expression\n", length);
0136
0137 for (i = 0; i < length; i++) {
0138
0139 config_setting_t *node;
0140 const char *name;
0141 int polling;
0142
0143 node = config_setting_get_elem(tz, i);
0144 if (!node) {
0145 ERROR("Missing node name '%d'\n", i);
0146 return -1;
0147 }
0148
0149 if (!config_setting_lookup_string(node, "name", &name)) {
0150 ERROR("Thermal zone name not found\n");
0151 return -1;
0152 }
0153
0154 if (!config_setting_lookup_int(node, "polling", &polling)) {
0155 ERROR("Polling value not found");
0156 return -1;
0157 }
0158
0159 config->tz_regex = realloc(config->tz_regex, sizeof(*config->tz_regex) *
0160 (config->nr_tz_regex + 1));
0161
0162 if (regcomp(&config->tz_regex[config->nr_tz_regex].regex, name,
0163 REG_NOSUB | REG_EXTENDED)) {
0164 ERROR("Invalid regular expression '%s'\n", name);
0165 continue;
0166 }
0167
0168 config->tz_regex[config->nr_tz_regex].polling = polling;
0169 config->nr_tz_regex++;
0170
0171 INFO("Thermal zone regular expression '%s' with polling %d\n",
0172 name, polling);
0173 }
0174
0175 return 0;
0176 }
0177
0178 static void usage(const char *cmd)
0179 {
0180 printf("%s Version: %s\n", cmd, VERSION);
0181 printf("Usage: %s [options]\n", cmd);
0182 printf("\t-h, --help\t\tthis help\n");
0183 printf("\t-o, --output <dir>\toutput directory for temperature capture\n");
0184 printf("\t-c, --config <file>\tconfiguration file\n");
0185 printf("\t-d, --duration <seconds>\tcapture duration\n");
0186 printf("\t-l, --loglevel <level>\tlog level: ");
0187 printf("DEBUG, INFO, NOTICE, WARN, ERROR\n");
0188 printf("\t-p, --postfix <string>\tpostfix to be happened at the end of the files\n");
0189 printf("\t-s, --syslog\t\toutput to syslog\n");
0190 printf("\t-w, --overwrite\t\toverwrite the temperature capture files if they exist\n");
0191 printf("\n");
0192 exit(0);
0193 }
0194
0195 static int options_init(int argc, char *argv[], struct options *options)
0196 {
0197 int opt;
0198 time_t now = time(NULL);
0199
0200 struct option long_options[] = {
0201 { "help", no_argument, NULL, 'h' },
0202 { "config", required_argument, NULL, 'c' },
0203 { "duration", required_argument, NULL, 'd' },
0204 { "loglevel", required_argument, NULL, 'l' },
0205 { "postfix", required_argument, NULL, 'p' },
0206 { "output", required_argument, NULL, 'o' },
0207 { "syslog", required_argument, NULL, 's' },
0208 { "overwrite", no_argument, NULL, 'w' },
0209 { 0, 0, 0, 0 }
0210 };
0211
0212 strftime(options->postfix, sizeof(options->postfix),
0213 "-%Y-%m-%d_%H:%M:%S", gmtime(&now));
0214
0215 while (1) {
0216
0217 int optindex = 0;
0218
0219 opt = getopt_long(argc, argv, "ho:c:d:l:p:sw", long_options, &optindex);
0220 if (opt == -1)
0221 break;
0222
0223 switch (opt) {
0224 case 'c':
0225 options->config = optarg;
0226 break;
0227 case 'd':
0228 options->duration = atoi(optarg) * 1000;
0229 break;
0230 case 'l':
0231 options->loglvl = log_str2level(optarg);
0232 break;
0233 case 'h':
0234 usage(basename(argv[0]));
0235 break;
0236 case 'p':
0237 strcpy(options->postfix, optarg);
0238 break;
0239 case 'o':
0240 strcpy(options->output, optarg);
0241 break;
0242 case 's':
0243 options->logopt = TO_SYSLOG;
0244 break;
0245 case 'w':
0246 options->overwrite = 1;
0247 break;
0248 default:
0249 ERROR("Usage: %s --help\n", argv[0]);
0250 return -1;
0251 }
0252 }
0253
0254 return 0;
0255 }
0256
0257 static int thermometer_add_tz(const char *path, const char *name, int polling,
0258 struct thermometer *thermometer)
0259 {
0260 int fd;
0261 char tz_path[PATH_MAX];
0262
0263 sprintf(tz_path, CLASS_THERMAL"/%s/temp", path);
0264
0265 fd = open(tz_path, O_RDONLY);
0266 if (fd < 0) {
0267 ERROR("Failed to open '%s': %m\n", tz_path);
0268 return -1;
0269 }
0270
0271 thermometer->tz = realloc(thermometer->tz,
0272 sizeof(*thermometer->tz) * (thermometer->nr_tz + 1));
0273 if (!thermometer->tz) {
0274 ERROR("Failed to allocate thermometer->tz\n");
0275 return -1;
0276 }
0277
0278 thermometer->tz[thermometer->nr_tz].fd_temp = fd;
0279 thermometer->tz[thermometer->nr_tz].name = strdup(name);
0280 thermometer->tz[thermometer->nr_tz].polling = polling;
0281 thermometer->nr_tz++;
0282
0283 INFO("Added thermal zone '%s->%s (polling:%d)'\n", path, name, polling);
0284
0285 return 0;
0286 }
0287
0288 static int thermometer_init(struct configuration *config,
0289 struct thermometer *thermometer)
0290 {
0291 DIR *dir;
0292 struct dirent *dirent;
0293 struct tz_regex *tz_regex;
0294 const char *tz_dirname = "thermal_zone";
0295
0296 if (mainloop_init()) {
0297 ERROR("Failed to start mainloop\n");
0298 return -1;
0299 }
0300
0301 dir = opendir(CLASS_THERMAL);
0302 if (!dir) {
0303 ERROR("failed to open '%s'\n", CLASS_THERMAL);
0304 return -1;
0305 }
0306
0307 while ((dirent = readdir(dir))) {
0308 char tz_type[THERMAL_NAME_LENGTH];
0309 char tz_path[PATH_MAX];
0310 FILE *tz_file;
0311
0312 if (strncmp(dirent->d_name, tz_dirname, strlen(tz_dirname)))
0313 continue;
0314
0315 sprintf(tz_path, CLASS_THERMAL"/%s/type", dirent->d_name);
0316
0317 tz_file = fopen(tz_path, "r");
0318 if (!tz_file) {
0319 ERROR("Failed to open '%s': %m", tz_path);
0320 continue;
0321 }
0322
0323 fscanf(tz_file, "%s", tz_type);
0324
0325 fclose(tz_file);
0326
0327 tz_regex = configuration_tz_match(tz_type, config);
0328 if (!tz_regex)
0329 continue;
0330
0331 if (thermometer_add_tz(dirent->d_name, tz_type,
0332 tz_regex->polling, thermometer))
0333 continue;
0334 }
0335
0336 closedir(dir);
0337
0338 return 0;
0339 }
0340
0341 static int timer_temperature_callback(int fd, void *arg)
0342 {
0343 struct tz *tz = arg;
0344 char buf[16] = { 0 };
0345
0346 pread(tz->fd_temp, buf, sizeof(buf), 0);
0347
0348 fprintf(tz->file_out, "%ld %s", getuptimeofday_ms(), buf);
0349
0350 read(fd, buf, sizeof(buf));
0351
0352 return 0;
0353 }
0354
0355 static int thermometer_start(struct thermometer *thermometer,
0356 struct options *options)
0357 {
0358 struct itimerspec timer_it = { 0 };
0359 char *path;
0360 FILE *f;
0361 int i;
0362
0363 INFO("Capturing %d thermal zone(s) temperature...\n", thermometer->nr_tz);
0364
0365 if (access(options->output, F_OK) && mkdir(options->output, 0700)) {
0366 ERROR("Failed to create directory '%s'\n", options->output);
0367 return -1;
0368 }
0369
0370 for (i = 0; i < thermometer->nr_tz; i++) {
0371
0372 asprintf(&path, "%s/%s%s", options->output,
0373 thermometer->tz[i].name, options->postfix);
0374
0375 if (!options->overwrite && !access(path, F_OK)) {
0376 ERROR("'%s' already exists\n", path);
0377 return -1;
0378 }
0379
0380 f = fopen(path, "w");
0381 if (!f) {
0382 ERROR("Failed to create '%s':%m\n", path);
0383 return -1;
0384 }
0385
0386 fprintf(f, "timestamp(ms) %s(°mC)\n", thermometer->tz[i].name);
0387
0388 thermometer->tz[i].file_out = f;
0389
0390 DEBUG("Created '%s' file for thermal zone '%s'\n", path, thermometer->tz[i].name);
0391
0392
0393
0394
0395 thermometer->tz[i].fd_timer = timerfd_create(CLOCK_MONOTONIC, 0);
0396 if (thermometer->tz[i].fd_timer < 0) {
0397 ERROR("Failed to create timer for '%s': %m\n",
0398 thermometer->tz[i].name);
0399 return -1;
0400 }
0401
0402 DEBUG("Watching '%s' every %d ms\n",
0403 thermometer->tz[i].name, thermometer->tz[i].polling);
0404
0405 timer_it.it_interval = timer_it.it_value =
0406 msec_to_timespec(thermometer->tz[i].polling);
0407
0408 if (timerfd_settime(thermometer->tz[i].fd_timer, 0,
0409 &timer_it, NULL) < 0)
0410 return -1;
0411
0412 if (mainloop_add(thermometer->tz[i].fd_timer,
0413 timer_temperature_callback,
0414 &thermometer->tz[i]))
0415 return -1;
0416 }
0417
0418 return 0;
0419 }
0420
0421 static int thermometer_execute(int argc, char *argv[], char *const envp[], pid_t *pid)
0422 {
0423 if (!argc)
0424 return 0;
0425
0426 *pid = fork();
0427 if (*pid < 0) {
0428 ERROR("Failed to fork process: %m");
0429 return -1;
0430 }
0431
0432 if (!(*pid)) {
0433 execvpe(argv[0], argv, envp);
0434 exit(1);
0435 }
0436
0437 return 0;
0438 }
0439
0440 static int kill_process(__maybe_unused int fd, void *arg)
0441 {
0442 pid_t pid = *(pid_t *)arg;
0443
0444 if (kill(pid, SIGTERM))
0445 ERROR("Failed to send SIGTERM signal to '%d': %p\n", pid);
0446 else if (waitpid(pid, NULL, 0))
0447 ERROR("Failed to wait pid '%d': %p\n", pid);
0448
0449 mainloop_exit();
0450
0451 return 0;
0452 }
0453
0454 static int exit_mainloop(__maybe_unused int fd, __maybe_unused void *arg)
0455 {
0456 mainloop_exit();
0457 return 0;
0458 }
0459
0460 static int thermometer_wait(struct options *options, pid_t pid)
0461 {
0462 int fd;
0463 sigset_t mask;
0464
0465
0466
0467
0468
0469
0470 if (options->duration) {
0471 struct itimerspec timer_it = { 0 };
0472
0473 timer_it.it_value = msec_to_timespec(options->duration);
0474
0475 fd = timerfd_create(CLOCK_MONOTONIC, 0);
0476 if (fd < 0) {
0477 ERROR("Failed to create duration timer: %m\n");
0478 return -1;
0479 }
0480
0481 if (timerfd_settime(fd, 0, &timer_it, NULL)) {
0482 ERROR("Failed to set timer time: %m\n");
0483 return -1;
0484 }
0485
0486 if (mainloop_add(fd, pid < 0 ? exit_mainloop : kill_process, &pid)) {
0487 ERROR("Failed to set timer exit mainloop callback\n");
0488 return -1;
0489 }
0490 }
0491
0492
0493
0494
0495
0496 sigemptyset(&mask);
0497 sigaddset(&mask, SIGINT);
0498 sigaddset(&mask, SIGQUIT);
0499 sigaddset(&mask, SIGCHLD);
0500
0501 if (sigprocmask(SIG_BLOCK, &mask, NULL)) {
0502 ERROR("Failed to set sigprocmask: %m\n");
0503 return -1;
0504 }
0505
0506 fd = signalfd(-1, &mask, 0);
0507 if (fd < 0) {
0508 ERROR("Failed to set the signalfd: %m\n");
0509 return -1;
0510 }
0511
0512 if (mainloop_add(fd, exit_mainloop, NULL)) {
0513 ERROR("Failed to set timer exit mainloop callback\n");
0514 return -1;
0515 }
0516
0517 return mainloop(-1);
0518 }
0519
0520 static int thermometer_stop(struct thermometer *thermometer)
0521 {
0522 int i;
0523
0524 INFO("Closing/flushing output files\n");
0525
0526 for (i = 0; i < thermometer->nr_tz; i++)
0527 fclose(thermometer->tz[i].file_out);
0528
0529 return 0;
0530 }
0531
0532 int main(int argc, char *argv[], char *const envp[])
0533 {
0534 struct options options = {
0535 .loglvl = LOG_DEBUG,
0536 .logopt = TO_STDOUT,
0537 .output = ".",
0538 };
0539 struct configuration config = { 0 };
0540 struct thermometer thermometer = { 0 };
0541
0542 pid_t pid = -1;
0543
0544 if (options_init(argc, argv, &options))
0545 return THERMOMETER_OPTION_ERROR;
0546
0547 if (log_init(options.loglvl, argv[0], options.logopt))
0548 return THERMOMETER_LOG_ERROR;
0549
0550 if (configuration_init(options.config, &config))
0551 return THERMOMETER_CONFIG_ERROR;
0552
0553 if (uptimeofday_init())
0554 return THERMOMETER_TIME_ERROR;
0555
0556 if (thermometer_init(&config, &thermometer))
0557 return THERMOMETER_INIT_ERROR;
0558
0559 if (thermometer_start(&thermometer, &options))
0560 return THERMOMETER_RUNTIME_ERROR;
0561
0562 if (thermometer_execute(argc - optind, &argv[optind], envp, &pid))
0563 return THERMOMETER_RUNTIME_ERROR;
0564
0565 if (thermometer_wait(&options, pid))
0566 return THERMOMETER_RUNTIME_ERROR;
0567
0568 if (thermometer_stop(&thermometer))
0569 return THERMOMETER_RUNTIME_ERROR;
0570
0571 return THERMOMETER_SUCCESS;
0572 }