1 /*  cdrdao - write audio CD-Rs in disc-at-once mode
2  *
3  *  Copyright (C) 2007 Denis Leroy <denis@poolshark.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., 675 Mass Ave, Cambridge, MA 02139, USA.
18  */
19 
20 #include <config.h>
21 
22 #include <sys/types.h>
23 #include <sys/stat.h>
24 
25 #include <stdio.h>
26 #include <fcntl.h>
27 #include <unistd.h>
28 #include <errno.h>
29 #include <string.h>
30 #include <assert.h>
31 #include <sys/ioctl.h>
32 #include <glob.h>
33 #include <asm/param.h>
34 #include <scsi/scsi.h>
35 #include <scsi/sg.h>
36 
37 #include "ScsiIf.h"
38 #include "sg_err.h"
39 #include "log.h"
40 #include "util.h"
41 
42 //
43 // SG_IO Linux SCSI interface
44 ///
45 
46 #ifndef SG_GET_RESERVED_SIZE
47 #define SG_GET_RESERVED_SIZE 0x2272
48 #endif
49 
50 #ifndef SG_SET_RESERVED_SIZE
51 #define SG_SET_RESERVED_SIZE 0x2275
52 #endif
53 
54 #ifndef SG_GET_VERSION_NUM
55 #define SG_GET_VERSION_NUM 0x2282
56 #endif
57 
58 #ifndef SG_MAX_SENSE
59 #define SG_MAX_SENSE 16
60 #endif
61 
62 #define CDRDAO_DEFAULT_TIMEOUT 30000
63 
64 #define SYSFS_SCSI_DEVICES "/sys/bus/scsi/devices"
65 
66 typedef unsigned char uchar;
67 
68 class ScsiIfImpl
69 {
70 public:
71     char* filename_; // user provided device name
72     int   fd_;
73     bool  readOnlyMode;
74 
75     int openScsiDevAsSg(const char* devname);
76     int adjustReservedBuffer(int requestedSize);
77 
78     uchar sense_buffer[SG_MAX_SENSE];
79     uchar sense_buffer_length;
80 
81     uchar last_sense_buffer_length;
82     uchar last_command_status;
83 
84     int timeout_ms;
85 };
86 
87 
ScsiIf(const char * dev)88 ScsiIf::ScsiIf(const char *dev)
89 {
90     impl_ = new ScsiIfImpl;
91     memset(impl_, 0, sizeof(ScsiIfImpl));
92 
93     impl_->filename_ = strdupCC(dev);
94     impl_->fd_ = -1;
95     impl_->sense_buffer_length = SG_MAX_SENSE;
96     impl_->timeout_ms = CDRDAO_DEFAULT_TIMEOUT;
97 }
98 
~ScsiIf()99 ScsiIf::~ScsiIf()
100 {
101     if (impl_->fd_ >= 0)
102 	close(impl_->fd_);
103 
104     delete[] impl_->filename_;
105     delete impl_;
106 }
107 
108 // Opens and flushes scsi device.
109 
init()110 int ScsiIf::init()
111 {
112     int flags;
113     int sg_version = 0;
114 
115     impl_->fd_ = open(impl_->filename_, O_RDWR | O_NONBLOCK | O_EXCL);
116 
117     if (impl_->fd_ < 0) {
118 
119 	if (errno == EACCES || errno == EBUSY) {
120 	    impl_->fd_ = open(impl_->filename_, O_RDONLY | O_NONBLOCK);
121 
122 	    if (impl_->fd_ < 0) {
123 		goto failed;
124 	    }
125 	    impl_->readOnlyMode = true;
126             if (errno == EACCES)
127                 log_message(-1, "No permission to write to SCSI device. "
128                             "Only read commands are supported.");
129             if (errno == EBUSY)
130                 log_message(-1, "SCSI device is currently in use. "
131                             "Only read commands are supported.");
132 	} else {
133 	    goto failed;
134 	}
135     }
136 
137     if (ioctl(impl_->fd_, SG_GET_VERSION_NUM, &sg_version) == 0) {
138 	log_message(3, "Detected SG driver version: %d.%d.%d",
139 		    sg_version / 10000,
140 		    (sg_version / 100) % 100, sg_version % 100);
141 	if (sg_version < 30000) {
142 	    log_message(-2, "SG interface under 3.0 not supported.");
143 	    return 1;
144 	}
145     }
146 
147     maxDataLen_ = impl_->adjustReservedBuffer(64 * 1024);
148 
149     if (inquiry() != 0) {
150 	return 2;
151     }
152 
153     return 0;
154 
155  failed:
156     log_message(-2, "Unable to open SCSI device %s: %s.",
157 		impl_->filename_, strerror(errno));
158     return 1;
159 }
160 
161 // Sets given timeout value in seconds and returns old timeout. Return
162 // the previous timeout value.
163 
timeout(int t)164 int ScsiIf::timeout(int t)
165 {
166     int old = impl_->timeout_ms / 1000;
167     impl_->timeout_ms = t * 1000;
168 
169     return old;
170 }
171 
172 // Sens a scsi command and send/receive data.
173 
sendCmd(const uchar * cmd,int cmdLen,const uchar * dataOut,int dataOutLen,uchar * dataIn,int dataInLen,int showMsg)174 int ScsiIf::sendCmd(const uchar *cmd, int cmdLen, const uchar *dataOut,
175 		    int dataOutLen, uchar *dataIn, int dataInLen, int showMsg)
176 {
177     int status;
178 
179     sg_io_hdr_t io_hdr;
180     memset(&io_hdr, 0, sizeof(io_hdr));
181 
182     // Check SCSI cdb length.
183     assert(cmdLen >= 0 && cmdLen <= 16);
184     // Can't both input and output data.
185     assert(!(dataOut && dataIn));
186 
187     io_hdr.interface_id = 'S';
188     io_hdr.cmd_len = cmdLen;
189     io_hdr.cmdp = (unsigned char*)cmd;
190     io_hdr.timeout = impl_->timeout_ms;
191     io_hdr.sbp = impl_->sense_buffer;
192     io_hdr.mx_sb_len = impl_->sense_buffer_length;
193     io_hdr.flags = 1;
194 
195     if (dataOut) {
196 	io_hdr.dxferp = (void*)dataOut;
197 	io_hdr.dxfer_len = dataOutLen;
198 	io_hdr.dxfer_direction = SG_DXFER_TO_DEV;
199     } else if (dataIn) {
200 	io_hdr.dxferp = dataIn;
201 	io_hdr.dxfer_len = dataInLen;
202 	io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
203     }
204 
205     log_message(4, "%s: Initiating SCSI command %s%s",
206 		impl_->filename_, sg_strcommand(cmd[0]),
207 		sg_strcmdopts(cmd));
208 
209     if (ioctl(impl_->fd_, SG_IO, &io_hdr) < 0) {
210 	int errnosave = errno;
211 	log_message((showMsg ? -2 : 3), "%s: SCSI command %s (0x%02x) "
212 		    "failed: %s.", impl_->filename_,
213 		    sg_strcommand(cmd[0]), cmd[0],
214 		    strerror(errnosave));
215 	return 1;
216     }
217 
218     log_message(4, "%s: SCSI command %s (0x%02x) executed in %u ms, status=%d",
219 		impl_->filename_, sg_strcommand(cmd[0]),
220 		cmd[0], io_hdr.duration, io_hdr.status);
221 
222     impl_->last_sense_buffer_length = io_hdr.sb_len_wr;
223     impl_->last_command_status = io_hdr.status;
224 
225     if (io_hdr.status) {
226 	if (io_hdr.sb_len_wr > 0)
227 	    return 2;
228 	else
229 	    return 1;
230     }
231 
232     return 0;
233 }
234 
getSense(int & len) const235 const uchar *ScsiIf::getSense(int &len) const
236 {
237     len = impl_->last_sense_buffer_length;
238     return impl_->sense_buffer;
239 }
240 
printError()241 void ScsiIf::printError()
242 {
243     sg_print_sense("\nSCSI command failed", impl_->sense_buffer);
244 }
245 
246 
inquiry()247 int ScsiIf::inquiry()
248 {
249     unsigned char cmd[6] = { INQUIRY, 0, 0, 0, 0x2c, 0 };
250     unsigned char result[0x2c];
251     int i;
252 
253     memset(result, 0, sizeof(result));
254 
255     if (sendCmd(cmd, 6, NULL, 0, result, 0x2c, 1) != 0) {
256 	log_message(-2, "Inquiry command failed on \"%s\"", impl_->filename_);
257 	return 1;
258     }
259 
260     strncpy(vendor_, (char *)(result + 0x08), 8);
261     vendor_[8] = 0;
262 
263     strncpy(product_, (char *)(result + 0x10), 16);
264     product_[16] = 0;
265 
266     strncpy(revision_, (char *)(result + 0x20), 4);
267     revision_[4] = 0;
268 
269     // Remove all trailing spaces.
270     for (i = 7; i >= 0 && vendor_[i] == ' '; i--) {
271 	vendor_[i] = 0;
272     }
273     for (i = 15; i >= 0 && product_[i] == ' '; i--) {
274 	product_[i] = 0;
275     }
276     for (i = 3; i >= 0 && revision_[i] == ' '; i--) {
277 	revision_[i] = 0;
278     }
279 
280     return 0;
281 }
282 
283 // Scan implementation uses sysfs to
284 
scan(int * len,char * scsi_dev_path)285 ScsiIf::ScanData *ScsiIf::scan(int *len, char* scsi_dev_path)
286 {
287     struct stat st;
288     int matches = 0;
289     unsigned i;
290     ScanData* sdata = NULL;
291     char* path = NULL;
292     glob_t pglob;
293 
294     if (stat(SYSFS_SCSI_DEVICES, &st) != 0) {
295 	log_message(-2, "Unable to access sysfs filesystem at %s",
296 		    SYSFS_SCSI_DEVICES);
297 	goto fail;
298     }
299 
300     path = (char*)alloca(strlen(SYSFS_SCSI_DEVICES) + 64);
301     sprintf(path, "%s/*", SYSFS_SCSI_DEVICES);
302     if (glob(path, 0, NULL, &pglob) != 0) {
303 	log_message(-2, "Unable to glob through sysfs filesystem (%d).",
304 		    errno);
305 	goto fail;
306     }
307 
308     sdata = new ScanData[pglob.gl_pathc];
309 
310     for (i = 0; i < pglob.gl_pathc; i++) {
311 	int type;
312 	char rbuf[16];
313 	FILE* f;
314 
315 	sprintf(path, "%s/type", pglob.gl_pathv[i]);
316 	f = fopen(path, "r");
317 	if (!f)
318 	    continue;
319 	int ret = fscanf(f, "%d", &type);
320 	fclose(f);
321 
322 	if (ret != 1 || type != TYPE_ROM)
323 	    continue;
324 
325 	// Now we have a CD-ROM device.
326 	memset(&sdata[matches].vendor, 0, sizeof(sdata[matches].vendor));
327 	memset(&sdata[matches].product, 0, sizeof(sdata[matches].product));
328 	memset(&sdata[matches].revision, 0, sizeof(sdata[matches].revision));
329 
330 	// Copy vendor data
331 	sprintf(path, "%s/vendor", pglob.gl_pathv[i]);
332 	f = fopen(path, "r");
333 	if (!f)
334 	    continue;
335 	if (fread(sdata[matches].vendor, 8, 1, f) != 1) {
336 	    fclose(f);
337 	    continue;
338 	}
339 	fclose(f);
340 
341 	// Copy product data
342 	sprintf(path, "%s/model", pglob.gl_pathv[i]);
343 	f = fopen(path, "r");
344 	if (!f)
345 	    continue;
346 	if (fread(sdata[matches].product, 16, 1, f) != 1) {
347 	    fclose(f);
348 	    continue;
349 	}
350 	fclose(f);
351 
352 	// Copy revision data
353 	sprintf(path, "%s/rev", pglob.gl_pathv[i]);
354 	f = fopen(path, "r");
355 	if (!f)
356 	    continue;
357 	if (fread(sdata[matches].revision, 4, 1, f) != 1) {
358 	    fclose(f);
359 	    continue;
360 	}
361 	fclose(f);
362 
363 	// figure out the block device
364 	glob_t bglob;
365 	char* devname = NULL;
366 	sprintf(path, "%s/block:*", pglob.gl_pathv[i]);
367 	if (glob(path, 0, NULL, &bglob) == 0) {
368 
369 	    if (bglob.gl_pathc != 1) {
370 		globfree(&bglob);
371 		continue;
372 	    }
373 
374 	    char* match = strrchr(bglob.gl_pathv[0], ':');
375 	    if (!match) {
376 		globfree(&bglob);
377 		continue;
378 	    }
379 	    devname = (char*)alloca(strlen(match));
380 	    strcpy(devname, match+1);
381 	} else {
382 	    sprintf(path, "%s/block/*", pglob.gl_pathv[i]);
383 	    if (glob(path, 0, NULL, &bglob) == 0) {
384 
385 		if (bglob.gl_pathc != 1) {
386 		    globfree(&bglob);
387 		    continue;
388 		}
389 		char* match = strrchr(bglob.gl_pathv[0], '/');
390 		devname = (char*)alloca(strlen(match));
391 		strcpy(devname, match + 1);
392 	    }
393 	}
394 
395 	if (devname) {
396 	    sdata[matches].dev = "/dev/";
397 	    sdata[matches].dev += devname;
398 	    globfree(&bglob);
399 	} else {
400 	    continue;
401 	}
402 
403 	matches++;
404 
405     }
406     globfree(&pglob);
407 
408     if (matches) {
409 	*len = matches;
410 	return sdata;
411     }
412 
413     delete[] sdata;
414  fail:
415     *len = 0;
416     return NULL;
417 }
418 
419 #include "ScsiIf-common.cc"
420 
adjustReservedBuffer(int requestedSize)421 int ScsiIfImpl::adjustReservedBuffer(int requestedSize)
422 {
423     int maxTransferLength;
424 
425     if (ioctl(fd_, SG_SET_RESERVED_SIZE, &requestedSize) < 0) {
426 	log_message(-2, "SG_SET_RESERVED_SIZE ioctl failed: %s",
427 		    strerror(errno));
428 	return 0;
429     }
430     if (ioctl(fd_, SG_GET_RESERVED_SIZE, &maxTransferLength) < 0) {
431 	log_message(-2, "SG_GET_RESERVED_SIZE ioctl failed: %s",
432 		    strerror(errno));
433 	return 0;
434     }
435 
436     log_message(4, "SG: Maximum transfer length: %ld", maxTransferLength);
437 
438     return maxTransferLength;
439 }
440