scsi-spin.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. /*
  2. File: scsi-spin.c
  3. A simple program to manually spin up and down a scsi device.
  4. Copyright 1998 Rob Browning <rlb@cs.utexas.edu>
  5. Copyright 2001 Eric Delaunay <delaunay@debian.org>
  6. This source is covered by the terms the GNU Public License.
  7. Some of the original code came from
  8. The Linux SCSI programming HOWTO
  9. Heiko Ei<DF>feldt heiko@colossus.escape.de
  10. v1.5, 7 May 1996
  11. */
  12. #include <stdlib.h>
  13. #include <stdint.h>
  14. #include <stdio.h>
  15. #include <unistd.h>
  16. #include <getopt.h>
  17. #include <string.h>
  18. #include <fcntl.h>
  19. #include <errno.h>
  20. #include <mntent.h>
  21. #include <sys/ioctl.h>
  22. #include <scsi/sg.h>
  23. #include <scsi/scsi.h>
  24. #include <scsi/scsi_ioctl.h>
  25. #include <linux/major.h>
  26. #include <sys/sysmacros.h>
  27. #include <sys/stat.h>
  28. #include <sys/types.h>
  29. #define SCSI_DISK_MAJOR(M) ((M) == SCSI_DISK0_MAJOR || \
  30. ((M) >= SCSI_DISK1_MAJOR && \
  31. (M) <= SCSI_DISK7_MAJOR) || \
  32. ((M) >= SCSI_DISK8_MAJOR && \
  33. (M) <= SCSI_DISK15_MAJOR))
  34. #define SCSI_BLK_MAJOR(M) \
  35. (SCSI_DISK_MAJOR(M) || \
  36. (M) == SCSI_CDROM_MAJOR)
  37. /* define USE_SG_IO to send commands using scsi generic interface
  38. */
  39. #define USE_SG_IO
  40. #ifdef USE_SG_IO
  41. int opt_oldioctl = 0;
  42. int opt_verbose = 0;
  43. const char* SENSE_KEY_STR[16] = {
  44. "NO SENSE",
  45. "RECOVERED ERROR",
  46. "NOT READY",
  47. "MEDIUM ERROR",
  48. "HARDWARE ERROR",
  49. "ILLEGAL REQUEST",
  50. "UNIT ATTENTION",
  51. "DATA PROJECT",
  52. "BLANK CHECK",
  53. "VENDOR-SPECIFIC",
  54. "COPY ARBORTED",
  55. "ABORTED COMMAND",
  56. "EQUAL",
  57. "VOLUME OVERFLOW",
  58. "MISCOMPARED",
  59. "RESERVED"
  60. };
  61. /* process a complete SCSI cmd. Use the generic SCSI interface. */
  62. static int handle_SCSI_cmd(const int fd,
  63. const unsigned cmd_len, /* command length */
  64. unsigned char *cmd, /* command buffer */
  65. const unsigned in_size, /* input data size */
  66. const unsigned out_size, /* output data size */
  67. unsigned char *io_buff, /* i/o buffer */
  68. unsigned sense_size, /* sense buf length */
  69. unsigned char* sense_buff, /* sense buffer */
  70. const unsigned timeout /* timeout in s */
  71. ) {
  72. ssize_t status = 0;
  73. int k, err;
  74. sg_io_hdr_t sg_hdr;
  75. unsigned char sense[16];
  76. /* safety checks */
  77. if (!cmd_len) return -1; /* need a cmd_len != 0 */
  78. if (in_size > 0 && io_buff == NULL) return -1; /* need an input buffer != NULL */
  79. /* generic SCSI device header construction */
  80. memset(&sg_hdr, 0, sizeof(sg_hdr));
  81. sg_hdr.interface_id = 'S';
  82. sg_hdr.dxfer_direction = SG_DXFER_NONE;
  83. sg_hdr.cmd_len = cmd_len;
  84. sg_hdr.cmdp = cmd;
  85. sg_hdr.dxfer_len = in_size;
  86. sg_hdr.dxferp = io_buff;
  87. sg_hdr.timeout = (timeout ? timeout : 2)*1000; /* timeout in ms */
  88. if (sense_buff == NULL) {
  89. sense_buff = sense;
  90. sense_size = sizeof(sense);
  91. }
  92. sg_hdr.mx_sb_len = sense_size;
  93. sg_hdr.sbp = sense_buff;
  94. if (opt_verbose > 1) {
  95. fprintf( stderr, " cmd = " );
  96. for( k = 0 ; k < cmd_len ; k++ )
  97. fprintf( stderr, " %02x", cmd[k] );
  98. fputc( '\n', stderr );
  99. }
  100. /* send command */
  101. status = ioctl( fd, SG_IO, &sg_hdr );
  102. if (status < 0 || sg_hdr.masked_status == CHECK_CONDITION) {
  103. /* some error happened */
  104. fprintf( stderr, "SG_IO: status = 0x%x cmd = 0x%x\n",
  105. sg_hdr.status, cmd[0] );
  106. if (opt_verbose > 0) {
  107. fprintf( stderr, " sense = " );
  108. for( k = 0 ; k < sg_hdr.sb_len_wr ; k++ )
  109. fprintf( stderr, " %02x", sense_buff[k] );
  110. fputc( '\n', stderr );
  111. err = sense_buff[0] & 0x7f;
  112. if (err == 0x70 || err == 0x71) {
  113. fprintf( stderr, " (%s)\n", SENSE_KEY_STR[sense_buff[2] & 0xf] );
  114. }
  115. }
  116. perror("");
  117. }
  118. return status; /* 0 means no error */
  119. }
  120. #endif
  121. static void
  122. scsi_spin(const int fd, const int desired_state, const int load_eject, const int wait) {
  123. #ifdef USE_SG_IO
  124. if (! opt_oldioctl) {
  125. unsigned char cmdblk [6] =
  126. { START_STOP, /* command */
  127. (wait ? 0 : 1), /* lun(3 bits)/reserved(4 bits)/immed(1 bit) */
  128. 0, /* reserved */
  129. 0, /* reserved */
  130. (load_eject ? 2 : 0)
  131. | (desired_state ? 1 : 0), /* reserved(6)/LoEj(1)/Start(1)*/
  132. 0 };/* reserved/flag/link */
  133. if (handle_SCSI_cmd(fd, sizeof(cmdblk), cmdblk, 0, 0, NULL, 0, NULL, wait)) {
  134. fprintf( stderr, "start/stop failed\n" );
  135. exit(2);
  136. }
  137. return;
  138. }
  139. #endif
  140. int ret;
  141. if (desired_state != 0)
  142. ret = ioctl( fd, SCSI_IOCTL_START_UNIT );
  143. else
  144. ret = ioctl( fd, SCSI_IOCTL_STOP_UNIT );
  145. if (ret < 0)
  146. perror( "scsi_spin: ioctl" );
  147. }
  148. static void
  149. scsi_lock(const int fd, const int door_lock) {
  150. #ifdef USE_SG_IO
  151. if (! opt_oldioctl) {
  152. unsigned char cmdblk [6] =
  153. { ALLOW_MEDIUM_REMOVAL, /* command */
  154. 0, /* lun(3 bits)/reserved(5 bits) */
  155. 0, /* reserved */
  156. 0, /* reserved */
  157. (door_lock ? 1 : 0), /* reserved(7)/Prevent(1)*/
  158. 0 };/* control */
  159. if (handle_SCSI_cmd(fd, sizeof(cmdblk), cmdblk, 0, 0, NULL, 0, NULL, 2)) {
  160. fprintf( stderr, "lock/unlock failed\n" );
  161. exit(2);
  162. }
  163. return;
  164. }
  165. #endif
  166. int ret;
  167. if (door_lock != 0)
  168. ret = ioctl( fd, SCSI_IOCTL_DOORLOCK );
  169. else
  170. ret = ioctl( fd, SCSI_IOCTL_DOORUNLOCK );
  171. if (ret < 0)
  172. perror( "scsi_lock: ioctl" );
  173. }
  174. /* -- [ED] --
  175. * Check if the device has some of its partitions mounted.
  176. * The check is done by comparison between device major and minor numbers so it
  177. * even works when the device name of the mount point is not the same of the
  178. * one passed to scsi-spin (for example, scsidev creates device aliases under
  179. * /dev/scsi).
  180. */
  181. static int
  182. is_mounted( const char* device, int use_proc, int devmaj, int devmin )
  183. {
  184. struct mntent *mnt;
  185. struct stat devstat;
  186. int mounted = 0;
  187. struct {
  188. uint32_t dev_id;
  189. uint32_t host_unique_id;
  190. } scsi_dev_id, scsi_id;
  191. FILE *mtab;
  192. char *mtabfile = use_proc ? "/proc/mounts" : "/etc/mtab";
  193. if (devmaj == SCSI_GENERIC_MAJOR) {
  194. /* scsi-spin device arg is /dev/sgN */
  195. int fd = open( device, O_RDONLY );
  196. if (fd >= 0) {
  197. int ret = ioctl( fd, SCSI_IOCTL_GET_IDLUN, &scsi_dev_id );
  198. close( fd );
  199. if (ret < 0)
  200. return -1;
  201. }
  202. }
  203. /*printf("devid=%x\n",scsi_dev_id.dev_id);*/
  204. mtab = setmntent( mtabfile, "r" );
  205. if (mtab == NULL)
  206. return -1;
  207. while ((mnt = getmntent( mtab )) != 0) {
  208. char * mdev = mnt->mnt_fsname;
  209. if (stat( mdev, &devstat ) == 0) {
  210. int maj = major(devstat.st_rdev);
  211. int min = minor(devstat.st_rdev);
  212. if (SCSI_DISK_MAJOR(maj) && SCSI_DISK_MAJOR(devmaj)) {
  213. if (maj == devmaj && (min & ~15) == (devmin & ~15)) {
  214. mounted = 1;
  215. break;
  216. }
  217. }
  218. else if (devmaj == SCSI_GENERIC_MAJOR && SCSI_BLK_MAJOR(maj)) {
  219. /* scsi-spin device arg is /dev/sgN */
  220. int fd = open( mdev, O_RDONLY );
  221. if (fd >= 0) {
  222. int ret = ioctl( fd, SCSI_IOCTL_GET_IDLUN, &scsi_id );
  223. close( fd );
  224. /*printf("id=%x\n",scsi_id.dev_id);*/
  225. if (ret == 0 && scsi_id.dev_id == scsi_dev_id.dev_id) {
  226. /* same SCSI ID => same device */
  227. mounted = 1;
  228. break;
  229. }
  230. }
  231. }
  232. else if (maj == SCSI_CDROM_MAJOR && maj == devmaj && min == devmin) {
  233. mounted = 1;
  234. break;
  235. }
  236. }
  237. }
  238. endmntent( mtab );
  239. return mounted;
  240. }
  241. static void
  242. usage()
  243. {
  244. static char usage_string[] =
  245. "usage: scsi-spin {-u,-d} [-nfpe] device\n"
  246. " -u, --up spin up device.\n"
  247. " -d, --down spin down device.\n"
  248. " -v, --verbose[=n] verbose mode (1: normal, 2: debug).\n"
  249. #ifdef SG_IO
  250. " -e, --loej load (-u) or eject (-d) removable medium.\n"
  251. " -w, --wait=[n] wait the spin up/down operation to be completed\n"
  252. " (n is the number of seconds to timeout).\n"
  253. " -I, --oldioctl use legacy ioctl instead of SG I/O (-e,-w ignored).\n"
  254. #endif
  255. " -l, --lock prevent medium removal.\n"
  256. " -L, --unlock allow medium removal.\n"
  257. " -n, --noact do nothing but check if the device is in use.\n"
  258. " -f, --force force spinning up/down even if the device is in use.\n"
  259. " -p, --proc use /proc/mounts instead of /etc/mtab to do the check.\n"
  260. " device is one of /dev/sd[a-z], /dev/scd[0-9]* or /dev/sg[0-9]*.\n";
  261. fputs(usage_string, stderr);
  262. }
  263. int
  264. main(int argc, char *argv[])
  265. {
  266. int result = 0;
  267. int fd;
  268. int opt_up = 0;
  269. int opt_down = 0;
  270. int opt_loej = 0;
  271. int opt_wait = 0;
  272. int opt_force = 0;
  273. int opt_noact = 0;
  274. int opt_proc = 0;
  275. int opt_lock = 0;
  276. int opt_unlock = 0;
  277. struct option cmd_line_opts[] = {
  278. {"verbose", 2, NULL, 'v'},
  279. {"up", 0, NULL, 'u'},
  280. {"down", 0, NULL, 'd'},
  281. #ifdef SG_IO
  282. {"loej", 0, NULL, 'e'},
  283. {"wait", 2, NULL, 'w'},
  284. {"oldioctl", 0, NULL, 'I'},
  285. #endif
  286. {"lock", 0, NULL, 'l'},
  287. {"unlock", 0, NULL, 'L'},
  288. {"force", 0, NULL, 'f'},
  289. {"noact", 0, NULL, 'n'},
  290. {"proc", 0, NULL, 'p'},
  291. {0, 0, 0, 0},
  292. };
  293. char* endptr = "";
  294. char* device;
  295. struct stat devstat;
  296. char c;
  297. while((c = getopt_long(argc, argv, "vudewlLfnp", cmd_line_opts, NULL)) != EOF) {
  298. switch (c) {
  299. case 'v': opt_verbose = optarg ? strtol(optarg, &endptr, 10) : opt_verbose+1;
  300. if (*endptr) goto error;
  301. break;
  302. case 'u': opt_up = 1; break;
  303. case 'd': opt_down = 1; break;
  304. #ifdef SG_IO
  305. case 'e': opt_loej = 1; break;
  306. case 'w': opt_wait = optarg ? strtol(optarg, &endptr, 10) : opt_wait+1;
  307. if (*endptr) goto error;
  308. break;
  309. case 'I': opt_oldioctl = 1; break;
  310. #endif
  311. case 'f': opt_force = 1; break;
  312. case 'l': opt_lock = 1; break;
  313. case 'L': opt_unlock = 1; break;
  314. case 'n': opt_noact = 1; break;
  315. case 'p': opt_proc = 1; break;
  316. default:
  317. error:
  318. usage();
  319. exit(1);
  320. }
  321. }
  322. if(opt_up && opt_down) {
  323. fputs("scsi-spin: specified both --up and --down. "
  324. "Is this some kind of test?\n", stderr);
  325. exit(1);
  326. }
  327. if(opt_lock && opt_unlock) {
  328. fputs("scsi-spin: specified both --lock and --unlock. "
  329. "Is this some kind of test?\n", stderr);
  330. exit(1);
  331. }
  332. if (opt_oldioctl && (opt_wait || opt_loej)) {
  333. fputs("scsi-spin: -e or -w not working in old ioctl mode.\n", stderr);
  334. exit(1);
  335. }
  336. if(!(opt_up || opt_down || opt_lock || opt_unlock)) {
  337. fputs("scsi-spin: must specify --up, --down, --lock or --unlock at least.\n", stderr);
  338. exit(1);
  339. }
  340. if(optind != (argc - 1)) {
  341. usage();
  342. exit(1);
  343. }
  344. device = argv[optind];
  345. if(stat(device, &devstat) == -1) {
  346. fprintf(stderr, "scsi-spin [stat]: %s: %s\n", device, strerror(errno));
  347. result = 1;
  348. }
  349. if (is_mounted( device, opt_proc, major(devstat.st_rdev), minor(devstat.st_rdev) )) {
  350. if (! opt_force) {
  351. fprintf( stderr, "scsi-spin: device already in use (mounted partition)\n" );
  352. exit(1);
  353. }
  354. else {
  355. fprintf( stderr, "scsi-spin [warning]: device is mounted but --force is passed\n" );
  356. }
  357. }
  358. /* first try to open the device r/w */
  359. fd = open(device, O_RDWR);
  360. if (fd < 0) {
  361. /* if it's fail, then try ro */
  362. fd = open(device, O_RDONLY);
  363. if (fd < 0) {
  364. fprintf(stderr, "scsi-spin [open]: %s: %s\n", device, strerror(errno));
  365. exit(1);
  366. }
  367. }
  368. if ((S_ISBLK(devstat.st_mode) &&
  369. SCSI_BLK_MAJOR(major(devstat.st_rdev))) ||
  370. (S_ISCHR(devstat.st_mode) &&
  371. major(devstat.st_rdev) == SCSI_GENERIC_MAJOR))
  372. {
  373. if (! opt_noact) {
  374. if (opt_lock || opt_unlock)
  375. scsi_lock(fd, opt_lock);
  376. if (opt_up || opt_down)
  377. scsi_spin(fd, opt_up, opt_loej, opt_wait);
  378. }
  379. }
  380. else {
  381. fprintf(stderr, "scsi-spin: %s is not a disk or generic SCSI device.\n", device);
  382. result = 1;
  383. }
  384. close(fd);
  385. return result;
  386. }