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