1/* scsi-idle
2 * Copyright (C) 19?? Christer Weinigel
3 * Copyright (C) 1999 Trent Piepho <xyzzy@speakeasy.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 */
19
20/* Changelog:
21 * August 25, 2002:
22 * Daniel Sterling (dan@lost-habit.com):
23   added #include <unistd.h>
24   removed unused variables
25   down variable set when hd is down instead of vice versa
26   don't go into daemon mode if timeout is 30 seconds or less
27   warn if timeout is less than 60 seconds.... that's still very low--
28    I recommend timeouts of at least 20 minutes
29   fixed daemon to spin down drive more than just one time (oops!!)
30 */
31
32#include <stdio.h>
33#include <stdlib.h>
34#include <signal.h>
35#include <fcntl.h>
36#include <unistd.h>
37#include <sys/ioctl.h>
38#include <sys/stat.h>
39#include <linux/major.h>
40#include <linux/kdev_t.h>
41#include <scsi/scsi_ioctl.h>
42
43/* Kernel 2.0 and 2.2 differ on how SCSI_DISK_MAJOR works */
44#ifdef SCSI_DISK0_MAJOR
45#define IS_SCSI_DISK(rdev)	SCSI_DISK_MAJOR(MAJOR(rdev))
46#else
47#define IS_SCSI_DISK(rdev)	(MAJOR(rdev)==SCSI_DISK_MAJOR)
48#endif
49
50#ifndef SD_IOCTL_IDLE
51#define SD_IOCTL_IDLE 4746	/* get idle time */
52#endif
53
54#define DEBUG 0
55
56char lockfn[64];
57
58void handler()
59{
60    unlink(lockfn);
61    exit(0);
62}
63
64int main(int argc, char *argv[])
65{
66    int fd;
67    int down = 0;
68    struct stat statbuf;
69
70    if (argc < 2 || argc > 3) {
71	fprintf(stderr,
72		"usage: %s device [timeout]\n"
73		"  where timeout is the time until motor off in seconds\n"
74		"  to idle the disk immediately, use a timeout of zero\n",
75		argv[0]);
76	exit(1);
77    }
78    if ((fd = open(argv[1], O_RDWR)) < 0) {
79	perror(argv[1]);
80	exit(1);
81    }
82    if ((fstat(fd, &statbuf)) < 0) {
83	perror(argv[1]);
84	close(fd);
85	exit(1);
86    }
87    if (!S_ISBLK(statbuf.st_mode)
88	|| !IS_SCSI_DISK(statbuf.st_rdev)) {
89	fprintf(stderr, "%s is not a SCSI disk device\n", argv[1]);
90	close(fd);
91	exit(1);
92    }
93    if (argc == 2) {
94	long last;
95
96	/* Report idle time */
97
98	if ((last = ioctl(fd, SD_IOCTL_IDLE, &last)) < 0) {
99	    perror(argv[1]);
100	    close(fd);
101	    exit(1);
102	}
103	if (last == 0)
104	    printf("%s has not been accessed yet\n", argv[1]);
105	else
106	    printf("%s has been idle for %i second%s\n", argv[1],
107		 (int) last, (last == 1 ? "" : "s"));
108    } else {
109	long timeout;
110
111	if ((timeout = atol(argv[2])) < 0) {
112	    fprintf(stderr, "timeout may not be negative\n");
113	    close(fd);
114	    exit(1);
115	}
116	if (timeout <= 30) {
117	    if (timeout != 0) fprintf(stderr,
118	        "Really low timeouts are a bad, "
119	        "bad idea.\nI'll spin down immediately for you. Use a higher "
120	        "timeout for daemon mode.");
121	    /* Spin down disk immediately */
122
123	    if (ioctl(fd, SCSI_IOCTL_STOP_UNIT) < 0) {
124		perror(argv[1]);
125		close(fd);
126		exit(1);
127	    }
128	} else {
129	    FILE *fp;
130	    int dev;
131	    long last;
132	    long llast = 0;
133	    pid_t pid = 0;
134
135	    /* Spin down disk after an idle period */
136	    if (timeout < 60)
137		fprintf(stderr,
138			"Warning: timeouts less than 60 seconds are really "
139			"not such a good idea..\n");
140
141	    /* This is ugly, but I guess there is no portable way to do this */
142	    dev = ((statbuf.st_rdev >> 4) & 15) + 'a';
143
144	    sprintf(lockfn, "/var/run/scsi-idle.sd%c.pid", dev);
145
146	    if ((fp = fopen(lockfn, "r")) != NULL) {
147		fscanf(fp, "%i", &pid);
148		fclose(fp);
149
150		kill(pid, SIGTERM);
151
152		unlink(lockfn);
153	    }
154	    switch (fork()) {
155	    case -1:
156		perror("fork failed");
157		close(fd);
158		exit(1);
159
160	    case 0:
161		signal(SIGINT, handler);
162		signal(SIGTERM, handler);
163
164		if ((fp = fopen(lockfn, "w")) == NULL) {
165		    perror(lockfn);
166		    close(fd);
167		    exit(1);
168		}
169		fprintf(fp, "%i\n", (int)getpid());
170		fclose(fp);
171
172		nice(10);
173
174/* main daemon loop.
175 * obtain idle time.
176 * have we shut down the drive already?
177   * IF yes: has the idle time reset since the last check?
178     * IF yes: reset our drive status variable
179 * IF the drive is running AND idle time > user-specified timeout,
180   send STOP_UNIT and set our status variable.
181 * IF idle time < timeout, sleep until idle time would be > timeout.
182 * ELSE sleep for timeout.
183 */
184		for (;;) {
185		    if ((last = ioctl(fd, SD_IOCTL_IDLE, &last)) < 0) {
186			perror(argv[1]);
187			close(fd);
188			exit(1);
189		    }
190#if DEBUG
191		    if (last == 0)
192			printf("%s has not been accessed\n", argv[1]);
193		    else
194			printf("%s has been idle for %d second%s\n", argv[1],
195			       last, (last == 1 ? "" : "s"));
196#endif
197		    if (down == 1 && llast >= last)
198		        down = 0;
199		    llast = last;
200		    if (last >= timeout) {
201			if (down == 0 &&
202			(ioctl(fd, SCSI_IOCTL_STOP_UNIT) != 0)) {
203			    perror(argv[1]);
204			    close(fd);
205			    exit(1);
206			}
207			last = 0;
208			down = 1;
209		    }
210#if DEBUG
211		    printf("sleeping for %d seconds\n", timeout - last);
212#endif
213		    sleep(timeout - last);
214		}
215		/* not reached */
216
217	    default:
218		printf("%s: idle daemon started, timeout is %ld seconds\n",
219		       argv[1], timeout);
220		/* Ok, done */
221		break;
222	    }
223	}
224    }
225
226    close(fd);
227    exit(0);
228}
229