Finished the conversion to C.
[breezed.git] / breezed.c
diff --git a/breezed.c b/breezed.c
new file mode 100644 (file)
index 0000000..054179e
--- /dev/null
+++ b/breezed.c
@@ -0,0 +1,594 @@
+
+/*
+
+   breezed is a fan speed control daemon for Linux computers.
+
+   Copyright (c) 2008, 2009 Francois Fleuret
+   Written by Francois Fleuret <francois@fleuret.org>
+
+   This file is part of breezed.
+
+   breezed is free software: you can redistribute it and/or modify it
+   under the terms of the GNU General Public License version 3 as
+   published by the Free Software Foundation.
+
+   breezed is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with breezed.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+
+const int major_version_number = 1;
+const int minor_version_number = 2;
+
+const int buffer_size = 1024;
+
+const char *default_configuration_file = "/etc/breezed.conf";
+
+/* The time period to check the temperature. */
+const int polling_delay = 5;
+
+/* Minimum time between last change and a change down. */
+const int minimum_delay_to_go_down = 30;
+
+/* Gap between a threshold to go up and a threshold to go down to
+   reduce the oscillations. */
+const int down_temperature_delta = 2;
+
+char *file_fan = 0;
+
+int debug = 0;
+int nb_rounds_since_last_change = 0;
+int last_level = -1;
+
+int file_fan_fd;
+
+int nb_temperature_thresholds;
+int *temperature_thresholds = 0;
+
+int nb_file_thermal = 0;
+char **file_thermal = 0;
+int *file_thermal_fd = 0;
+
+char *configuration_file;
+
+/******************************************************************/
+
+char *next_word(char *buffer, char *r, int buffer_size) {
+  char *s;
+  s = buffer;
+
+  if(r != 0) {
+    while((*r == ' ') || (*r == '\t') || (*r == ',')) r++;
+    if(*r == '"') {
+      r++;
+      while((*r != '"') && (*r != '\0') &&
+            (s<buffer+buffer_size-1))
+        *s++ = *r++;
+      if(*r == '"') r++;
+    } else {
+      while((*r != '\r') && (*r != '\n') && (*r != '\0') &&
+            (*r != '\t') && (*r != ' ') && (*r != ',')) {
+        if(s == buffer + buffer_size) {
+          fprintf(stderr, "Buffer overflow in next_word.\n");
+          exit(1);
+        }
+        *s++ = *r++;
+      }
+    }
+
+    while((*r == ' ') || (*r == '\t') || (*r == ',')) r++;
+    if((*r == '\0') || (*r=='\r') || (*r=='\n')) r = 0;
+  }
+  *s = '\0';
+
+  return r;
+}
+
+void set_fan_level(int fan_fd, int f, int max_fan_level) {
+  char buffer[buffer_size];
+  if(f < 0 || f > max_fan_level) f = max_fan_level;
+  sprintf(buffer, "level %d\n", f);
+  if(write(fan_fd, buffer, strlen(buffer)) < 0) {
+    fprintf(stderr, "Error in setting the fan level (%s).\n",
+            strerror(errno));
+    exit(1);
+  }
+}
+
+void define_thermal_files(char *definition) {
+  char token[buffer_size];
+
+  if(file_thermal) {
+    fprintf(stderr, "Thermal files already defined.\n");
+    exit(1);
+  }
+
+  char *s;
+  s = definition;
+  while(s) {
+    s = next_word(token, s, buffer_size);
+    nb_file_thermal++;
+  }
+
+  file_thermal = (char **) malloc(nb_file_thermal * sizeof(char *));
+  file_thermal_fd = (int *) malloc(nb_file_thermal * sizeof(int));
+  s = definition;
+  int k = 0;
+  while(s) {
+    s = next_word(token, s, buffer_size);
+    file_thermal[k] = strdup(token);
+    k++;
+  }
+}
+
+void define_temperature_thresholds(char *definition) {
+  char token[buffer_size];
+
+  if(temperature_thresholds) {
+    fprintf(stderr, "Temperature thresholds already defined.\n");
+    exit(1);
+  }
+
+  nb_temperature_thresholds = 1;
+
+  char *s;
+  s = definition;
+  while(s) {
+    s = next_word(token, s, buffer_size);
+    nb_temperature_thresholds++;
+  }
+
+  temperature_thresholds =
+    (int *) malloc(nb_temperature_thresholds * sizeof(int));
+
+  temperature_thresholds[0] = -1;
+
+  s = definition;
+  int k = 1;
+  while(s) {
+    s = next_word(token, s, buffer_size);
+    temperature_thresholds[k] = atoi(token);
+    if(k > 0 &&
+       temperature_thresholds[k] < temperature_thresholds[k-1]) {
+      fprintf(stderr, "The temperature thresholds have to be increasing.\n");
+      exit(0);
+    }
+    k++;
+  }
+}
+
+void evaluate_one_configuration_line(char *line, int line_number) {
+  char token[buffer_size];
+  char *s;
+
+  s = next_word(token, line, buffer_size);
+
+  if(strcmp(token, "thermal_files") == 0) {
+    if(s == 0) {
+      fprintf(stderr, "Missing parameter in %s:%d\n",
+              configuration_file, line_number);
+      exit(1);
+    }
+    define_thermal_files(s);
+  }
+
+  else if(strcmp(token, "debug") == 0) {
+    debug = 1;
+  }
+
+  else if(strcmp(token, "fan_file") == 0) {
+    if(file_fan) {
+      fprintf(stderr, "Fan file already defined.\n");
+      exit(1);
+    }
+    if(s == 0) {
+      fprintf(stderr, "Missing parameter in %s:%d\n",
+              configuration_file, line_number);
+      exit(1);
+    }
+    file_fan = strdup(s);
+  }
+
+  else if(strcmp(token, "temperature_thresholds") == 0) {
+    if(s == 0) {
+      fprintf(stderr, "Missing parameter in %s:%d\n",
+              configuration_file, line_number);
+      exit(1);
+    }
+    define_temperature_thresholds(s);
+  }
+
+  else if(token[0] && token[0] != '#') {
+    fprintf(stderr, "Unknown keyword '%s' in %s:%d.\n",
+            token, configuration_file, line_number);
+    exit(1);
+  }
+}
+
+/******************************************************************/
+
+int main(int argc, char **argv) {
+  char buffer[buffer_size];
+  int i, t;
+
+  configuration_file = strdup(default_configuration_file);
+
+  i = 1;
+  while(i < argc) {
+
+    if(strcmp(argv[i], "--debug") == 0 || strcmp(argv[i], "-d") == 0) {
+      debug = 1;
+      i++;
+    }
+
+    else if(strcmp(argv[i], "--version") == 0 || strcmp(argv[i], "-v") == 0) {
+      printf("Breezed v%d.%d. Written by Francois Fleuret (francois@fleuret.org).\n",
+             major_version_number, minor_version_number);
+      exit(0);
+    }
+
+    else if(strcmp(argv[i], "--no-configuration-file") == 0 ||
+            strcmp(argv[i], "-ncf") == 0) {
+      free(configuration_file);
+      configuration_file = 0;
+      i++;
+    }
+
+    else if(strcmp(argv[i], "--configuration-file") == 0 ||
+            strcmp(argv[i], "-cf") == 0) {
+      i++;
+      if(i == argc) {
+        fprintf(stderr, "Missing parameter for %s.\n", argv[i - 1]);
+        exit(1);
+      }
+
+      free(configuration_file);
+      configuration_file = strdup(argv[i]);
+
+      i++;
+    }
+
+    else if(strcmp(argv[i], "--thermal-files") == 0 ||
+            strcmp(argv[i], "-tf") == 0) {
+      i++;
+      if(i == argc) {
+        fprintf(stderr, "Missing parameter for %s.\n", argv[i - 1]);
+        exit(1);
+      }
+      define_thermal_files(argv[i]);
+      i++;
+    }
+
+    else if(strcmp(argv[i], "--fan-file") == 0 ||
+            strcmp(argv[i], "-ff") == 0) {
+      i++;
+      if(i == argc) {
+        fprintf(stderr, "Missing parameter for %s.\n", argv[i - 1]);
+        exit(1);
+      }
+
+      if(file_fan) {
+        fprintf(stderr, "Fan file already defined.\n");
+        exit(1);
+      }
+      file_fan = strdup(argv[i]);
+
+      i++;
+    }
+
+    else if(strcmp(argv[i], "--temperature-thresholds") == 0 ||
+            strcmp(argv[i], "-tt") == 0) {
+      i++;
+
+      if(i == argc) {
+        fprintf(stderr, "Missing parameter for %s.\n", argv[i - 1]);
+        exit(1);
+      }
+
+      define_temperature_thresholds(argv[i]);
+      i++;
+    }
+
+    else if(strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
+
+      printf("%s [--version|-v] [--help|-h] [--debug|-d]\\\n\
+  [--configuration-file|-cf <file>]\\\n\
+  [--no-configuration-file|-ncf]\\\n\
+  [--thermal-files|-tf <thermalfile1,thermalfile2,...>] \\\n\
+  [--fanfile|-ff <fan file>] \\\n\
+  [--temperature-thresholds|-tt <t1,t2,t3,t4,...>]\n\
+\n\
+  --help|-h :                    shows this help\n\
+  --version|-v :                 shows the version number\n\
+  --debug|-d :                   prints out additional information\n\
+  --configuration-file|-cf       sets the configuration file\n\
+  --no-configuration-file|-ncf   do not load a configuration file\n\
+  --thermal-files|-tf :          sets where to look for temperatures\n\
+  --fanfile|-ff :                sets where to control the fan level\n\
+  --temperature-thresholds|-tt : sets the temperature thresholds\n\
+\n\
+This daemon polls the temperatures every 5s, takes the max and sets\n\
+the fan level accordingly. It uses as temperatures all the numbers it\n\
+finds in the provided \"thermal files\". Hence you can use as well\n\
+/proc/acpi/thermal_zone/THM*/temperature or /proc/acpi/ibm/thermal.\n\
+\n\
+The fan speed is set by echoing into the provided fan file.\n\
+\n\
+To reduce oscillations, it will not reduce the fan speed less than 30s\n\
+after the last previous change and the thresholds actually used to\n\
+reduce the fan speed are two degrees lower than the provided\n\
+thresholds, which are used to increase the fan speed.\n\
+\n\
+This daemon should be started through the adequate shell script in\n\
+/etc/init.d.\n\
+\n\
+Options can be set either in the configuration file, or on the \n\
+command line. Options can not be set twice.\n\
+\n\
+Version %d.%d, November 2009.\n\
+\n\
+Written by Francois Fleuret (francois@fleuret.org).\n",
+             argv[0],
+             major_version_number, minor_version_number);
+
+      exit(0);
+    }
+
+    else {
+      fprintf(stderr, "Unknown argument %s.\n", argv[i]);
+      exit(1);
+    }
+  }
+
+
+  /******************************************************************/
+
+  if(configuration_file) {
+    char raw_line[buffer_size];
+    int start, end, eol, k;
+    FILE *file;
+
+    file = fopen(configuration_file, "r");
+
+    if(!file) {
+      fprintf(stderr, "Can not open `%s' for reading.\n", configuration_file);
+      exit(1);
+    }
+
+    start = 0;
+    end = 0;
+
+    char *s;
+
+    int line_number = 0;
+
+    while(end > start || !feof(file)) {
+      eol = start;
+
+      /* Look for the end of a line in what is already in the buffer */
+      while(eol < end && raw_line[eol] != '\n') eol++;
+
+      /* if we did not find the of a line, move what has not been
+         processed and is in the buffer to the beginning of the buffer,
+         fill the buffer with new data from the file, and look for the
+         end of a line */
+      if(eol == end) {
+        for(k = 0; k < end - start; k++) {
+          raw_line[k] = raw_line[k + start];
+        }
+        end -= start;
+        eol -= start;
+        start = 0;
+        end += fread(raw_line + end, sizeof(char), buffer_size - end, file);
+        while(eol < end && raw_line[eol] != '\n') eol++;
+      }
+
+      /* The end of the line is the buffer size, which means the line is
+         too long */
+
+      if(eol == buffer_size) {
+        raw_line[buffer_size - 1] = '\0';
+        fprintf(stderr, "Selector: Line too long (max is %d characters):\n",
+                buffer_size);
+        fprintf(stderr, raw_line);
+        fprintf(stderr, "\n");
+        exit(1);
+      }
+
+      /* If we got a line, we replace the carriage return by a \0 to
+         finish the string */
+
+      raw_line[eol] = '\0';
+
+      /* here we process the line */
+
+      line_number++;
+
+      if(debug) {
+        printf("%s:%d \"%s\"\n",
+               configuration_file, line_number, raw_line + start);
+      }
+
+      evaluate_one_configuration_line(raw_line + start, line_number);
+
+      start = eol + 1;
+    }
+
+  }
+
+  /******************************************************************/
+
+  if(nb_temperature_thresholds == 0) {
+    fprintf(stderr, "No temperature threshold was provided.\n");
+    exit(1);
+  }
+
+  if(nb_file_thermal == 0) {
+    fprintf(stderr, "No thermal file was provided.\n");
+    exit(1);
+  }
+
+  if(file_fan == 0){
+    fprintf(stderr, "No fan file was provided.\n");
+    exit(1);
+  }
+
+  for(i = 0; i < nb_file_thermal; i++) {
+    file_thermal_fd[i] = open(file_thermal[i], O_RDONLY);
+    if(file_thermal_fd[i] < 0) {
+      fprintf(stderr, "Can not open %s for reading (%s).\n",
+              file_thermal[i], strerror(errno));
+      exit(1);
+    }
+  }
+
+  file_fan_fd = open(file_fan, O_WRONLY);
+
+  if(file_fan_fd < 0) {
+    fprintf(stderr, "Can not open %s for writing (%s).\n",
+            file_fan, strerror(errno));
+    exit(1);
+  }
+
+  /******************************************************************/
+
+  if(debug) {
+    for(t = 0; t < nb_file_thermal; t++) {
+      printf("file_thermal[%d] %s\n", t, file_thermal[t]);
+    }
+
+    printf("file_fan %s\n", file_fan);
+
+    for(t = 0; t < nb_temperature_thresholds; t++) {
+      printf("temperature_thresholds[%d] %d",
+             t, temperature_thresholds[t]);
+    }
+  }
+
+  /******************************************************************/
+
+  while(1) {
+
+    int temperature = -1;
+
+    if(debug) {
+      printf("Temperature:");
+    }
+
+    for(i = 0; i < nb_file_thermal; i++) {
+      lseek(file_thermal_fd[i], 0, SEEK_SET);
+      int size = read(file_thermal_fd[i], buffer, buffer_size);
+
+      if(size > 0 && size < buffer_size) {
+        buffer[size] = '\0';
+
+        char *s = buffer;
+
+        while(*s) {
+          while(*s && *s != '-' && (*s < '0') || (*s > '9')) s++;
+
+          if(*s) {
+            int t = 0, sign = 1;
+            if(*s == '-') {
+              sign = -1;
+              s++;
+            }
+
+            while(*s >= '0' && *s <= '9') {
+              t = t * 10 + (*s - '0');
+              s++;
+            }
+
+            t *= sign;
+            if(debug) {
+              printf(" %d", t);
+            }
+
+            /* So that we can deal with the new files where the
+               temperature are in 1/1000th of C */
+            if(t > 1000) t /= 1000;
+
+            if(t > temperature) temperature = t;
+          }
+        }
+
+        if(debug) {
+          if(i < nb_file_thermal - 1)
+            printf(" /");
+          else
+            printf("\n");
+        }
+      } else {
+        if(size == 0) {
+          fprintf(stderr, "Nothing to read in %d.\n", file_thermal[i]);
+        } else if(size < 0) {
+          fprintf(stderr, "Error while reading %s (%s).\n",
+                  file_thermal[i], strerror(errno));
+        } else {
+          fprintf(stderr, "Error while reading %s (too large).\n",
+                  file_thermal[i]);
+        }
+        exit(1);
+      }
+    }
+
+    if(temperature < 0) {
+      fprintf(stderr, "Could not read a meaningful temperature.\n");
+      exit(1);
+    }
+
+    nb_rounds_since_last_change++;
+
+    int new_level = last_level;
+
+    int new_level_up = nb_temperature_thresholds - 1;
+    while(new_level_up > 0 &&
+          temperature < temperature_thresholds[new_level_up]) {
+      new_level_up--;
+    }
+
+    if(new_level_up > last_level) {
+      new_level = new_level_up;
+    } else {
+      int new_level_down = nb_temperature_thresholds - 1;
+      while(new_level_down > 0 &&
+            temperature <
+            temperature_thresholds[new_level_down] - down_temperature_delta)
+        new_level_down--;
+      if(new_level_down < last_level &&
+         nb_rounds_since_last_change * polling_delay >=
+         minimum_delay_to_go_down) {
+        new_level = new_level_down;
+      }
+    }
+
+    /* We set it every time, even when there is no change, to handle
+       when the level has been modified somewhere else (for instance
+       when combing back from suspend). */
+
+    set_fan_level(file_fan_fd, new_level, nb_temperature_thresholds - 1);
+
+    if(new_level != last_level) {
+      nb_rounds_since_last_change = 0;
+      last_level = new_level;
+      if(debug) {
+        printf("Temperature is %dC setting the fan level to %d.",
+               temperature, new_level);
+      }
+    }
+
+    sleep(polling_delay);
+  }
+}