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