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