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