// // This is part of dvd+rw-tools by Andy Polyakov // // 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 #include #include #include #include #include #include #include #include inline long getmsecs() { struct timeval tv; gettimeofday (&tv,NULL); return tv.tv_sec*1000+tv.tv_usec/1000; } #include #ifndef EMEDIUMTYPE #define EMEDIUMTYPE EINVAL #endif #ifndef ENOMEDIUM #define ENOMEDIUM ENODEV #endif #include #define ENV_LOCALE "" #elif defined(_WIN32) #include #include #include #include #define ssize_t LONG_PTR #define off64_t __int64 #include "win32err.h" #define poll(a,b,t) Sleep(t) #define getmsecs() GetTickCount() #include #define ENV_LOCALE ".OCP" #endif #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; if (err==-1) fprintf (stderr,":-( unable to %s: ",cmd); 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 fprintf (stderr,":-[ %s failed with SK=%Xh/ASC=%02Xh/ACQ=%02Xh]: ", cmd,SK(err),ASC(err),ASCQ(err)); } 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)'±'); if (l>0) str[l]='\0'; else str[0]='±',str[1]='\0'; } ~__plusminus() { } operator char*(){ return str; } } plusminus; return plusminus; } #if defined(__linux) #include #include #include #include #include #include #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 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(sg_io.sbp); if (ret==0) ret=-1; else CREAM_ON_ERRNO(sg_io.sbp); } } 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 #include #include #include #include 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 #include #include #include #include #include #include #include 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 extern "C" int _dev_unmount(char *); // VolMgt ON Consolidation Private API #include #include #include #include #include #include #include #include #include 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; 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) { // 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 #include #include #include 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 // conflicts with ? #endif #include #ifdef PRIVATE #undef PRIVATE // conflicts with ? #endif #include #include #include #include #include #include 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 #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 #include #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 #include #include #include #include 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;if_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