Fixed a bug for level 0 which must now be defined in the configuration file.
[breezed.git] / breezed.c
1
2 /*
3
4    breezed is a fan speed control daemon for Linux computers.
5
6    Copyright (c) 2008, 2009, 2010 Francois Fleuret
7    Written by Francois Fleuret <francois@fleuret.org>
8
9    This file is part of breezed.
10
11    breezed is free software: you can redistribute it and/or modify it
12    under the terms of the GNU General Public License version 3 as
13    published by the Free Software Foundation.
14
15    breezed is distributed in the hope that it will be useful, but
16    WITHOUT ANY WARRANTY; without even the implied warranty of
17    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18    General Public License for more details.
19
20    You should have received a copy of the GNU General Public License
21    along with breezed.  If not, see <http://www.gnu.org/licenses/>.
22
23 */
24
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <fcntl.h>
28 #include <errno.h>
29 #include <unistd.h>
30 #include <string.h>
31
32 const int major_version_number = 1;
33 const int minor_version_number = 4;
34
35 const int buffer_size = 1024;
36
37 const char *default_configuration_file = "/etc/breezed.conf";
38
39 /* The time period to check the temperature. */
40 const int polling_delay = 5;
41
42 /* Minimum time between last change and a change down. */
43 const int minimum_delay_to_go_down = 30;
44
45 /* Gap between a threshold to go up and a threshold to go down to
46    reduce the oscillations. */
47 const int down_temperature_delta = 2;
48
49 char *file_fan = 0;
50
51 int debug = 0;
52 int nb_rounds_since_last_change = 0;
53 int last_level = -1;
54
55 int file_fan_fd;
56
57 int nb_temperature_thresholds;
58 int *temperature_thresholds = 0;
59 char **speed_names = 0;
60
61 int nb_file_thermal = 0;
62 char **file_thermal = 0;
63 int *file_thermal_fd = 0;
64
65 char *configuration_file;
66
67 /********************************************************************/
68
69 /* malloc with error checking.  */
70
71 void *safe_malloc(size_t n) {
72   void *p = malloc(n);
73   if (!p && n != 0) {
74     fprintf(stderr, "Can not allocate memory: %s\n", strerror(errno));
75     exit(EXIT_FAILURE);
76   }
77   return p;
78 }
79
80 /******************************************************************/
81
82 char *next_word(char *buffer, char *r, int buffer_size) {
83   char *s;
84   s = buffer;
85
86   if(r != 0) {
87     while((*r == ' ') || (*r == '\t') || (*r == ',')) r++;
88     if(*r == '"') {
89       r++;
90       while((*r != '"') && (*r != '\0') &&
91             (s<buffer+buffer_size-1))
92         *s++ = *r++;
93       if(*r == '"') r++;
94     } else {
95       while((*r != '\r') && (*r != '\n') && (*r != '\0') &&
96             (*r != '\t') && (*r != ' ') && (*r != ',')) {
97         if(s == buffer + buffer_size) {
98           fprintf(stderr, "Buffer overflow in next_word.\n");
99           exit(EXIT_FAILURE);
100         }
101         *s++ = *r++;
102       }
103     }
104
105     while((*r == ' ') || (*r == '\t') || (*r == ',')) r++;
106     if((*r == '\0') || (*r=='\r') || (*r=='\n')) r = 0;
107   }
108   *s = '\0';
109
110   return r;
111 }
112
113 void set_fan_level(int fan_fd, int f, int max_fan_level) {
114   char buffer[buffer_size];
115   if(f < 0 || f > max_fan_level) f = max_fan_level;
116   sprintf(buffer, "level %s\n", speed_names[f]);
117   if(write(fan_fd, buffer, strlen(buffer)) < 0) {
118     fprintf(stderr, "Error in setting the fan level (%s).\n",
119             strerror(errno));
120     exit(EXIT_FAILURE);
121   }
122 }
123
124 void define_thermal_files(char *definition) {
125   char token[buffer_size];
126
127   if(file_thermal) {
128     fprintf(stderr, "Thermal files already defined.\n");
129     exit(EXIT_FAILURE);
130   }
131
132   char *s;
133   s = definition;
134   while(s) {
135     s = next_word(token, s, buffer_size);
136     nb_file_thermal++;
137   }
138
139   file_thermal = safe_malloc(nb_file_thermal * sizeof(char *));
140   file_thermal_fd = safe_malloc(nb_file_thermal * sizeof(int));
141   s = definition;
142   int k = 0;
143   while(s) {
144     s = next_word(token, s, buffer_size);
145     file_thermal[k] = strdup(token);
146     k++;
147   }
148 }
149
150 void define_temperature_thresholds(char *definition) {
151   char token[buffer_size];
152
153   if(temperature_thresholds) {
154     fprintf(stderr, "Temperature thresholds already defined.\n");
155     exit(EXIT_FAILURE);
156   }
157
158   nb_temperature_thresholds = 0;
159
160   char *s, *u;
161
162   s = definition;
163   while(s) {
164     s = next_word(token, s, buffer_size);
165     nb_temperature_thresholds++;
166   }
167
168   temperature_thresholds =
169     safe_malloc(nb_temperature_thresholds * sizeof(int));
170
171   speed_names =
172     safe_malloc(nb_temperature_thresholds * sizeof(char *));
173
174   s = definition;
175   int k = 0;
176   while(s) {
177     s = next_word(token, s, buffer_size);
178     u = token;
179     while(*u && *u != ':') { u++; }
180     if(*u) {
181       *u = '\0';
182       temperature_thresholds[k] = atoi(token);
183       u++;
184       speed_names[k] = strdup(u);
185     } else {
186       temperature_thresholds[k] = atoi(token);
187       snprintf(token, buffer_size, "%d", k);
188       speed_names[k] = strdup(token);
189     }
190
191     if(k > 0 &&
192        temperature_thresholds[k] < temperature_thresholds[k-1]) {
193       fprintf(stderr, "The temperature thresholds have to be increasing.\n");
194       exit(EXIT_FAILURE);
195     }
196     k++;
197   }
198
199   if(nb_temperature_thresholds <= 0) {
200     fprintf(stderr, "There has to be at least one temperature.\n");
201     exit(EXIT_FAILURE);
202   }
203
204   if(temperature_thresholds[0] > 0) {
205     fprintf(stderr, "The lowest temperature has to be 0.\n");
206     exit(EXIT_FAILURE);
207   }
208
209 }
210
211 void evaluate_one_configuration_line(char *line, int line_number) {
212   char token[buffer_size];
213   char *s;
214
215   s = next_word(token, line, buffer_size);
216
217   if(strcmp(token, "thermal_files") == 0) {
218     if(s == 0) {
219       fprintf(stderr, "Missing parameter in %s:%d\n",
220               configuration_file, line_number);
221       exit(EXIT_FAILURE);
222     }
223     define_thermal_files(s);
224   }
225
226   else if(strcmp(token, "debug") == 0) {
227     debug = 1;
228   }
229
230   else if(strcmp(token, "fan_file") == 0) {
231     if(file_fan) {
232       fprintf(stderr, "Fan file already defined.\n");
233       exit(EXIT_FAILURE);
234     }
235     if(s == 0) {
236       fprintf(stderr, "Missing parameter in %s:%d\n",
237               configuration_file, line_number);
238       exit(EXIT_FAILURE);
239     }
240     file_fan = strdup(s);
241   }
242
243   else if(strcmp(token, "temperature_thresholds") == 0) {
244     if(s == 0) {
245       fprintf(stderr, "Missing parameter in %s:%d\n",
246               configuration_file, line_number);
247       exit(EXIT_FAILURE);
248     }
249     define_temperature_thresholds(s);
250   }
251
252   else if(token[0] && token[0] != '#') {
253     fprintf(stderr, "Unknown keyword '%s' in %s:%d.\n",
254             token, configuration_file, line_number);
255     exit(EXIT_FAILURE);
256   }
257 }
258
259 /******************************************************************/
260
261 int main(int argc, char **argv) {
262   char buffer[buffer_size];
263   int i, t;
264
265   configuration_file = strdup(default_configuration_file);
266
267   i = 1;
268   while(i < argc) {
269
270     if(strcmp(argv[i], "--debug") == 0 || strcmp(argv[i], "-d") == 0) {
271       debug = 1;
272       i++;
273     }
274
275     else if(strcmp(argv[i], "--version") == 0 || strcmp(argv[i], "-v") == 0) {
276       printf("Breezed v%d.%d. Written by Francois Fleuret (francois@fleuret.org).\n",
277              major_version_number, minor_version_number);
278       exit(0);
279     }
280
281     else if(strcmp(argv[i], "--no-configuration-file") == 0 ||
282             strcmp(argv[i], "-ncf") == 0) {
283       free(configuration_file);
284       configuration_file = 0;
285       i++;
286     }
287
288     else if(strcmp(argv[i], "--configuration-file") == 0 ||
289             strcmp(argv[i], "-cf") == 0) {
290       i++;
291       if(i == argc) {
292         fprintf(stderr, "Missing parameter for %s.\n", argv[i - 1]);
293         exit(EXIT_FAILURE);
294       }
295
296       free(configuration_file);
297       configuration_file = strdup(argv[i]);
298
299       i++;
300     }
301
302     else if(strcmp(argv[i], "--thermal-files") == 0 ||
303             strcmp(argv[i], "-tf") == 0) {
304       i++;
305       if(i == argc) {
306         fprintf(stderr, "Missing parameter for %s.\n", argv[i - 1]);
307         exit(EXIT_FAILURE);
308       }
309       define_thermal_files(argv[i]);
310       i++;
311     }
312
313     else if(strcmp(argv[i], "--fan-file") == 0 ||
314             strcmp(argv[i], "-ff") == 0) {
315       i++;
316       if(i == argc) {
317         fprintf(stderr, "Missing parameter for %s.\n", argv[i - 1]);
318         exit(EXIT_FAILURE);
319       }
320
321       if(file_fan) {
322         fprintf(stderr, "Fan file already defined.\n");
323         exit(EXIT_FAILURE);
324       }
325       file_fan = strdup(argv[i]);
326
327       i++;
328     }
329
330     else if(strcmp(argv[i], "--temperature-thresholds") == 0 ||
331             strcmp(argv[i], "-tt") == 0) {
332       i++;
333
334       if(i == argc) {
335         fprintf(stderr, "Missing parameter for %s.\n", argv[i - 1]);
336         exit(EXIT_FAILURE);
337       }
338
339       define_temperature_thresholds(argv[i]);
340       i++;
341     }
342
343     else if(strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
344
345       printf("%s [--version|-v] [--help|-h] [--debug|-d]\\\n\
346   [--configuration-file|-cf <file>]\\\n\
347   [--no-configuration-file|-ncf]\\\n\
348   [--thermal-files|-tf <thermalfile1,thermalfile2,...>] \\\n\
349   [--fanfile|-ff <fan file>] \\\n\
350   [--temperature-thresholds|-tt <t1,t2,t3,t4,...>]\n\
351 \n\
352   --help|-h :                    shows this help\n\
353   --version|-v :                 shows the version number\n\
354   --debug|-d :                   prints out additional information\n\
355   --configuration-file|-cf       sets the configuration file\n\
356   --no-configuration-file|-ncf   do not load a configuration file\n\
357   --thermal-files|-tf :          sets where to look for temperatures\n\
358   --fanfile|-ff :                sets where to control the fan level\n\
359   --temperature-thresholds|-tt : sets the temperature thresholds\n\
360 \n\
361 This daemon polls the temperatures every 5s, takes the max and sets\n\
362 the fan level accordingly. It uses as temperatures all the numbers it\n\
363 finds in the provided \"thermal files\". Hence you can use as well\n\
364 /proc/acpi/thermal_zone/THM*/temperature or /proc/acpi/ibm/thermal.\n\
365 \n\
366 The fan speed is set by echoing into the provided fan file.\n\
367 \n\
368 To reduce oscillations, it will not reduce the fan speed less than 30s\n\
369 after the last previous change and the thresholds actually used to\n\
370 reduce the fan speed are two degrees lower than the provided\n\
371 thresholds, which are used to increase the fan speed.\n\
372 \n\
373 This daemon should be started through the adequate shell script in\n\
374 /etc/init.d.\n\
375 \n\
376 Options can be set either in the configuration file, or on the \n\
377 command line. Options can not be set twice.\n\
378 \n\
379 Version %d.%d, November 2009.\n\
380 \n\
381 Written by Francois Fleuret (francois@fleuret.org).\n",
382              argv[0],
383              major_version_number, minor_version_number);
384
385       exit(0);
386     }
387
388     else {
389       fprintf(stderr, "Unknown argument %s.\n", argv[i]);
390       exit(EXIT_FAILURE);
391     }
392   }
393
394
395   /******************************************************************/
396
397   if(configuration_file) {
398     char raw_line[buffer_size];
399     int start, end, eol, k;
400     FILE *file;
401
402     file = fopen(configuration_file, "r");
403
404     if(!file) {
405       fprintf(stderr, "Can not open `%s' for reading.\n", configuration_file);
406       exit(EXIT_FAILURE);
407     }
408
409     start = 0;
410     end = 0;
411
412     char *s;
413
414     int line_number = 0;
415
416     while(end > start || !feof(file)) {
417       eol = start;
418
419       /* Look for the end of a line in what is already in the buffer */
420       while(eol < end && raw_line[eol] != '\n') eol++;
421
422       /* if we did not find the of a line, move what has not been
423          processed and is in the buffer to the beginning of the buffer,
424          fill the buffer with new data from the file, and look for the
425          end of a line */
426       if(eol == end) {
427         for(k = 0; k < end - start; k++) {
428           raw_line[k] = raw_line[k + start];
429         }
430         end -= start;
431         eol -= start;
432         start = 0;
433         end += fread(raw_line + end, sizeof(char), buffer_size - end, file);
434         while(eol < end && raw_line[eol] != '\n') eol++;
435       }
436
437       /* The end of the line is the buffer size, which means the line is
438          too long */
439
440       if(eol == buffer_size) {
441         raw_line[buffer_size - 1] = '\0';
442         fprintf(stderr, "Selector: Line too long (max is %d characters):\n",
443                 buffer_size);
444         fprintf(stderr, raw_line);
445         fprintf(stderr, "\n");
446         exit(EXIT_FAILURE);
447       }
448
449       /* If we got a line, we replace the carriage return by a \0 to
450          finish the string */
451
452       raw_line[eol] = '\0';
453
454       /* here we process the line */
455
456       line_number++;
457
458       if(debug) {
459         printf("%s:%d \"%s\"\n",
460                configuration_file, line_number, raw_line + start);
461       }
462
463       evaluate_one_configuration_line(raw_line + start, line_number);
464
465       start = eol + 1;
466     }
467
468   }
469
470   /******************************************************************/
471
472   if(nb_temperature_thresholds == 0) {
473     fprintf(stderr, "No temperature threshold was provided.\n");
474     exit(EXIT_FAILURE);
475   }
476
477   if(nb_file_thermal == 0) {
478     fprintf(stderr, "No thermal file was provided.\n");
479     exit(EXIT_FAILURE);
480   }
481
482   if(file_fan == 0){
483     fprintf(stderr, "No fan file was provided.\n");
484     exit(EXIT_FAILURE);
485   }
486
487   for(i = 0; i < nb_file_thermal; i++) {
488     file_thermal_fd[i] = open(file_thermal[i], O_RDONLY);
489     if(file_thermal_fd[i] < 0) {
490       fprintf(stderr, "Can not open %s for reading (%s).\n",
491               file_thermal[i], strerror(errno));
492       exit(EXIT_FAILURE);
493     }
494   }
495
496   file_fan_fd = open(file_fan, O_WRONLY);
497
498   if(file_fan_fd < 0) {
499     fprintf(stderr, "Can not open %s for writing (%s).\n",
500             file_fan, strerror(errno));
501     exit(EXIT_FAILURE);
502   }
503
504   /******************************************************************/
505
506   if(debug) {
507     for(t = 0; t < nb_file_thermal; t++) {
508       printf("file_thermal[%d] %s\n", t, file_thermal[t]);
509     }
510
511     printf("file_fan %s\n", file_fan);
512
513     for(t = 0; t < nb_temperature_thresholds; t++) {
514       printf("temperature_thresholds[%d] = %d speed_names[%d] = \"%s\"\n",
515              t, temperature_thresholds[t],
516              t, speed_names[t]);
517     }
518   }
519
520   /******************************************************************/
521
522   while(1) {
523
524     int temperature = -1;
525
526     if(debug) {
527       printf("Temperature:");
528     }
529
530     for(i = 0; i < nb_file_thermal; i++) {
531       lseek(file_thermal_fd[i], 0, SEEK_SET);
532       int size = read(file_thermal_fd[i], buffer, buffer_size);
533
534       if(size > 0 && size < buffer_size) {
535         buffer[size] = '\0';
536
537         char *s = buffer;
538
539         while(*s) {
540           while(*s && *s != '-' && (*s < '0') || (*s > '9')) s++;
541
542           if(*s) {
543             int t = 0, sign = 1;
544             if(*s == '-') {
545               sign = -1;
546               s++;
547             }
548
549             while(*s >= '0' && *s <= '9') {
550               t = t * 10 + (*s - '0');
551               s++;
552             }
553
554             t *= sign;
555             if(debug) {
556               printf(" %d", t);
557             }
558
559             /* So that we can deal with the new files where the
560                temperature are in 1/1000th of C */
561             if(t > 1000) t /= 1000;
562
563             if(t > temperature) temperature = t;
564           }
565         }
566
567         if(debug) {
568           if(i < nb_file_thermal - 1)
569             printf(" /");
570           else
571             printf("\n");
572         }
573       } else {
574         if(size == 0) {
575           fprintf(stderr, "Nothing to read in %d.\n", file_thermal[i]);
576         } else if(size < 0) {
577           fprintf(stderr, "Error while reading %s (%s).\n",
578                   file_thermal[i], strerror(errno));
579         } else {
580           fprintf(stderr, "Error while reading %s (too large).\n",
581                   file_thermal[i]);
582         }
583         exit(EXIT_FAILURE);
584       }
585     }
586
587     if(temperature < 0) {
588       fprintf(stderr, "Could not read a meaningful temperature.\n");
589       exit(EXIT_FAILURE);
590     }
591
592     nb_rounds_since_last_change++;
593
594     int new_level = last_level;
595
596     int new_level_up = nb_temperature_thresholds - 1;
597     while(new_level_up > 0 &&
598           temperature < temperature_thresholds[new_level_up]) {
599       new_level_up--;
600     }
601
602     if(new_level_up > last_level) {
603       new_level = new_level_up;
604     } else {
605       int new_level_down = nb_temperature_thresholds - 1;
606       while(new_level_down > 0 &&
607             temperature <
608             temperature_thresholds[new_level_down] - down_temperature_delta)
609         new_level_down--;
610       if(new_level_down < last_level &&
611          nb_rounds_since_last_change * polling_delay >=
612          minimum_delay_to_go_down) {
613         new_level = new_level_down;
614       }
615     }
616
617     /* We set it every time, even when there is no change, to handle
618        when the level has been modified somewhere else (for instance
619        when coming back from suspend). */
620
621     set_fan_level(file_fan_fd, new_level, nb_temperature_thresholds - 1);
622
623     if(new_level != last_level) {
624       nb_rounds_since_last_change = 0;
625       last_level = new_level;
626       if(debug) {
627         printf("Temperature is %dC setting the fan level to %d (%s).\n",
628                temperature,
629                new_level,
630                speed_names[new_level]);
631       }
632     }
633
634     sleep(polling_delay);
635   }
636 }