4 * Copyright (C) 2010 Jeff Gibbons All Rights Reserved
6 * Author: Jeff Gibbons aka karog
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:
39 #include <scsi/scsi.h>
41 #include <sys/ioctl.h>
42 #include <sys/utsname.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
59 // time in seconds a disk must be idle before being spun down
62 _idletime
= IDLETIME_DEFAULT
;
64 // time in seconds disks have been idle
69 // time in seconds to sleep between idle checks
72 _checktime
= CHECKTIME_DEFAULT
;
74 // time in seconds between transitions, up and down
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
91 // bits indicating if a disk is spinning
96 // I/O stats for disks
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
, ... );
114 openlog ( basename ( argv
[ 0 ] ), LOG_PID
, LOG_USER
);
116 _check_linux_version();
118 _parse_options ( argc
, argv
);
122 _log ( LOG_INFO
, "initialized" );
138 va_start ( arg_list
, fmt
);
141 vprintf ( fmt
, arg_list
);
143 vsyslog ( level
, fmt
, arg_list
);
150 _check_linux_version (
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 ] )
166 _log ( LOG_ERR
, "Wrong linux version: %s; must be 2.6; exiting", name
.release
);
167 exit ( EXIT_FAILURE
);
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
);
208 _log ( LOG_ERR
, "Missing parameter for option: %s; exiting", option
);
209 exit ( EXIT_FAILURE
);
221 case 0: // empty so default to all
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
);
240 _bit_set ( _managing
, *disks
- 'a' );
242 _bit_clear ( _managing
, *disks
- 'a' );
245 if ( 0 == _managing
)
247 _log ( LOG_ERR
, "No disks specified to be managed; exiting" );
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
;
271 _log ( LOG_WARNING
, "Invalid idletime: %s; using default %d", idletime
, IDLETIME_DEFAULT
);
272 _idletime
= IDLETIME_DEFAULT
;
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
;
293 _log ( LOG_WARNING
, "Invalid checktime: %s; using default %d", checktime
, CHECKTIME_DEFAULT
);
294 _checktime
= CHECKTIME_DEFAULT
;
305 for ( a
= 1; a
< argc
; a
++ )
307 if ( !strcmp ( "-h", argv
[ a
] ) || !strcmp ( "--help", argv
[ a
] ) )
310 _usage ( argv
[ 0 ], EXIT_SUCCESS
);
313 if ( !strcmp ( "-v", argv
[ a
] ) || !strcmp ( "--version", argv
[ a
] ) )
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" ) );
328 if ( !strcmp ( "-i", argv
[ a
] ) )
330 _set_idletime ( _get_param ( argc
, argv
, ++a
, "-i" ) );
333 if ( !strcmp ( "-c", argv
[ a
] ) )
335 _set_checktime ( _get_param ( argc
, argv
, ++a
, "-c" ) );
339 _log ( LOG_ERR
, "Unknown option: %s; exiting", argv
[ a
] );
340 _usage ( argv
[ 0 ], EXIT_FAILURE
);
349 sg_io_hdr_t sg_io_hdr
;
350 unsigned char sense_data
[ 255 ];
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
;
360 sg_io_hdr
.cmdp
= ( unsigned char [ ] ) { START_STOP
, 0x0, 0x0, 0x0, 0x0, 0x0 };
361 sg_io_hdr
.cmd_len
= 6;
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
) );
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
);
389 _transition_message (
395 int days
, hours
, mins
, secs
;
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
407 bufp
+= sprintf ( bufp
, " after " );
411 secs
-= days
* 86400;
413 secs
-= hours
* 3600;
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
);
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
] ) )
437 _log ( LOG_INFO
, "%s", _transition_message ( disk
, "down", msg_buf
) );
441 _bit_clear ( _spinning
, 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
);
465 _manage_diskstats_line (
466 char * diskstats_line
)
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
) )
479 if ( _is_bit_clear ( _managing
, disk
) )
482 if ( strlen ( diskstats_line
) >= sizeof ( _diskstats
[ 0 ] ) )
484 _log ( LOG_WARNING
, "/proc/diskstats line too long: %s; ignoring", diskstats_line
);
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
++ );
495 _log ( LOG_WARNING
, "Missing I/O in progress stat for device: sd%c", 'a' + disk
);
499 // check for idleness
500 if ( ( '0' == *++io
) && !strcmp ( _diskstats
[ disk
], diskstats_line
) )
503 _disk_active ( disk
, diskstats_line
);
511 FILE * proc_diskstats
;
512 char diskstats
[ 256 ];
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
);
525 while ( NULL
!= fgets ( diskstats
, sizeof ( diskstats
), proc_diskstats
) )
526 _manage_diskstats_line ( diskstats
);
528 fclose ( proc_diskstats
);
530 sleep ( _checktime
);