Busybox: Upgrade to 1.21.1 (stable). lsof active.
[tomato.git] / release / src / router / sd-idle / sd-idle.c
blob9081b08d66b45861ee7e7d18e4816086b52ade11
1 /*
2 * sd-idle-2.6.c
4 * Copyright (C) 2010 Jeff Gibbons All Rights Reserved
5 *
6 * Author: Jeff Gibbons aka karog
7 * Date: Oct 1, 2010
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 3 of the License, or
12 * (at your option) any later version. See the GNU General Public License
13 * for more details at:
15 * http://www.gnu.org/licenses/gpl.html
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
21 * This program manages devices /dev/sdX 'a'<=X<='z' watching for idleness
22 * and spinning them down. It runs as a daemon. For usage do:
24 * sd-idle-2.6 --help
26 * Requires linux 2.6.
29 #define _GNU_SOURCE
31 #include <errno.h>
32 #include <fcntl.h>
33 #include <stdarg.h>
34 #include <stdint.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <syslog.h>
39 #include <scsi/scsi.h>
40 #include <scsi/sg.h>
41 #include <sys/ioctl.h>
42 #include <sys/utsname.h>
43 #include <time.h>
44 #include <unistd.h>
46 #define VERSION "1.00"
48 #define IDLETIME_DEFAULT 900
49 #define IDLETIME_MIN 300
51 #define CHECKTIME_DEFAULT 30
52 #define CHECKTIME_MIN 5
54 // log to stdout if non-zero else to syslog
55 static
56 int
57 _log_stdout;
59 // time in seconds a disk must be idle before being spun down
60 static
61 int
62 _idletime = IDLETIME_DEFAULT;
64 // time in seconds disks have been idle
65 static
66 int
67 _idletimes [ 26 ];
69 // time in seconds to sleep between idle checks
70 static
71 int
72 _checktime = CHECKTIME_DEFAULT;
74 // time in seconds between transitions, up and down
75 static
76 time_t
77 _transtimes [ 26 ];
79 #define TRANSTIME_BUF_SIZE 128
81 #define _bit_clear( bits, b ) bits &= ~( 1 << ( b ) )
82 #define _bit_set( bits, b ) bits |= ( 1 << ( b ) )
83 #define _is_bit_clear( bits, b ) ( 0 == ( bits & ( 1 << ( b ) ) ) )
84 #define _is_bit_set( bits, b ) ( 0 != ( bits & ( 1 << ( b ) ) ) )
86 // bits indicating if a disk is being managed
87 static
88 uint32_t
89 _managing = -1;
91 // bits indicating if a disk is spinning
92 static
93 uint32_t
94 _spinning = -1;
96 // I/O stats for disks
97 static
98 char
99 _diskstats [ 26 ] [ 160 ];
101 static void _check_linux_version ( void );
103 static void _parse_options ( int argc, char * argv [ ] );
105 static void _manage ( void );
107 static void _log ( int level, const char * fmt, ... );
110 main (
111 int argc,
112 char * argv [ ] )
114 openlog ( basename ( argv [ 0 ] ), LOG_PID, LOG_USER );
116 _check_linux_version();
118 _parse_options ( argc, argv );
120 daemon ( 0, 0 );
122 _log ( LOG_INFO, "initialized" );
124 _manage ( );
126 return EXIT_SUCCESS;
129 static
130 void
131 _log (
132 int level,
133 const char * fmt,
134 ... )
136 va_list arg_list;
138 va_start ( arg_list, fmt );
140 if ( _log_stdout )
141 vprintf ( fmt, arg_list );
142 else
143 vsyslog ( level, fmt, arg_list );
145 va_end ( arg_list );
148 static
149 void
150 _check_linux_version (
151 void )
153 struct utsname name;
155 if ( 0 > uname ( &name ) )
157 _log ( LOG_ERR, "Failure to get linux version number: %s", strerror ( errno ) );
158 exit ( EXIT_FAILURE );
161 switch ( name.release [ 2 ] )
163 case '6':
164 return;
165 default:
166 _log ( LOG_ERR, "Wrong linux version: %s; must be 2.6; exiting", name.release );
167 exit ( EXIT_FAILURE );
171 static
172 void
173 _usage (
174 char * progname,
175 int exit_status )
177 progname = basename ( progname );
179 _log ( LOG_INFO, "Usage: ( runs as a daemon )\n" );
180 _log ( LOG_INFO, " %s [ -d devices ] [ -i idletime ] [ -c checktime ] [ -h --help ] [ -v --version ]\n", progname);
181 _log ( LOG_INFO, " -d [a-z]+ include where a => /dev/sda, b => /dev/sdb (default is all disks)\n" );
182 _log ( LOG_INFO, " ![a-z]+ exclude\n" );
183 _log ( LOG_INFO, " -i n n seconds a disk must be idle to spin it down (default %d, min %d)\n",
184 IDLETIME_DEFAULT, IDLETIME_MIN );
185 _log ( LOG_INFO, " -c n n seconds to sleep between idle checks (default %d, min %d)\n",
186 CHECKTIME_DEFAULT, CHECKTIME_MIN);
187 _log ( LOG_INFO, " -h --help usage\n" );
188 _log ( LOG_INFO, " -v --version version\n" );
189 _log ( LOG_INFO, " for example:\n" );
190 _log ( LOG_INFO, " %s will manage all disks with default times\n", progname );
191 _log ( LOG_INFO, " %s -d bc will manage /dev/sdb, /dev/sdc with default times\n", progname );
192 _log ( LOG_INFO, " %s -d !bc will manage all disks except /dev/sdb, /dev/sdc with default times\n", progname );
193 _log ( LOG_INFO, " %s -i 600 will manage all disks spinning down after 600 seconds or 10 minutes\n", progname );
195 exit ( exit_status );
198 static
199 char *
200 _get_param (
201 int argc,
202 char * argv [ ],
203 int a,
204 char * option )
206 if ( a < argc )
207 return argv [ a ];
208 _log ( LOG_ERR, "Missing parameter for option: %s; exiting", option );
209 exit ( EXIT_FAILURE );
212 static
213 void
214 _set_disks (
215 char * disks )
217 int include = 1;
219 switch ( *disks )
221 case 0: // empty so default to all
222 return;
223 case '!': // exclude
224 include = 0;
225 disks++;
226 break;
227 default: // include
228 _managing = 0;
229 break;
232 for ( ; 0 != *disks; disks++ )
234 if ( ( *disks < 'a' ) || ( 'z' < *disks ) )
236 _log ( LOG_WARNING, "Invalid disk: %c; must be among [a-z]; ignoring", *disks );
237 continue;
239 if ( include )
240 _bit_set ( _managing, *disks - 'a' );
241 else
242 _bit_clear ( _managing, *disks - 'a' );
245 if ( 0 == _managing )
247 _log ( LOG_ERR, "No disks specified to be managed; exiting" );
248 exit ( 1 );
252 static
253 void
254 _set_idletime (
255 char * idletime )
257 char * endp;
259 _idletime = strtol ( idletime, &endp, 10 );
260 if ( ( 0 != *idletime ) && ( 0 == *endp ) )
262 if ( _idletime < IDLETIME_MIN )
264 _log ( LOG_WARNING, "IDLETIME set too small which is bad for disks: %d; setting to minimum %d",
265 _idletime, IDLETIME_MIN );
266 _idletime = IDLETIME_MIN;
268 return;
271 _log ( LOG_WARNING, "Invalid idletime: %s; using default %d", idletime, IDLETIME_DEFAULT );
272 _idletime = IDLETIME_DEFAULT;
275 static
276 void
277 _set_checktime (
278 char * checktime )
280 char * endp;
281 _checktime = strtol ( checktime, &endp, 10 );
282 if ( ( 0 != *checktime ) && ( 0 == *endp ) )
284 if ( _checktime < CHECKTIME_MIN )
286 _log ( LOG_WARNING, "CHECKTIME set too small: %d; setting to minimum %d",
287 _checktime, CHECKTIME_MIN );
288 _checktime = CHECKTIME_MIN;
290 return;
293 _log ( LOG_WARNING, "Invalid checktime: %s; using default %d", checktime, CHECKTIME_DEFAULT );
294 _checktime = CHECKTIME_DEFAULT;
297 static
298 void
299 _parse_options (
300 int argc,
301 char * argv [ ] )
303 int a;
305 for ( a = 1; a < argc; a++ )
307 if ( !strcmp ( "-h", argv [ a ] ) || !strcmp ( "--help", argv [ a ] ) )
309 _log_stdout = 1;
310 _usage ( argv [ 0 ], EXIT_SUCCESS );
313 if ( !strcmp ( "-v", argv [ a ] ) || !strcmp ( "--version", argv [ a ] ) )
315 _log_stdout = 1;
316 _log ( LOG_INFO, "%s version %s\n", basename ( argv [ 0 ] ), VERSION );
317 exit ( EXIT_SUCCESS );
321 for ( a = 1; a < argc; a++ )
323 if ( !strcmp ( "-d", argv [ a ] ) )
325 _set_disks ( _get_param ( argc, argv, ++a, "-d" ) );
326 continue;
328 if ( !strcmp ( "-i", argv [ a ] ) )
330 _set_idletime ( _get_param ( argc, argv, ++a, "-i" ) );
331 continue;
333 if ( !strcmp ( "-c", argv [ a ] ) )
335 _set_checktime ( _get_param ( argc, argv, ++a, "-c" ) );
336 continue;
339 _log ( LOG_ERR, "Unknown option: %s; exiting", argv [ a ] );
340 _usage ( argv [ 0 ], EXIT_FAILURE );
344 static
345 void
346 _disk_stop (
347 int disk )
349 sg_io_hdr_t sg_io_hdr;
350 unsigned char sense_data [ 255 ];
351 char device [ 16 ];
352 int fd;
354 memset( &sg_io_hdr, 0, sizeof ( sg_io_hdr ) );
356 sg_io_hdr.interface_id = 'S';
357 sg_io_hdr.dxfer_direction = SG_DXFER_NONE;
359 // STOP cmd
360 sg_io_hdr.cmdp = ( unsigned char [ ] ) { START_STOP, 0x0, 0x0, 0x0, 0x0, 0x0 };
361 sg_io_hdr.cmd_len = 6;
363 // sense data
364 sg_io_hdr.sbp = sense_data;
365 sg_io_hdr.mx_sb_len = sizeof ( sense_data );
367 sprintf ( device, "/dev/sd%c", 'a' + disk );
369 if ( 0 > (fd = open ( device, O_RDONLY ) ) )
371 _log ( LOG_WARNING, "Failure to open %s for spinning down: %s", device, strerror ( errno ) );
372 return;
375 if ( 0 > ioctl ( fd, SG_IO, &sg_io_hdr ) )
377 _log ( LOG_WARNING, "Failure to spin down %s: %s", device, strerror ( errno ) );
378 _log ( LOG_WARNING, "Sense data: %s", sense_data );
381 if ( 0 != sg_io_hdr.status )
382 _log ( LOG_WARNING, "Non-zero status spinning down %s: %d", device, sg_io_hdr.status );
384 close ( fd );
387 static
388 char *
389 _transition_message (
390 int disk,
391 char * direction,
392 char * buf)
394 time_t then, now;
395 int days, hours, mins, secs;
396 char * bufp;
398 bufp = buf + sprintf ( buf, "spinning %s /dev/sd%c", direction, 'a' + disk );
400 then = _transtimes [ disk ];
401 _transtimes [ disk ] = time ( &now );
403 // if this is the first time, no interval to include in message
404 if ( 0 == then )
405 return buf;
407 bufp += sprintf ( bufp, " after " );
409 secs = now - then;
410 days = secs / 86400;
411 secs -= days * 86400;
412 hours = secs / 3600;
413 secs -= hours * 3600;
414 mins = secs / 60;
415 secs -= mins * 60;
416 if ( days ) bufp += sprintf ( bufp, "%d days " , days );
417 if ( hours ) bufp += sprintf ( bufp, "%d hours ", hours );
418 if ( mins ) bufp += sprintf ( bufp, "%d mins " , mins );
419 if ( secs ) bufp += sprintf ( bufp, "%d secs " , secs );
420 return buf;
423 static
424 void
425 _disk_idle (
426 int disk )
428 char msg_buf [ TRANSTIME_BUF_SIZE ];
430 // increment idle time
431 _idletimes [ disk ] += _checktime;
433 // if was not spinning or not enough ilde time
434 if ( _is_bit_clear ( _spinning, disk ) || ( _idletime > _idletimes [ disk ] ) )
435 return;
437 _log ( LOG_INFO, "%s", _transition_message ( disk, "down", msg_buf ) );
439 _disk_stop ( disk );
441 _bit_clear ( _spinning, disk );
444 static
445 void
446 _disk_active (
447 int disk,
448 char * diskstats_line )
450 char msg_buf [ TRANSTIME_BUF_SIZE ];
452 // if was not spinning, inform spinning up
453 if ( _is_bit_clear ( _spinning, disk ) )
455 _log ( LOG_INFO, "%s", _transition_message ( disk, "up", msg_buf ) );
456 _bit_set ( _spinning, disk );
459 _idletimes [ disk ] = 0;
460 strcpy ( _diskstats [ disk ], diskstats_line );
463 static
464 void
465 _manage_diskstats_line (
466 char * diskstats_line )
468 char * sd;
469 char * io;
470 int disk;
471 int i;
473 // look for " sdX " with 'a' <= X <= 'z'
474 if ( NULL == ( sd = strstr ( diskstats_line, " sd" ) ) || ( ' ' != sd [ 4 ] ) ||
475 ( ( disk = sd [ 3 ] ) < 'a' ) || ( 'z' < disk ) )
476 return;
478 disk -= 'a';
479 if ( _is_bit_clear ( _managing, disk ) )
480 return;
482 if ( strlen ( diskstats_line ) >= sizeof ( _diskstats [ 0 ] ) )
484 _log ( LOG_WARNING, "/proc/diskstats line too long: %s; ignoring", diskstats_line );
485 return;
488 // remove prefix through device name
489 diskstats_line = sd + 5;
491 // find I/O in progress
492 for ( i = 0, io = diskstats_line; ( i < 8 ) && ( NULL != ( io = strchr ( ++io, ' ' ) ) ); i++ );
493 if ( NULL == io )
495 _log ( LOG_WARNING, "Missing I/O in progress stat for device: sd%c", 'a' + disk );
496 return;
499 // check for idleness
500 if ( ( '0' == *++io ) && !strcmp ( _diskstats [ disk ], diskstats_line ) )
501 _disk_idle ( disk );
502 else
503 _disk_active ( disk, diskstats_line );
506 static
507 void
508 _manage (
509 void )
511 FILE * proc_diskstats;
512 char diskstats [ 256 ];
514 for ( ; ; )
516 // open /proc/diskstats
517 proc_diskstats = fopen ( "/proc/diskstats", "r" );
518 if ( NULL == proc_diskstats )
520 _log ( LOG_ERR, "Failure to open /proc/diskstats: %d - %s", errno, strerror ( errno ) );
521 exit ( EXIT_FAILURE );
524 // process each line
525 while ( NULL != fgets ( diskstats, sizeof ( diskstats ), proc_diskstats ) )
526 _manage_diskstats_line ( diskstats );
528 fclose ( proc_diskstats );
530 sleep ( _checktime );