dvd.rw-tools/transport.hxx

2095 lines
58 KiB
C++
Raw Blame History

//
// This is part of dvd+rw-tools by Andy Polyakov <appro@fy.chalmers.se>
//
// Use-it-on-your-own-risk, GPL bless...
//
// For further details see http://fy.chalmers.se/~appro/linux/DVD+RW/
//
#if defined(__unix) || defined(__unix__)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <poll.h>
#include <sys/time.h>
inline long getmsecs()
{ struct timeval tv;
gettimeofday (&tv,NULL);
return tv.tv_sec*1000+tv.tv_usec/1000;
}
#include <errno.h>
#ifndef EMEDIUMTYPE
#define EMEDIUMTYPE EINVAL
#endif
#ifndef ENOMEDIUM
#define ENOMEDIUM ENODEV
#endif
#include <locale.h>
#define ENV_LOCALE ""
#elif defined(_WIN32)
#include <windows.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#define ssize_t LONG_PTR
#define off64_t __int64
#include "win32err.h"
#define poll(a,b,t) Sleep(t)
#define getmsecs() GetTickCount()
#include <locale.h>
#define ENV_LOCALE ".OCP"
#endif
#include "asctable.h"
#define CREAM_ON_ERRNO_NAKED(s) \
switch ((s)[12]) \
{ case 0x04: errno=EAGAIN; break; \
case 0x20: errno=ENODEV; break; \
case 0x21: if ((s)[13]==0) errno=ENOSPC; \
else errno=EINVAL; \
break; \
case 0x30: errno=EMEDIUMTYPE; break; \
case 0x3A: errno=ENOMEDIUM; break; \
}
#define CREAM_ON_ERRNO(s) do { CREAM_ON_ERRNO_NAKED(s) } while(0)
#ifndef FATAL_START
#define FATAL_START(er) (0x80|(er))
#endif
#define ERRCODE(s) ((((s)[2]&0x0F)<<16)|((s)[12]<<8)|((s)[13]))
#define SK(errcode) (((errcode)>>16)&0xF)
#define ASC(errcode) (((errcode)>>8)&0xFF)
#define ASCQ(errcode) ((errcode)&0xFF)
static void sperror (const char *cmd,int err)
{ int saved_errno=errno;
const char *msg;
if (err==-1)
fprintf (stderr,":-( unable to %s: ",cmd);
else if ((msg=ASC_lookup(err))!=NULL)
fprintf (stderr,":-[ %s failed with SK=%Xh/%s]: ",
cmd,SK(err),msg);
else
fprintf (stderr,":-[ %s failed with SK=%Xh/ASC=%02Xh/ACQ=%02Xh]: ",
cmd,SK(err),ASC(err),ASCQ(err));
errno=saved_errno, perror (NULL);
}
static void sperror (const char *cmd,unsigned char *sense)
{ int saved_errno=errno;
int err=ERRCODE(sense);
if (err==0)
fprintf (stderr,":-( unable to %s: ",cmd);
else
{ if ((err==0x20407 || err==0x20408) && sense[15]&0x80)
fprintf (stderr,":-[ %s failed with SK=%Xh/ASC=%02Xh/ACQ=%02Xh@%.1f%%]: ",
cmd,SK(err),ASC(err),ASCQ(err),
100.0*(sense[16]<<8|sense[17])/65536.0);
else
{ errno=saved_errno;
sperror (cmd,err);
return;
}
}
errno=saved_errno, perror (NULL);
}
class autofree {
private:
unsigned char *ptr;
public:
autofree() { ptr=NULL; }
~autofree() { if (ptr) free(ptr); }
unsigned char *operator=(unsigned char *str)
{ return ptr=str; }
operator unsigned char *() { return ptr; }
};
extern "C" char *plusminus_locale()
{ static class __plusminus {
private:
char str[4];
public:
__plusminus() { setlocale(LC_CTYPE,ENV_LOCALE);
int l = wctomb(str,(wchar_t)(unsigned char)'<EFBFBD>');
if (l>0) str[l]='\0';
else str[0]='<EFBFBD>',str[1]='\0';
}
~__plusminus() { }
operator char*(){ return str; }
} plusminus;
return plusminus;
}
#if defined(__linux)
#include <sys/ioctl.h>
#include <linux/cdrom.h>
#include <mntent.h>
#include <sys/wait.h>
#include <sys/utsname.h>
#include <scsi/sg.h>
#if !defined(SG_FLAG_LUN_INHIBIT)
# if defined(SG_FLAG_UNUSED_LUN_INHIBIT)
# define SG_FLAG_LUN_INHIBIT SG_FLAG_UNUSED_LUN_INHIBIT
# else
# define SG_FLAG_LUN_INHIBIT 0
# endif
#endif
#ifndef CHECK_CONDITION
#define CHECK_CONDITION 0x01
#endif
typedef enum { NONE=CGC_DATA_NONE, // 3
READ=CGC_DATA_READ, // 2
WRITE=CGC_DATA_WRITE // 1
} Direction;
#ifdef SG_IO
static const int Dir_xlate [4] = { // should have been defined
// private in USE_SG_IO scope,
// but it appears to be too
0, // implementation-dependent...
SG_DXFER_TO_DEV, // 1,CGC_DATA_WRITE
SG_DXFER_FROM_DEV, // 2,CGC_DATA_READ
SG_DXFER_NONE }; // 3,CGC_DATA_NONE
static const class USE_SG_IO {
private:
int yes_or_no;
public:
USE_SG_IO() { struct utsname buf;
uname (&buf);
// was CDROM_SEND_PACKET declared dead in 2.5?
yes_or_no=(strcmp(buf.release,"2.5.43")>=0);
}
~USE_SG_IO(){}
operator int() const { return yes_or_no; }
int operator[] (Direction dir) const { return Dir_xlate[dir]; }
} use_sg_io;
#endif
#if 0
#include <dlfcn.h>
static union dl_rsm_open_device {
void *p;
int (*f)(const char *,int,...);
dl_rsm_open_device(){ void *h;
if ((h=dlopen("libresmgr.so.1",RTLD_LAZY))==NULL ||
(p=dlsym(h,"rsm_open_device"))==NULL)
f = open;
}
~dl_rsm_open_device(){}
} rsm_open_device;
extern "C" int dev_open(const char *pathname,int flags)
{ return rsm_open_device.f(pathname,flags); }
extern "C" int dev_open_patched()
{ return rsm_open_device.f!=open; }
#endif
class Scsi_Command {
private:
int fd,autoclose;
char *filename;
struct cdrom_generic_command cgc;
union {
struct request_sense s;
unsigned char u[18];
} _sense;
#ifdef SG_IO
struct sg_io_hdr sg_io;
#else
struct { int cmd_len,timeout; } sg_io;
#endif
public:
Scsi_Command() { fd=-1, autoclose=1; filename=NULL; }
Scsi_Command(int f) { fd=f, autoclose=0; filename=NULL; }
Scsi_Command(void*f){ fd=(long)f, autoclose=0; filename=NULL; }
~Scsi_Command() { if (fd>=0 && autoclose) close(fd),fd=-1;
if (filename) free(filename),filename=NULL;
}
int associate (const char *file,const struct stat *ref=NULL)
{ struct stat sb;
/*
* O_RDWR is expected to provide for none set-root-uid
* execution under Linux kernel 2.6[.8]. Under 2.4 it
* falls down to O_RDONLY...
*/
if ((fd=open (file,O_RDWR|O_NONBLOCK)) < 0 &&
(fd=open (file,O_RDONLY|O_NONBLOCK)) < 0) return 0;
if (fstat(fd,&sb) < 0) return 0;
if (!S_ISBLK(sb.st_mode)) { errno=ENOTBLK;return 0; }
if (ref && (!S_ISBLK(ref->st_mode) || ref->st_rdev!=sb.st_rdev))
{ errno=ENXIO; return 0; }
filename=strdup(file);
return 1;
}
unsigned char &operator[] (size_t i)
{ if (i==0)
{ memset(&cgc,0,sizeof(cgc)), memset(&_sense,0,sizeof(_sense));
cgc.quiet = 1;
cgc.sense = &_sense.s;
#ifdef SG_IO
if (use_sg_io)
{ memset(&sg_io,0,sizeof(sg_io));
sg_io.interface_id= 'S';
sg_io.mx_sb_len = sizeof(_sense);
sg_io.cmdp = cgc.cmd;
sg_io.sbp = _sense.u;
sg_io.flags = SG_FLAG_LUN_INHIBIT|SG_FLAG_DIRECT_IO;
}
#endif
}
sg_io.cmd_len = i+1;
return cgc.cmd[i];
}
unsigned char &operator()(size_t i) { return _sense.u[i]; }
unsigned char *sense() { return _sense.u; }
void timeout(int i) { cgc.timeout=sg_io.timeout=i*1000; }
#ifdef SG_IO
size_t residue() { return use_sg_io?sg_io.resid:0; }
#else
size_t residue() { return 0; }
#endif
int transport(Direction dir=NONE,void *buf=NULL,size_t sz=0)
{ int ret = 0;
#ifdef SG_IO
#define KERNEL_BROKEN 0
if (use_sg_io)
{ sg_io.dxferp = buf;
sg_io.dxfer_len = sz;
sg_io.dxfer_direction = use_sg_io[dir];
if (ioctl (fd,SG_IO,&sg_io)) return -1;
#if !KERNEL_BROKEN
if ((sg_io.info&SG_INFO_OK_MASK) != SG_INFO_OK)
#else
if (sg_io.status)
#endif
{ errno=EIO; ret=-1;
#if !KERNEL_BROKEN
if (sg_io.masked_status&CHECK_CONDITION)
#endif
{ ret = ERRCODE(_sense.u);
if (ret==0) ret=-1;
else CREAM_ON_ERRNO(_sense.u);
}
}
return ret;
}
else
#undef KERNEL_BROKEN
#endif
{ cgc.buffer = (unsigned char *)buf;
cgc.buflen = sz;
cgc.data_direction = dir;
if (ioctl (fd,CDROM_SEND_PACKET,&cgc))
{ ret = ERRCODE(_sense.u);
if (ret==0) ret=-1;
}
}
return ret;
}
int umount(int f=-1)
{ struct stat fsb,msb;
struct mntent *mb;
FILE *fp;
pid_t pid,rpid;
int ret=0,rval;
if (f==-1) f=fd;
if (fstat (f,&fsb) < 0) return -1;
if ((fp=setmntent ("/proc/mounts","r"))==NULL) return -1;
while ((mb=getmntent (fp))!=NULL)
{ if (stat (mb->mnt_fsname,&msb) < 0) continue; // corrupted line?
#ifdef I_HAVE_PATCHED_SUBMOUNTD // see O_EXCL commentary in growisofs.c
if (!strcmp (mb->mnt_type,"subfs")) continue;
#endif
if (msb.st_rdev == fsb.st_rdev)
{ ret = -1;
if ((pid = fork()) == (pid_t)-1) break;
if (pid == 0) execl ("/bin/umount","umount",mb->mnt_dir,(void*)NULL);
while (1)
{ rpid = waitpid (pid,&rval,0);
if (rpid == (pid_t)-1)
{ if (errno==EINTR) continue;
else break;
}
else if (rpid != pid)
{ errno = ECHILD;
break;
}
if (WIFEXITED(rval))
{ if (WEXITSTATUS(rval) == 0) ret=0;
else errno=EBUSY; // most likely
break;
}
else
{ errno = ENOLINK; // some phony errno
break;
}
}
break;
}
}
endmntent (fp);
return ret;
}
int is_reload_needed (int same_capacity)
{ if (same_capacity && ioctl (fd,0x1261,0) == 0) // try BLKFLSBUF
return 0;
else
return ioctl (fd,CDROM_MEDIA_CHANGED,CDSL_CURRENT) == 0;
}
};
#elif defined(__OpenBSD__) || defined(__NetBSD__)
#include <sys/ioctl.h>
#include <sys/scsiio.h>
#include <sys/wait.h>
#include <sys/param.h>
#include <sys/mount.h>
typedef off_t off64_t;
#define stat64 stat
#define fstat64 fstat
#define open64 open
#define pread64 pread
#define pwrite64 pwrite
#define lseek64 lseek
typedef enum { NONE=0,
READ=SCCMD_READ,
WRITE=SCCMD_WRITE
} Direction;
class Scsi_Command {
private:
int fd,autoclose;
char *filename;
scsireq_t req;
public:
Scsi_Command() { fd=-1, autoclose=1; filename=NULL; }
Scsi_Command(int f) { fd=f, autoclose=0; filename=NULL; }
Scsi_Command(void*f){ fd=(long)f, autoclose=0; filename=NULL; }
~Scsi_Command() { if (fd>=0 && autoclose) close(fd),fd=-1;
if (filename) free(filename),filename=NULL;
}
int associate (const char *file,const struct stat *ref=NULL)
{ struct stat sb;
fd=open(file,O_RDWR|O_NONBLOCK);
// this is --^^^^^^-- why we have to run set-root-uid...
if (fd < 0) return 0;
if (fstat(fd,&sb) < 0) return 0;
if (!S_ISCHR(sb.st_mode)) { errno=EINVAL; return 0; }
if (ref && (!S_ISCHR(ref->st_mode) || ref->st_rdev!=sb.st_rdev))
{ errno=ENXIO; return 0; }
filename=strdup(file);
return 1;
}
unsigned char &operator[] (size_t i)
{ if (i==0)
{ memset(&req,0,sizeof(req));
req.flags = SCCMD_ESCAPE;
req.timeout = 30000;
req.senselen = 18; //sizeof(req.sense);
}
req.cmdlen = i+1;
return req.cmd[i];
}
unsigned char &operator()(size_t i) { return req.sense[i]; }
unsigned char *sense() { return req.sense; }
void timeout(int i) { req.timeout=i*1000; }
size_t residue() { return req.datalen-req.datalen_used; }
int transport(Direction dir=NONE,void *buf=NULL,size_t sz=0)
{ int ret=0;
req.databuf = (caddr_t)buf;
req.datalen = sz;
req.flags |= dir;
if (ioctl (fd,SCIOCCOMMAND,&req) < 0) return -1;
if (req.retsts==SCCMD_OK) return 0;
errno=EIO; ret=-1;
if (req.retsts==SCCMD_SENSE)
{ ret = ERRCODE(req.sense);
if (ret==0) ret=-1;
else CREAM_ON_ERRNO(req.sense);
}
return ret;
}
// this code is basically redundant... indeed, we normally want to
// open device O_RDWR, but we can't do that as long as it's mounted.
// in other words, whenever this routine is invoked, device is not
// mounted, so that it could as well just return 0;
int umount(int f=-1)
{ struct stat fsb,msb;
struct statfs *mntbuf;
int ret=0,mntsize,i;
if (f==-1) f=fd;
if (fstat (f,&fsb) < 0) return -1;
if ((mntsize=getmntinfo(&mntbuf,MNT_NOWAIT))==0)return -1;
for (i=0;i<mntsize;i++)
{ char rdev[MNAMELEN+1],*slash,*rslash;
mntbuf[i].f_mntfromname[MNAMELEN-1]='\0'; // paranoia
if ((slash=strrchr (mntbuf[i].f_mntfromname,'/'))==NULL) continue;
strcpy (rdev,mntbuf[i].f_mntfromname); // rdev is 1 byte larger!
rslash = strrchr (rdev,'/');
*(rslash+1) = 'r', strcpy (rslash+2,slash+1);
if (stat (rdev,&msb) < 0) continue;
if (msb.st_rdev == fsb.st_rdev)
{ ret=unmount (mntbuf[i].f_mntonname,0);
break;
}
}
return ret;
}
int is_reload_needed (int not_used)
{ return 1; }
};
#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
#include <sys/ioctl.h>
#include <camlib.h>
#include <cam/scsi/scsi_message.h>
#include <cam/scsi/scsi_pass.h>
#include <sys/wait.h>
#include <sys/param.h>
#include <sys/mount.h>
#include <dirent.h>
typedef off_t off64_t;
#define stat64 stat
#define fstat64 fstat
#define open64 open
#define pread64 pread
#define pwrite64 pwrite
#define lseek64 lseek
#define ioctl_fd (((struct cam_device *)ioctl_handle)->fd)
typedef enum { NONE=CAM_DIR_NONE,
READ=CAM_DIR_IN,
WRITE=CAM_DIR_OUT
} Direction;
class Scsi_Command {
private:
int fd,autoclose;
char *filename;
struct cam_device *cam;
union ccb ccb;
public:
Scsi_Command()
{ cam=NULL, fd=-1, autoclose=1; filename=NULL; }
Scsi_Command(int f)
{ char pass[32]; // periph_name is 16 chars long
cam=NULL, fd=-1, autoclose=1, filename=NULL;
memset (&ccb,0,sizeof(ccb));
ccb.ccb_h.func_code = XPT_GDEVLIST;
if (ioctl (f,CAMGETPASSTHRU,&ccb) < 0) return;
sprintf (pass,"/dev/%.15s%u",ccb.cgdl.periph_name,ccb.cgdl.unit_number);
cam=cam_open_pass (pass,O_RDWR,NULL);
}
Scsi_Command(void *f)
{ cam=(struct cam_device *)f, autoclose=0; fd=-1; filename=NULL; }
~Scsi_Command()
{ if (cam && autoclose) cam_close_device(cam), cam=NULL;
if (fd>=0) close(fd);
if (filename) free(filename), filename=NULL;
}
int associate (const char *file,const struct stat *ref=NULL)
{ struct stat sb;
char pass[32]; // periph_name is 16 chars long
fd=open(file,O_RDONLY|O_NONBLOCK);
// all if (ref) code is actually redundant, it never runs
// as long as RELOAD_NEVER_NEEDED...
if (ref && fd<0 && errno==EPERM)
{ // expectedly we would get here if file is /dev/passN
if (stat(file,&sb) < 0) return 0;
if (!S_ISCHR(ref->st_mode) || ref->st_rdev!=sb.st_rdev)
return (errno=ENXIO,0);
fd=open(file,O_RDWR);
}
if (fd < 0) return 0;
if (fstat(fd,&sb) < 0) return 0;
if (!S_ISCHR(sb.st_mode)) return (errno=EINVAL,0);
if (ref && (!S_ISCHR(ref->st_mode) || ref->st_rdev!=sb.st_rdev))
return (errno=ENXIO,0);
memset (&ccb,0,sizeof(ccb));
ccb.ccb_h.func_code = XPT_GDEVLIST;
if (ioctl(fd,CAMGETPASSTHRU,&ccb)<0) return (close(fd),fd=-1,0);
sprintf (pass,"/dev/%.15s%u",ccb.cgdl.periph_name,ccb.cgdl.unit_number);
cam=cam_open_pass (pass,O_RDWR,NULL);
if (cam==NULL) return (close(fd),fd=-1,0);
filename=strdup(file);
return 1;
}
unsigned char &operator[] (size_t i)
{ if (i==0)
{ memset(&ccb,0,sizeof(ccb));
ccb.ccb_h.path_id = cam->path_id;
ccb.ccb_h.target_id = cam->target_id;
ccb.ccb_h.target_lun = cam->target_lun;
cam_fill_csio (&(ccb.csio),
1, // retries
NULL, // cbfncp
CAM_DEV_QFRZDIS, // flags
MSG_SIMPLE_Q_TAG, // tag_action
NULL, // data_ptr
0, // dxfer_len
sizeof(ccb.csio.sense_data), // sense_len
0, // cdb_len
30*1000); // timeout
}
ccb.csio.cdb_len = i+1;
return ccb.csio.cdb_io.cdb_bytes[i];
}
unsigned char &operator()(size_t i)
{ return ((unsigned char *)&ccb.csio.sense_data)[i]; }
unsigned char *sense()
{ return (unsigned char*)&ccb.csio.sense_data; }
void timeout(int i) { ccb.ccb_h.timeout=i*1000; }
size_t residue() { return ccb.csio.resid; }
int transport(Direction dir=NONE,void *buf=NULL,size_t sz=0)
{ int ret=0;
ccb.csio.ccb_h.flags |= dir;
ccb.csio.data_ptr = (u_int8_t *)buf;
ccb.csio.dxfer_len = sz;
if ((ret = cam_send_ccb(cam, &ccb)) < 0)
return -1;
if ((ccb.ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP)
return 0;
unsigned char *sense=(unsigned char *)&ccb.csio.sense_data;
errno = EIO;
// FreeBSD 5-CURRENT since 2003-08-24, including 5.2 fails to
// pull sense data automatically, at least for ATAPI transport,
// so I reach for it myself...
if ((ccb.csio.scsi_status==SCSI_STATUS_CHECK_COND) &&
!(ccb.ccb_h.status&CAM_AUTOSNS_VALID))
{ u_int8_t _sense[18];
u_int32_t resid=ccb.csio.resid;
memset(_sense,0,sizeof(_sense));
operator[](0) = 0x03; // REQUEST SENSE
ccb.csio.cdb_io.cdb_bytes[4] = sizeof(_sense);
ccb.csio.cdb_len = 6;
ccb.csio.ccb_h.flags |= CAM_DIR_IN|CAM_DIS_AUTOSENSE;
ccb.csio.data_ptr = _sense;
ccb.csio.dxfer_len = sizeof(_sense);
ccb.csio.sense_len = 0;
ret = cam_send_ccb(cam, &ccb);
ccb.csio.resid = resid;
if (ret<0) return -1;
if ((ccb.ccb_h.status&CAM_STATUS_MASK) != CAM_REQ_CMP)
return errno=EIO,-1;
memcpy(sense,_sense,sizeof(_sense));
}
ret = ERRCODE(sense);
if (ret == 0) ret = -1;
else CREAM_ON_ERRNO(sense);
return ret;
}
int umount(int f=-1)
{ struct stat fsb,msb;
struct statfs *mntbuf;
int ret=0,mntsize,i;
if (f==-1) f=fd;
if (fstat (f,&fsb) < 0) return -1;
if ((mntsize=getmntinfo(&mntbuf,MNT_NOWAIT))==0)return -1;
for (i=0;i<mntsize;i++)
{ if (stat (mntbuf[i].f_mntfromname,&msb) < 0) continue;
if (msb.st_rdev == fsb.st_rdev)
{ ret=unmount (mntbuf[i].f_mntonname,0);
break;
}
}
return ret;
}
#define RELOAD_NEVER_NEEDED // according to Matthew Dillon
int is_reload_needed (int not_used)
{ return 0; }
};
#elif defined(__sun) || defined(sun)
//
// Licensing terms for commercial deployment under Solaris are to be
// settled with Inserve Technology, <20>v<EFBFBD>gen 6, 412 50 G<>TEBORG, Sweden,
// tel. +46-(0)31-86 87 88, see http://www.inserve.se/ for further
// details about Inserve Technology.
//
#include <volmgt.h>
extern "C" int _dev_unmount(char *); // VolMgt ON Consolidation Private API
#include <sys/param.h>
#include <sys/scsi/impl/uscsi.h>
#include <sys/mount.h>
#include <sys/mnttab.h>
#include <sys/wait.h>
#include <sys/cdio.h>
#include <sys/utsname.h>
#include <sys/dklabel.h>
#include <sys/dkio.h>
#include <dlfcn.h>
#include <pwd.h>
#include <alloca.h>
typedef enum { NONE=0,
READ=USCSI_READ,
WRITE=USCSI_WRITE
} Direction;
class Scsi_Command {
private:
int fd,autoclose;
char *filename;
struct uscsi_cmd ucmd;
unsigned char cdb[16], _sense[18];
public:
Scsi_Command() { fd=-1, autoclose=1; filename=NULL; }
Scsi_Command(int f) { fd=f, autoclose=0; filename=NULL; }
Scsi_Command(void*f){ fd=(long)f, autoclose=0; filename=NULL; }
~Scsi_Command() { if (fd>=0 && autoclose) close(fd),fd=-1;
if (filename) free(filename),filename=NULL;
}
int associate (const char *file,const struct stat *ref=NULL)
{ class autofree {
private:
char *ptr;
public:
autofree() { ptr=NULL; }
~autofree() { if (ptr) free(ptr); }
char *operator=(char *str) { return ptr=str; }
operator char *() { return ptr; }
} volname,device;
struct stat sb;
int v;
uid_t uid;
if ((uid=getuid())!=0)
{ void *secdb = dlopen("libsecdb.so.1",RTLD_LAZY);
union { void *p; int (*f)(const char *,const char *); } chkauthattr;
if (secdb && (chkauthattr.p=dlsym(secdb,"chkauthattr")))
{ struct passwd *pwd = getpwuid(uid);
if (pwd==NULL || !chkauthattr.f("solaris.device.cdrw",pwd->pw_name))
return (errno=EACCES),0;
}
if (secdb) dlclose(secdb);
}
if ((v=volmgt_running()))
{ if ((volname=volmgt_symname ((char *)file)))
{ if ((device=media_findname (volname)) == NULL)
return 0;
}
else if ((device=media_findname ((char *)file))==NULL)
return 0;
}
else device=strdup(file);
fd=open (device,O_RDONLY|O_NONBLOCK);
if (fd<0) return 0;
if (fstat(fd,&sb) < 0) return 0;
if (!S_ISCHR(sb.st_mode)) { errno=ENOTTY; return 0; }
if (ref && (!S_ISCHR(ref->st_mode) || ref->st_rdev!=sb.st_rdev))
{ errno=ENXIO; return 0; }
filename=strdup(device);
return 1;
}
unsigned char &operator[] (size_t i)
{ if (i==0)
{ memset (&ucmd,0,sizeof(ucmd));
memset (cdb,0,sizeof(cdb));
memset (_sense,0,sizeof(_sense));
ucmd.uscsi_cdb = (caddr_t)cdb;
ucmd.uscsi_rqbuf = (caddr_t)_sense;
ucmd.uscsi_rqlen = sizeof(_sense);
ucmd.uscsi_flags = USCSI_SILENT | USCSI_DIAGNOSE |
USCSI_ISOLATE | USCSI_RQENABLE;
ucmd.uscsi_timeout= 60;
}
ucmd.uscsi_cdblen = i+1;
return cdb[i];
}
unsigned char &operator()(size_t i) { return _sense[i]; }
unsigned char *sense() { return _sense; }
void timeout(int i) { ucmd.uscsi_timeout=i; }
size_t residue() { return ucmd.uscsi_resid; }
int transport(Direction dir=NONE,void *buf=NULL,size_t sz=0)
{ int ret=0;
ucmd.uscsi_bufaddr = (caddr_t)buf;
ucmd.uscsi_buflen = sz;
ucmd.uscsi_flags |= dir;
if (ioctl(fd,USCSICMD,&ucmd))
{ if (errno==EIO && _sense[0]==0) // USB seems to be broken...
{ size_t residue=ucmd.uscsi_resid;
memset(cdb,0,sizeof(cdb));
cdb[0]=0x03; // REQUEST SENSE
cdb[4]=sizeof(_sense);
ucmd.uscsi_cdblen = 6;
ucmd.uscsi_bufaddr = (caddr_t)_sense;
ucmd.uscsi_buflen = sizeof(_sense);
ucmd.uscsi_flags = USCSI_SILENT | USCSI_DIAGNOSE |
USCSI_ISOLATE | USCSI_READ;
ucmd.uscsi_timeout = 1;
ret = ioctl(fd,USCSICMD,&ucmd);
ucmd.uscsi_resid = residue;
if (ret) return -1;
}
ret = ERRCODE(_sense);
if (ret==0) ret=-1;
//else CREAM_ON_ERRNO(_sense);
}
return ret;
}
// mimics umount(2), therefore inconsistent return values
int umount(int f=-1)
{ struct stat fsb,msb;
struct mnttab mb;
FILE *fp;
pid_t pid,rpid;
int ret=0,i,rval;
if (f==-1) f=fd;
if (fstat (f,&fsb) < 0) return -1;
if ((fp=fopen (MNTTAB,"r")) == NULL) return -1;
while ((i=getmntent (fp,&mb)) != -1)
{ if (i != 0) continue; // ignore corrupted lines
if (stat (mb.mnt_special,&msb) < 0) continue; // also corrupted?
if (msb.st_rdev == fsb.st_rdev)
{ if (_dev_unmount (mb.mnt_special)) break;
{ struct utsname uts;
if (uname (&uts)>=0 && (strcmp(uts.release,"5.8")>=0 || strlen(uts.release)>3))
{ // M-m-m-m-m! Solaris 8 or later...
ret = ::umount (mb.mnt_special);
break;
}
}
ret = -1;
if ((pid = fork()) == (pid_t)-1) break;
if (pid == 0) execl ("/usr/sbin/umount","umount",mb.mnt_mountp,(void*)NULL);
while (1)
{ rpid = waitpid (pid,&rval,0);
if (rpid == (pid_t)-1)
{ if (errno==EINTR) continue;
else break;
}
else if (rpid != pid)
{ errno = ECHILD;
break;
}
if (WIFEXITED(rval))
{ if (WEXITSTATUS(rval) == 0) ret=0;
else errno=EBUSY; // most likely
break;
}
else if (WIFSTOPPED(rval) || WIFCONTINUED(rval))
continue;
else
{ errno = ENOLINK; // some phony errno
break;
}
}
break;
}
}
fclose (fp);
return ret;
}
int is_reload_needed (int not_used)
{ struct dk_minfo mi;
struct dk_allmap pt;
if (ioctl (fd,DKIOCGMEDIAINFO,&mi) < 0) return 1;
memset (&pt,0,sizeof(pt));
pt.dka_map[2].dkl_nblk = mi.dki_capacity*(mi.dki_lbsize/DEV_BSIZE);
pt.dka_map[0] = pt.dka_map[2];
if (ioctl (fd,DKIOCSAPART,&pt) < 0) return 1;
return 0;
}
};
#elif defined(__hpux)
//
// Copyright (C) 2003 Hewlett-Packard Development Company, L.P.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// =====================================================================
//
// The code targets 11i/11.11 and later, but it might just work on
// 11.0 as well. For further information contact following HP office
//
// Hewlett-Packard Company
// 3404 E Harmony Road
// Fort Collins, CO 80528 USA
//
#include <strings.h>
#include <sys/scsi.h>
#include <mntent.h>
#ifndef minor
#include <sys/mknod.h>
#endif
typedef enum { NONE=0,
READ=SCTL_READ,
WRITE=0
} Direction;
class Scsi_Command {
private:
int fd;
int autoclose;
char *filename;
struct sctl_io cmd;
public:
Scsi_Command() { fd=-1, autoclose=1; filename=NULL; }
Scsi_Command(int f) { fd=f, autoclose=0; filename=NULL; }
Scsi_Command(void*f){ fd=(long)f, autoclose=0; filename=NULL; }
~Scsi_Command() { if (fd>=0 && autoclose) close(fd),fd=-1;
if (filename) free(filename),filename=NULL;
}
int associate (const char *file,const struct stat *ref=NULL)
{ struct stat sb;
fd=open (file,O_RDONLY|O_NONBLOCK);
if (fd < 0) return 0;
if (fstat(fd,&sb) < 0) return 0;
if (!S_ISCHR(sb.st_mode)) { errno=EINVAL; return 0; }
// shall we check for DIOC_DESCRIBE here?
if (ref && (!S_ISCHR(ref->st_mode) || ref->st_rdev!=sb.st_rdev))
{ errno=ENXIO; return 0; }
filename=strdup(file);
return 1;
}
unsigned char &operator[] (size_t i)
{ if (i==0)
{ bzero (&cmd,sizeof(struct sctl_io));
cmd.max_msecs=30*1000;
}
cmd.cdb_length = i+1;
return cmd.cdb[i];
}
unsigned char &sense(size_t i) { return cmd.sense[i]; }
unsigned char *sense() { return cmd.sense; }
void timeout(int i) { cmd.max_msecs=i*1000; }
size_t residue() { return cmd.data_length-cmd.data_xfer; }
int transport(Direction dir=NONE,void *buf=NULL,size_t sz=0)
{ const char *err = NULL;
cmd.data = buf;
cmd.data_length = sz;
cmd.flags = dir;
if (ioctl (fd,SIOC_IO,&cmd) != 0) return -1;
if (cmd.cdb_status == S_GOOD) return 0;
errno = EIO;
switch (cmd.cdb_status)
{ case SCTL_INVALID_REQUEST: err = "SCTL_INVALID_REQUEST"; break;
case SCTL_SELECT_TIMEOUT: err = "SCTL_SELECT_TIMEOUT"; break;
case SCTL_INCOMPLETE: err = "SCTL_INCOMPLETE"; break;
case SCTL_POWERFAIL: err = "SCTL_POWERFAIL"; break;
default: err = NULL; break;
}
if (err != NULL)
{ fprintf (stderr,":-( FAIL: command failed with %s.\n",err);
return -1;
}
switch (cmd.cdb_status & 0xff)
{ case S_CHECK_CONDITION:
if (cmd.sense_status==S_GOOD && cmd.sense_xfer!=0)
{ CREAM_ON_ERRNO_NAKED(cmd.sense) // CREAM_ON_ERRNO
// provokes PA-RISC
// compiler bug...
return ERRCODE(cmd.sense);
}
else
fprintf (stderr,":-( FAIL: S_CHECK_CONDITION status, "
"but no sense data?\n");
break;
case S_BUSY:
fprintf (stderr,":-( FAIL: S_BUSY condition?\n");
errno = EAGAIN;
break;
default:
fprintf (stderr,":-( FAIL: unknown cdb_status=0x%x\n",
cmd.cdb_status);
break;
}
return -1;
}
// for now we only detect if media is mounted...
int umount(int f=-1)
{ struct stat fsb,msb;
struct mntent *mb;
FILE *fp;
int ret=0;
char bdev[32];
dev_t m;
if (f==-1) f=fd;
if (fstat (f,&fsb) < 0) return -1;
if ((fp=setmntent (MNT_MNTTAB,"r")) == NULL) return -1;
m=minor(fsb.st_rdev);
sprintf(bdev,"/dev/dsk/c%ut%ud%x",(m>>16)&0xFF,(m>>12)&0xF,(m>>8)&0xF);
if (stat (bdev,&fsb) < 0) return -1;
while ((mb=getmntent (fp))!=NULL)
{ if (stat (mb->mnt_fsname,&msb) < 0) continue; // corrupted line?
if (msb.st_rdev == fsb.st_rdev)
{ errno=EBUSY; ret=-1; break; }
}
endmntent (fp);
return ret;
}
int is_reload_needed (int not_used)
{ return 1; }
};
#elif defined(__sgi)
//
// Not necessarily relevant IRIX notes.
//
// Information about UDF support seems to be contradictory. Some manuals
// maintain that UDF writing is supported for DVD-RAM and hard disk, but
// the only mention of UDF in IRIX release notes is "6.5.18 introduces
// read-only support for the UDF filesystems format." If UDF writing is
// supported, then it was implemented presumably in 6.5.21. DVD-RAM
// writing at block device level was most likely introduced by then too.
//
// IRIX doesn't provide access to files larger than 2GB on ISO9660.
// That's because ISO9660 is implemented as NFSv2 user-land server,
// and 2GB limit is implied by NFSv2 protocol.
//
#ifdef PRIVATE
#undef PRIVATE // <sys/dsreq.h> conflicts with <sys/mman.h>?
#endif
#include <sys/dsreq.h>
#ifdef PRIVATE
#undef PRIVATE // <sys/dsreq.h> conflicts with <sys/mman.h>?
#endif
#include <mntent.h>
#include <sys/wait.h>
#include <poll.h>
#include <sys/attributes.h>
#include <sys/param.h>
#include <mediad.h>
typedef enum { NONE=0,
READ=DSRQ_READ,
WRITE=DSRQ_WRITE
} Direction;
class Scsi_Command {
private:
int fd,autoclose;
char *filename;
dsreq_t req;
unsigned char cdb[16], _sense[18];
public:
Scsi_Command() { fd=-1, autoclose=1; filename=NULL; }
Scsi_Command(int f) { fd=f, autoclose=0; filename=NULL; }
Scsi_Command(void*f){ fd=(long)f, autoclose=0; filename=NULL; }
~Scsi_Command() { if (fd>=0 && autoclose) close(fd),fd=-1;
if (filename) free(filename),filename=NULL;
}
int associate (const char *file,const struct stat *ref=NULL)
{ struct stat sb;
char hw_path[MAXPATHLEN];
int hw_len=sizeof(hw_path)-1;
if (attr_get(file,"_devname",hw_path,&hw_len,0)) return 0;
if (hw_len>=sizeof(hw_path)) hw_len=sizeof(hw_path)-1; // paranoia
hw_path[hw_len]='\0';
if (ref)
{ // hw_path is maintained by kernel through hwgfs and
// I assume it's not subject to race conditions...
if (stat(hw_path,&sb)) return 0;
if (!S_ISCHR(ref->st_mode) || ref->st_rdev!=sb.st_rdev)
{ errno=ENXIO; return 0; }
}
if (strcmp(hw_path+strlen(hw_path)-5,"/scsi"))
{ char *s=strstr(hw_path,"/disk/");
if (s==NULL) { errno=EINVAL; return 0; }
strcpy (s,"/scsi");
}
fd=open (hw_path,O_RDONLY|O_NONBLOCK);
if (fd<0) return 0;
if (fstat(fd,&sb) < 0) return 0;
if (!S_ISCHR(sb.st_mode)) { errno=ENOTTY; return 0; }
filename=strdup(file);
return 1;
}
unsigned char &operator[] (size_t i)
{ if (i==0)
{ memset (&req,0,sizeof(req));
memset (cdb,0,sizeof(cdb));
memset (_sense,0,sizeof(_sense));
req.ds_cmdbuf = (caddr_t)cdb;
req.ds_sensebuf = (caddr_t)_sense;
req.ds_senselen = sizeof(_sense);
req.ds_flags = DSRQ_SENSE;
req.ds_time = 60*1000;
}
req.ds_cmdlen = i+1;
return cdb[i];
}
unsigned char &operator()(size_t i) { return _sense[i]; }
unsigned char *sense() { return _sense; }
void timeout(int i) { req.ds_time=i*1000; }
size_t residue() { return req.ds_datalen-req.ds_datasent; }
int transport(Direction dir=NONE,void *buf=NULL,size_t sz=0)
{ int ret=0,retries=3;
req.ds_databuf = (caddr_t)buf;
req.ds_datalen = sz;
req.ds_flags |= dir;
// I personally don't understand why do we need to loop, but
// /usr/share/src/irix/examples/scsi/lib/src/dslib.c is looping...
while (retries--)
{ if (ioctl(fd,DS_ENTER,&req) < 0) return -1;
if (req.ds_status==STA_GOOD) return 0;
if (req.ds_ret==DSRT_NOSEL) continue;
if (req.ds_status==STA_BUSY || req.ds_status==STA_RESERV)
{ poll(NULL,0,500); continue; }
break;
}
errno=EIO; ret=-1;
if (req.ds_status==STA_CHECK && req.ds_sensesent>=14)
{ ret = ERRCODE(_sense);
if (ret==0) ret=-1;
else CREAM_ON_ERRNO(_sense);
}
return ret;
}
// mimics umount(2), therefore inconsistent return values
int umount(int f=-1)
{ struct stat fsb,msb;
struct mntent *mb;
FILE *fp;
pid_t pid,rpid;
int ret=0,rval;
char hw_path[MAXPATHLEN];
int hw_len=sizeof(hw_path)-1;
if (f==-1) f=fd;
if (fstat (f,&fsb) < 0) return -1;
if (!getenv("MEDIAD_GOT_EXCLUSIVEUSE"))
{ if (attr_getf (f,"_devname",hw_path,&hw_len,0)) return -1;
if (hw_len>=sizeof(hw_path)) hw_len=sizeof(hw_path)-1;// paranoia
hw_path[hw_len]='\0';
// mediad even unmounts removable volumes. However! The
// locks are "granted" even for unmanaged devices, so
// it's not possible to tell if device is ignored through
// /etc/config/mediad.config or actually managed. Therefore
// I have to pass through own unmount code in either case...
mediad_get_exclusiveuse(hw_path,"dvd+rw-tools");
switch (mediad_last_error())
{ case RMED_NOERROR: break;
case RMED_EACCESS:
case RMED_ECANTUMOUNT: errno=EBUSY; return -1;
case RMED_ENOMEDIAD: break;
case -1: if(errno==ECONNREFUSED) break; // no mediad...
else return -1;
default: errno=ENOTTY; return -1;
}
}
if ((fp=setmntent (MOUNTED,"r"))==NULL) return -1;
while ((mb=getmntent (fp))!=NULL)
{ if (stat (mb->mnt_fsname,&msb) < 0) continue; // corrupted line?
// Following effectively catches only /dev/rdsk/dksXdYvol,
// which is sufficient for iso9660 volumes, but not for e.g.
// EFS formatted media. I mean code might have to be more
// versatile... Wait for feedback...
if (msb.st_rdev == fsb.st_rdev)
{ ret = -1;
if ((pid = fork()) == (pid_t)-1) break;
if (pid == 0) execl ("/sbin/umount","umount",mb->mnt_dir,(void*)NULL);
while (1)
{ rpid = waitpid (pid,&rval,0);
if (rpid == (pid_t)-1)
{ if (errno==EINTR) continue;
else break;
}
else if (rpid != pid)
{ errno = ECHILD;
break;
}
if (WIFEXITED(rval))
{ if (WEXITSTATUS(rval) == 0) ret=0;
else errno=EBUSY; // most likely
break;
}
else if (WIFSTOPPED(rval) || WIFCONTINUED(rval))
continue;
else
{ errno = ENOLINK; // some phony errno
break;
}
}
break;
}
}
endmntent (fp);
return ret;
}
#if 0 // for now just an idea to test...
#define RELOAD_NEVER_NEEDED
int is_reload_needed (int not_used)
{ return 0; }
#else
int is_reload_needed (int not_used)
{ return 1; }
#endif
};
#elif defined(_WIN32)
#if defined(__MINGW32__)
#include <ddk/ntddscsi.h>
#define FSCTL_LOCK_VOLUME CTL_CODE(FILE_DEVICE_FILE_SYSTEM,6,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define FSCTL_UNLOCK_VOLUME CTL_CODE(FILE_DEVICE_FILE_SYSTEM,7,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define FSCTL_DISMOUNT_VOLUME CTL_CODE(FILE_DEVICE_FILE_SYSTEM,8,METHOD_BUFFERED,FILE_ANY_ACCESS)
#else
#include <winioctl.h>
#include <ntddscsi.h>
#endif
typedef enum { NONE=SCSI_IOCTL_DATA_UNSPECIFIED,
READ=SCSI_IOCTL_DATA_IN,
WRITE=SCSI_IOCTL_DATA_OUT
} Direction;
typedef struct {
SCSI_PASS_THROUGH_DIRECT spt;
unsigned char sense[18];
} SPKG;
class Scsi_Command {
private:
HANDLE fd;
int autoclose;
char *filename;
SPKG p;
public:
Scsi_Command() { fd=INVALID_HANDLE_VALUE; autoclose=1; filename=NULL; }
Scsi_Command(void*f){ fd=f, autoclose=0; filename=NULL; }
~Scsi_Command() { DWORD junk;
if (fd!=INVALID_HANDLE_VALUE && autoclose)
{ if (autoclose>1)
DeviceIoControl(fd,FSCTL_UNLOCK_VOLUME,
NULL,0,NULL,0,&junk,NULL);
CloseHandle (fd),fd=INVALID_HANDLE_VALUE;
}
if (filename) free(filename),filename=NULL;
}
int associate (const char *file,const struct stat *ref=NULL)
{ char dev[32];
sprintf(dev,"%.*s\\",sizeof(dev)-2,file);
if (GetDriveType(dev)!=DRIVE_CDROM)
return errno=EINVAL,0;
sprintf(dev,"\\\\.\\%.*s",sizeof(dev)-5,file);
fd=CreateFile (dev,GENERIC_WRITE|GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL,OPEN_EXISTING,0,NULL);
if (fd!=INVALID_HANDLE_VALUE)
filename=strdup(dev);
return fd!=INVALID_HANDLE_VALUE;
}
unsigned char &operator[] (size_t i)
{ if (i==0)
{ memset(&p,0,sizeof(p));
p.spt.Length = sizeof(p.spt);
p.spt.DataIn = SCSI_IOCTL_DATA_UNSPECIFIED;
p.spt.TimeOutValue = 30;
p.spt.SenseInfoLength = sizeof(p.sense);
p.spt.SenseInfoOffset = offsetof(SPKG,sense);
}
p.spt.CdbLength = i+1;
return p.spt.Cdb[i];
}
unsigned char &operator()(size_t i) { return p.sense[i]; }
unsigned char *sense() { return p.sense; }
void timeout(int i) { p.spt.TimeOutValue=i; }
size_t residue() { return 0; } // bogus
int transport(Direction dir=NONE,void *buf=NULL,size_t sz=0)
{ DWORD bytes;
int ret=0;
p.spt.DataBuffer = buf;
p.spt.DataTransferLength = sz;
p.spt.DataIn = dir;
if (DeviceIoControl (fd,IOCTL_SCSI_PASS_THROUGH_DIRECT,
&p,sizeof(p.spt),
&p,sizeof(p),
&bytes,FALSE) == 0) return -1;
if (p.sense[0]&0x70)
{ SetLastError (ERROR_GEN_FAILURE);
ret = ERRCODE(p.sense);
if (ret==0) ret=-1;
else CREAM_ON_ERRNO(p.sense);
}
#if 0
else if (p.spt.Cdb[0] == 0x00) // TEST UNIT READY
{ unsigned char _sense[18];
operator[](0) = 0x03; // REQUEST SENSE
p.spt.Cdb[4] = sizeof(_sense);
p.spt.CdbLength = 6;
p.spt.DataBuffer = _sense;
p.spt.DataTransferLength = sizeof(_sense);
p.spt.DataIn = READ;
if (DeviceIoControl (fd,IOCTL_SCSI_PASS_THROUGH_DIRECT,
&p,sizeof(p.spt),
&p,sizeof(p),
&bytes,FALSE) == 0) return -1;
if ((ret = ERRCODE(_sense))) CREAM_ON_ERRNO(_sense);
}
#endif
return ret;
}
int umount (int f=-1)
{ DWORD junk;
HANDLE h = (f==-1) ? fd : (HANDLE)f;
if (DeviceIoControl(h,FSCTL_LOCK_VOLUME,NULL,0,NULL,0,&junk,NULL) &&
DeviceIoControl(h,FSCTL_DISMOUNT_VOLUME,NULL,0,NULL,0,&junk,NULL))
{ if (h==fd) autoclose++;
return 0;
}
return -1;
}
#define RELOAD_NEVER_NEEDED
int is_reload_needed (int not_used)
{ return 0; }
};
#elif defined(__APPLE__) && defined(__MACH__)
//
// This code targets Darwin Kernel Version 6.x, a.k.a. Mac OS X v10.2,
// or later, but upon initial release was tested only on PowerPC under
// Darwin Kernel Version 8.7.0, a.k.a. Mac OS X v10.4.7 (Tiger).
//
typedef off_t off64_t;
#define stat64 stat
#define fstat64 fstat
#define open64 open
#define pread64 pread
#define pwrite64 pwrite
#define lseek64 lseek
#include <sys/param.h>
#include <sys/mount.h>
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/scsi/SCSITaskLib.h>
static int iokit_err (IOReturn ioret,SCSITaskStatus stat,
const unsigned char *sense)
{ int ret=-1;
if (ioret==kIOReturnSuccess) ret = 0;
else if (ioret==kIOReturnNoDevice) errno = ENXIO;
else if (ioret==kIOReturnNoMemory) errno = ENOMEM;
else if (ioret==kIOReturnExclusiveAccess) errno = EBUSY;
else errno = EIO;
if (ret) return ret;
if (stat==kSCSITaskStatus_CHECK_CONDITION)
{ ret = ERRCODE(sense);
if (ret==0) errno=EIO, ret=-1;
else CREAM_ON_ERRNO(sense);
}
else if (stat!=kSCSITaskStatus_GOOD)
errno = EIO, ret = -1;
return ret;
}
// NB! ellipsis is GCC-ism, but conveniently Apple ships only gcc:-)
#define MMCIO(h,func,...) ({ \
MMCDeviceInterface **di = (MMCDeviceInterface **)h;\
SCSITaskStatus stat; \
union { \
SCSI_Sense_Data s; \
unsigned char u[18]; \
} sense; \
IOReturn ioret; \
memset (&sense,0,sizeof(sense)); \
ioret = (*di)->func(di,__VA_ARGS__,&stat,&sense.s); \
iokit_err (ioret,stat,sense.u); })
typedef enum { NONE=kSCSIDataTransfer_NoDataTransfer,
READ=kSCSIDataTransfer_FromTargetToInitiator,
WRITE=kSCSIDataTransfer_FromInitiatorToTarget
} Direction;
class Scsi_Command {
private:
int autoclose,_timeout;
char *filename;
io_object_t scsiob;
IOCFPlugInInterface **plugin;
MMCDeviceInterface **mmcdif;
SCSITaskDeviceInterface **taskif;
unsigned char cdb[16];
union {
SCSI_Sense_Data s;
unsigned char u[18];
} _sense;
size_t cdblen,resid;
public:
Scsi_Command()
{ scsiob=IO_OBJECT_NULL, plugin=NULL, mmcdif=NULL, taskif=NULL;
autoclose=1; filename=NULL;
}
Scsi_Command(void *f)
{ taskif = (SCSITaskDeviceInterface **)f, autoclose=0; filename=NULL; }
~Scsi_Command()
{ if (autoclose)
{ if (taskif) (*taskif)->ReleaseExclusiveAccess(taskif),
(*taskif)->Release(taskif), taskif=NULL;
if (mmcdif) (*mmcdif)->Release(mmcdif), mmcdif=NULL;
if (plugin) IODestroyPlugInInterface(plugin), plugin=NULL;
if (scsiob) IOObjectRelease(scsiob), scsiob=IO_OBJECT_NULL;
}
if (filename) free(filename), filename=NULL;
}
int associate (const char *file,const struct stat *ref=NULL)
{ struct stat sb;
io_object_t scsiob=IO_OBJECT_NULL,parent;
CFMutableDictionaryRef match,bsddev;
CFNumberRef num;
int i;
if (ref) sb = *ref;
else if (stat(file,&sb)) return 0;
if (!(S_ISBLK(sb.st_mode) || S_ISCHR(sb.st_mode)))
return !(errno=ENOTBLK);
if ((match = CFDictionaryCreateMutable(kCFAllocatorDefault,0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks))
== NULL) return !(errno=ENOMEM);
if ((bsddev = CFDictionaryCreateMutable(kCFAllocatorDefault,0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks))
== NULL) return CFRelease(match),!(errno=ENOMEM);
i = major(sb.st_rdev);
num = CFNumberCreate(kCFAllocatorDefault,kCFNumberIntType,&i);
CFDictionarySetValue(bsddev,CFSTR("BSD Major"),num);
CFRelease(num);
i = minor(sb.st_rdev);
num = CFNumberCreate(kCFAllocatorDefault,kCFNumberIntType,&i);
CFDictionarySetValue(bsddev,CFSTR("BSD Minor"),num);
CFRelease(num);
CFDictionarySetValue(match,CFSTR(kIOPropertyMatchKey),bsddev);
CFRelease(bsddev);
if ((scsiob = IOServiceGetMatchingService(kIOMasterPortDefault,match))
== IO_OBJECT_NULL) return !(errno=ENXIO);
// traverse up to "SCSITaskAuthoringDevice"
kern_return_t kret;
while ((kret=IORegistryEntryGetParentEntry(scsiob,kIOServicePlane,
&parent)) == kIOReturnSuccess)
{ CFStringRef uclient;
const char *s;
int cmp;
IOObjectRelease(scsiob);
scsiob = parent;
uclient = (CFStringRef)IORegistryEntryCreateCFProperty(scsiob,
CFSTR(kIOPropertySCSITaskDeviceCategory),
kCFAllocatorDefault,0);
if (uclient)
{ s = CFStringGetCStringPtr(uclient,kCFStringEncodingMacRoman);
cmp = strcmp(s,kIOPropertySCSITaskAuthoringDevice);
CFRelease(uclient);
if (cmp==0) break;
}
}
if (kret!=kIOReturnSuccess)
{ if (scsiob!=IO_OBJECT_NULL) IOObjectRelease(scsiob);
return !(errno=ENXIO);
}
SInt32 score=0;
if (IOCreatePlugInInterfaceForService(scsiob,
kIOMMCDeviceUserClientTypeID,
kIOCFPlugInInterfaceID,
&plugin,&score) != kIOReturnSuccess)
{ IOObjectRelease(scsiob);
return !(errno=ENXIO);
}
if ((*plugin)->QueryInterface(plugin,
CFUUIDGetUUIDBytes(kIOMMCDeviceInterfaceID),
(void**)&mmcdif) != S_OK)
{ IODestroyPlugInInterface(plugin), plugin=NULL;
IOObjectRelease(scsiob);
return !(errno=ENXIO);
}
if ((taskif = (*mmcdif)->GetSCSITaskDeviceInterface(mmcdif)) == NULL)
{ (*mmcdif)->Release(mmcdif), mmcdif=NULL;
IODestroyPlugInInterface(plugin), plugin=NULL;
IOObjectRelease(scsiob);
return !(errno=ENXIO);
}
//
// Note that in order to ObtainExclusiveAccess no corresponding
// /dev/[r]diskN may remain open by that time. For reference,
// acquiring exclusive access temporarily removes BSD block
// storage device from I/O registry as well as corresponding
// /dev entries.
//
if ((*taskif)->ObtainExclusiveAccess(taskif) != kIOReturnSuccess)
{ (*taskif)->Release(taskif), taskif=NULL;
(*mmcdif)->Release(mmcdif), mmcdif=NULL;
IODestroyPlugInInterface(plugin), plugin=NULL;
IOObjectRelease(scsiob), scsiob=IO_OBJECT_NULL;
return !(errno=EBUSY);
}
filename=strdup(file);
return 1;
}
unsigned char &operator[] (size_t i)
{ if (i==0)
{ memset (cdb,0,sizeof(cdb));
memset (&_sense,0,sizeof(_sense));
_timeout = 30;
}
cdblen = i+1;
return cdb[i];
}
unsigned char &operator()(size_t i) { return _sense.u[i]; }
unsigned char *sense() { return _sense.u; }
void timeout(int i) { _timeout=i; }
size_t residue() { return resid; }
int transport(Direction dir=NONE,void *buf=NULL,size_t sz=0)
{ int ret=0;
SCSITaskInterface **cmd;
SCSITaskStatus stat;
UInt64 bytes;
IOVirtualRange range = { (IOVirtualAddress)buf, sz };
resid = sz;
cmd = (*taskif)->CreateSCSITask(taskif);
if (cmd==NULL) return (errno=ENOMEM),-1;
(*cmd)->SetCommandDescriptorBlock(cmd,cdb,cdblen);
(*cmd)->SetScatterGatherEntries(cmd,&range,1,sz,dir);
(*cmd)->SetTimeoutDuration(cmd,_timeout*1000);
if ((*cmd)->ExecuteTaskSync(cmd,&_sense.s,&stat,&bytes)
!= kIOReturnSuccess)
errno=EIO, ret=-1;
else if (stat==kSCSITaskStatus_GOOD)
{ resid = sz - bytes; }
else if (stat==kSCSITaskStatus_CHECK_CONDITION)
{ ret = ERRCODE(_sense.u);
if (ret==0) errno=EIO, ret=-1;
else CREAM_ON_ERRNO(_sense.u);
}
else
{ //SCSIServiceResponse resp;
//(*taskif)->GetSCSIServiceResponse(taskif,&resp);
errno=EIO, ret=-1;
}
(*cmd)->Release(cmd);
return ret;
}
int umount(int f=-1)
{ struct stat sb;
dev_t ref;
int i,n;
if (f>=0)
{ if (fstat (f,&sb)) return -1;
if (!S_ISCHR(sb.st_mode)) return errno=ENOTBLK,-1;
ref = sb.st_rdev;
// /dev/rdiskN and /dev/diskN have same st_rdev
}
else
{ char bsdname [16];
CFStringRef devname = (CFStringRef)IORegistryEntrySearchCFProperty (
scsiob,kIOServicePlane,
CFSTR("BSD Name"),
kCFAllocatorDefault,
kIORegistryIterateRecursively);
if (devname==NULL) return 0; // already exclusive
sprintf (bsdname,"/dev/%.*s",(int)(sizeof(bsdname)-6),
CFStringGetCStringPtr (devname,0));
CFRelease (devname);
if (stat (bsdname,&sb)) return -1;
ref = sb.st_rdev;
}
if ((n=getfsstat (NULL,0,MNT_NOWAIT)) < 0) return -1;
n += 4, n *= sizeof(struct statfs);
struct statfs *p = (struct statfs *)alloca(n);
if ((n=getfsstat (p,n,MNT_NOWAIT)) < 0) return -1;
for (i=0;i<n;i++,p++)
{ if (stat (p->f_mntfromname,&sb)==0 &&
S_ISBLK(sb.st_mode) &&
sb.st_rdev==ref)
#if 0 // looks neat, but causes irritaing popups on console...
return unmount (p->f_mntonname,0);
#else
{ int ret=0,rval;
pid_t pid,rpid;
ret = -1;
if ((pid = fork()) == (pid_t)-1) return -1;
if (pid == 0) // if diskutil will be proven broken,
// don't allow growisofs to be used as
// attack vector...
setuid (getuid ()),
execl ("/usr/sbin/diskutil",
"diskutil","unmount",
p->f_mntonname,(void*)NULL),
exit (errno);
while (1)
{ rpid = waitpid (pid,&rval,0);
if (rpid == (pid_t)-1)
{ if (errno==EINTR) continue;
else break;
}
else if (rpid != pid)
{ errno = ECHILD;
break;
}
if (WIFEXITED(rval))
{ if (WEXITSTATUS(rval) == 0) ret=0;
else errno=EBUSY; // most likely
break;
}
else if (WIFSTOPPED(rval) || WIFCONTINUED(rval))
continue;
else
{ errno = ENOLINK; // some phony errno
break;
}
}
// diskutil(8) seem to unmount only volfs-managed
// media, so I check if it managed to unmount and
// try the system call if it didn't...
if (ret==0)
{ struct statfs fs;
if (statfs (p->f_mntonname,&fs)==0 &&
!strcmp (fs.f_mntfromname,p->f_mntfromname))
return unmount (p->f_mntonname,0);
}
return ret;
}
#endif
}
return 0; // not mounted?
}
#define RELOAD_NEVER_NEEDED
int is_reload_needed(int not_used)
{ return 0; }
};
#else
#error "Unsupported OS"
#endif
#define DUMP_EVENTS 0
static int handle_events (Scsi_Command &cmd)
{ unsigned char event[8];
unsigned short profile=0,started=0;
int err,ret=0;
unsigned int descr;
static unsigned char events=0xFF; // "All events"
while (events)
{ cmd[0] = 0x4A; // GET EVENT
cmd[1] = 1; // "Polled"
cmd[4] = events;
cmd[8] = sizeof(event);
cmd[9] = 0;
if ((err=cmd.transport(READ,event,sizeof(event))))
{ events=0;
sperror ("GET EVENT",err);
return ret;
}
events = event[3];
if ((event[2]&7) == 0 ||
(event[0]<<8|event[1]) == 2 ||
(event[4]&0xF) == 0 ) // No Changes
return ret;
descr = event[4]<<24|event[5]<<16|event[6]<<8|event[7];
#if DUMP_EVENTS
fprintf(stderr,"< %d[%08x],%x >\n",event[2],descr,events);
#endif
switch(event[2]&7)
{ case 0: return ret; // No [supported] events
case 1: ret |= 1<<1; // Operational Change
if ((descr&0xFFFF) < 3)
goto read_profile;
start_unit:
if (!started)
{ cmd[0]=0x1B; // START STOP UNIT
cmd[4]=1; // "Start"
cmd[5]=0;
if ((err=cmd.transport()) && err!=0x62800)
sperror ("START UNIT",err);
started=1, profile=0;
}
read_profile:
if (!profile)
{ cmd[0] = 0x46; // GET CONFIGURATION
cmd[8] = sizeof(event);
cmd[9] = 0;
if (!cmd.transport(READ,event,sizeof(event)))
profile=event[6]<<8|event[7];
}
break;
case 2: ret |= 1<<2; // Power Management
if (event[5]>1) // State is other than Active
goto start_unit;
break;
case 3: ret |= 1<<3; break; // External Request
case 4: ret |= 1<<4; // Media
if (event[5]&2) // Media in
goto start_unit;
break;
case 5: ret |= 1<<5; break; // Multiple Initiators
case 6: // Device Busy
if ((event[4]&0xF)==1 && // Timeout occured
(event[5]&0x3)!=0)
{ poll(NULL,0,(descr&0xFFFF)*100+100);
cmd[0] = 0; // TEST UNIT READY
cmd[5] = 0;
if ((err=cmd.transport()))
sperror("TEST UNIT READY",err);
ret |= 1<<6;
}
break;
case 7: ret |= 1<<7; break; // Reserved
}
}
return ret;
}
#undef DUMP_EVENTS
static int wait_for_unit (Scsi_Command &cmd,volatile int *progress=NULL)
{ unsigned char *sense=cmd.sense(),sensebuf[18];
int err;
long msecs=1000;
while (1)
{ if (msecs > 0) poll(NULL,0,msecs);
msecs = getmsecs();
cmd[0] = 0; // TEST UNIT READY
cmd[5] = 0;
if (!(err=cmd.transport ())) break;
// I wish I could test just for sense.valid, but (at least)
// hp dvd100i returns 0 in valid bit at this point:-( So I
// check for all bits...
if ((sense[0]&0x70)==0)
{ perror (":-( unable to TEST UNIT READY");
return -1;
}
else if (sense[12]==0x3A) // doesn't get any further than "no media"
return err;
while (progress)
{ if (sense[15]&0x80)
{ *progress = sense[16]<<8|sense[17];
break;
}
// MMC-3 (draft) specification says that the unit should
// return progress indicator in key specific bytes even
// in reply to TEST UNIT READY. I.e. as above! But (at
// least) hp dvd100i doesn't do that and I have to fetch
// it separately:-(
cmd[0] = 0x03; // REQUEST SENSE
cmd[4] = sizeof(sensebuf);
cmd[5] = 0;
if ((err=cmd.transport (READ,sensebuf,sizeof(sensebuf))))
{ sperror ("REQUEST SENSE",err);
return err;
}
if (sensebuf[15]&0x80)
*progress = sensebuf[16]<<8|sensebuf[17];
break;
}
msecs = 1000 - (getmsecs() - msecs);
}
return 0;
}
static int pioneer_stop(Scsi_Command &cmd,volatile int *progress=NULL)
{ int err;
unsigned char *sense=cmd.sense();
// Pioneer units tend to just fall through wait_for_unit() and
// report "OP IN PROGRESS" on next non-TEST UNIT READY command.
// This behaviour is explicitly discouraged in MMC, but what one
// can do? Well, whenever appropriate one can deploy STOP UNIT
// as "barrier" command with all units...
while (1)
{ cmd[0] = 0x1B; // START/STOP UNIT
cmd[1] = 0x1; // "IMMED"
cmd[4] = 0; // "Stop"
cmd[5] = 0;
if ((err=cmd.transport()) == 0x20407) // "OP IN PROGRESS"
{ if (progress && sense[15]&0x80)
*progress = sense[16]<<8|sense[17];
poll (NULL,0,333);
continue;
}
break;
}
return err;
}
#define FEATURE21_BROKEN 1
static void page05_setup (Scsi_Command &cmd, unsigned short profile=0,
unsigned char p32=0xC0)
// 5 least significant bits of p32 go to p[2], Test Write&Write Type
// 2 most significant bits go to p[3], Multi-session field
// 0xC0 means "Multi-session, no Test Write, Incremental"
{ unsigned int len,bdlen;
unsigned char header[12],track[32],*p;
#if !FEATURE21_BROKEN
unsigned char feature21[24];
#endif
int err;
class autofree page05;
if (profile==0)
{ unsigned char prof[8];
cmd[0] = 0x46; // GET CONFIGURATION
cmd[8] = sizeof(prof);
cmd[9] = 0;
if ((err=cmd.transport(READ,prof,sizeof(prof))))
sperror ("GET CONFIGURATION",err), exit(FATAL_START(errno));
profile = prof[6]<<8|prof[7];
}
#if !FEATURE21_BROKEN
if (profile==0x11 || profile==0x14)
{ cmd[0] = 0x46; // GET CONFIGURATION
cmd[1] = 2; // ask for the only feature...
cmd[3] = 0x21; // the "Incremental Streaming Writable" one
cmd[8] = 8; // read the header only to start with
cmd[9] = 0;
if ((err=cmd.transport(READ,feature21,8)))
sperror ("GET CONFIGURATION",err), exit(FATAL_START(errno));
len = feature21[0]<<24|feature21[1]<<16|feature21[2]<<8|feature21[3];
len += 4;
if (len>sizeof(feature21))
len = sizeof(feature21);
else if (len<(8+8))
fprintf (stderr,":-( READ FEATURE DESCRIPTOR 0021h: insane length\n"),
exit(FATAL_START(EINVAL));
cmd[0] = 0x46; // GET CONFIGURATION
cmd[1] = 2; // ask for the only feature...
cmd[3] = 0x21; // the "Incremental Streaming Writable" one
cmd[8] = len; // this time with real length
cmd[9] = 0;
if ((err=cmd.transport(READ,feature21,len)))
sperror ("READ FEATURE DESCRIPTOR 0021h",err),
exit(FATAL_START(errno));
if ((feature21[8+2]&1)==0)
fprintf (stderr,":-( FEATURE 0021h is not in effect\n"),
exit(FATAL_START(EMEDIUMTYPE));
}
else
feature21[8+2]=0;
#endif
cmd[0] = 0x52; // READ TRACK INFORMATION
cmd[1] = 1; // TRACK INFORMATION
cmd[5] = 1; // track#1, in DVD context it's safe to assume
// that all tracks are in the same mode
cmd[8] = sizeof(track);
cmd[9] = 0;
if ((err=cmd.transport(READ,track,sizeof(track))))
sperror ("READ TRACK INFORMATION",err), exit(FATAL_START(errno));
// WRITE PAGE SETUP //
cmd[0] = 0x5A; // MODE SENSE
cmd[1] = 0x08; // "Disable Block Descriptors"
cmd[2] = 0x05; // "Write Page"
cmd[8] = sizeof(header); // header only to start with
cmd[9] = 0;
if ((err=cmd.transport(READ,header,sizeof(header))))
sperror ("MODE SENSE",err), exit(FATAL_START(errno));
len = (header[0]<<8|header[1])+2;
bdlen = header[6]<<8|header[7];
if (bdlen) // should never happen as we set "DBD" above
{ if (len <= (8+bdlen+14))
fprintf (stderr,":-( LUN is impossible to bear with...\n"),
exit(FATAL_START(EINVAL));
}
else if (len < (8+2+(unsigned int)header[9]))// SANYO does this.
len = 8+2+header[9];
page05 = (unsigned char *)malloc(len);
if (page05 == NULL)
fprintf (stderr,":-( memory exhausted\n"),
exit(FATAL_START(ENOMEM));
cmd[0] = 0x5A; // MODE SENSE
cmd[1] = 0x08; // "Disable Block Descriptors"
cmd[2] = 0x05; // "Write Page"
cmd[7] = len>>8;
cmd[8] = len; // real length this time
cmd[9] = 0;
if ((err=cmd.transport(READ,page05,len)))
sperror("MODE SENSE",err), exit(FATAL_START(errno));
len -= 2;
if (len < ((unsigned int)page05[0]<<8|page05[1])) // paranoia:-)
page05[0] = len>>8, page05[1] = len;
len = (page05[0]<<8|page05[1])+2;
bdlen = page05[6]<<8|page05[7];
len -= bdlen;
if (len < (8+14))
fprintf (stderr,":-( LUN is impossible to bear with...\n"),
exit(FATAL_START(EINVAL));
p = page05 + 8 + bdlen;
memset (p-8,0,8);
p[0] &= 0x7F;
// copy "Test Write" and "Write Type" from p32
p[2] &= ~0x1F, p[2] |= p32&0x1F;
p[2] |= 0x40; // insist on BUFE on
// setup Preferred Link Size
#if !FEATURE21_BROKEN
if (feature21[8+2]&1)
{ if (feature21[8+7]) p[2] |= 0x20, p[5] = feature21[8+8];
else p[2] &= ~0x20, p[5] = 0;
}
#else // At least Pioneer DVR-104 returns some bogus data in
// Preferred Link Size...
if (profile==0x11 || profile==0x14) // Sequential recordings...
p[2] |= 0x20, p[5] = 0x10;
#endif
else
p[2] &= ~0x20, p[5] = 0;
// copy Track Mode from TRACK INFORMATION
// [some DVD-R units (most notably Panasonic LF-D310), insist
// on Track Mode 5, even though it's effectively ignored]
p[3] &= ~0x0F, p[3] |= profile==0x11?5:(track[5]&0x0F);
// copy "Multi-session" bits from p32
p[3] &= ~0xC0, p[3] |= p32&0xC0;
if (profile == 0x13) // DVD-RW Restricted Overwrite
p[3] &= 0x3F; // always Single-session?
// setup Data Block Type
// Some units [e.g. Toshiba/Samsung TS-H542A] return "unknown Data
// Block Type" in track[6]&0x0F field. Essentially it's a firmware
// glitch, yet it makes certain sense, as track may not be written
// yet...
if ((track[6]&0x0F)==1 || (track[6]&0x0F)==0x0F)
p[4] = 8;
else fprintf (stderr,":-( none Mode 1 track\n"),
exit(FATAL_START(EMEDIUMTYPE));
// setup Packet Size
// [some DVD-R units (most notably Panasonic LF-D310), insist
// on fixed Packet Size of 16 blocks, even though it's effectively
// ignored]
p[3] |= 0x20, memset (p+10,0,4), p[13] = 0x10;
if (track[6]&0x10)
memcpy (p+10,track+20,4); // Fixed
else if (profile != 0x11)
p[3] &= ~0x20, p[13] = 0; // Variable
switch (profile)
{ case 0x13: // DVD-RW Restricted Overwrite
if (!(track[6]&0x10))
fprintf (stderr,":-( track is not formatted for fixed packet size\n"),
exit(FATAL_START(EMEDIUMTYPE));
break;
case 0x14: // DVD-RW Sequential Recording
case 0x11: // DVD-R Sequential Recording
if (track[6]&0x10)
fprintf (stderr,":-( track is formatted for fixed packet size\n"),
exit(FATAL_START(EMEDIUMTYPE));
break;
default:
#if 0
fprintf (stderr,":-( invalid profile %04xh\n",profile);
exit(FATAL_START(EMEDIUMTYPE));
#endif
break;
}
p[8] = 0; // "Session Format" should be ignored, but
// I reset it just in case...
cmd[0] = 0x55; // MODE SELECT
cmd[1] = 0x10; // conformant
cmd[7] = len>>8;
cmd[8] = len;
cmd[9] = 0;
if ((err=cmd.transport(WRITE,p-8,len)))
sperror ("MODE SELECT",err), exit(FATAL_START(errno));
// END OF WRITE PAGE SETUP //
}
#undef FEATURE21_BROKEN
#undef ERRCODE
#undef CREAM_ON_ERRNO
#undef CREAM_ON_ERRNO_NAKED