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