diff --git a/NEWS b/NEWS index e2d3e2fd..a291890c 100644 --- a/NEWS +++ b/NEWS @@ -1,8 +1,10 @@ version 0.83 (git) -- Add Recording and retrieval of SCSI sense reply. -- Add Write/burning interface. +- Add retrieval SCSI sense reply from the most-recent MMC command. +- Add exclusive read/write access for devices which is used for experimental + writing/burning. Currently only on GNU/Linux and FreeBSD. - MMC bug fixes +- FreeBSD drive list now shows empty drives. - Add ability to retrieve SCSI tuple for a name and/or fake one up for programs that wanto to cd-record compatible. - Tolerance for OS's without timezone in their struct tm (e.g. Solaris) diff --git a/lib/driver/FreeBSD/freebsd.c b/lib/driver/FreeBSD/freebsd.c index e84db710..e3557deb 100644 --- a/lib/driver/FreeBSD/freebsd.c +++ b/lib/driver/FreeBSD/freebsd.c @@ -37,6 +37,19 @@ static const char _rcsid[] = "$Id: freebsd.c,v 1.38 2009/10/22 18:30:20 rocky Ex #include +/* For freebsd_dev_lock() */ +#include + +#ifdef HAVE_SYS_TYPES_H +# include +#endif +#ifdef _HAVE_SYS_STAT_H +# include +#endif +#ifdef HAVE_FCNTL_H +# include +#endif + #include static lba_t get_track_lba_freebsd(void *p_user_data, track_t i_track); @@ -52,10 +65,12 @@ str_to_access_mode_freebsd(const char *psz_access_mode) return _AM_IOCTL; else if (!strcmp(psz_access_mode, "CAM")) return _AM_CAM; + else if (!strcmp(psz_access_mode, "MMC_RDWR")) + return _AM_MMC_RDWR; else if (!strcmp(psz_access_mode, "MMC_RDWR_EXCL")) return _AM_MMC_RDWR_EXCL; else { - cdio_warn ("unknown access type: %s. Default ioctl used.", + cdio_warn ("unknown access type: %s. Default used.", psz_access_mode); return default_access_mode; } @@ -72,6 +87,7 @@ free_freebsd (void *p_obj) switch (p_env->access_mode) { case _AM_CAM: + case _AM_MMC_RDWR: case _AM_MMC_RDWR_EXCL: free_freebsd_cam(p_env); break; @@ -104,6 +120,7 @@ read_audio_sectors_freebsd (void *p_user_data, void *p_buf, lsn_t i_lsn, _img_private_t *p_env = p_user_data; switch (p_env->access_mode) { case _AM_CAM: + case _AM_MMC_RDWR: case _AM_MMC_RDWR_EXCL: return mmc_read_sectors( p_env->gen.cdio, p_buf, i_lsn, CDIO_MMC_READ_TYPE_CDDA, i_blocks); @@ -129,6 +146,7 @@ read_mode2_sector_freebsd (void *p_user_data, void *data, lsn_t i_lsn, switch (p_env->access_mode) { case _AM_CAM: + case _AM_MMC_RDWR: case _AM_MMC_RDWR_EXCL: return read_mode2_sector_freebsd_cam(p_env, data, i_lsn, b_form2); case _AM_IOCTL: @@ -151,6 +169,7 @@ read_mode2_sectors_freebsd (void *p_user_data, void *p_data, lsn_t i_lsn, _img_private_t *p_env = p_user_data; if ( (p_env->access_mode == _AM_CAM || + p_env->access_mode == _AM_MMC_RDWR || p_env->access_mode == _AM_MMC_RDWR_EXCL) && b_form2 ) { /* We have a routine that covers this case without looping. */ @@ -184,6 +203,7 @@ get_disc_last_lsn_freebsd (void *p_obj) switch (p_env->access_mode) { case _AM_CAM: + case _AM_MMC_RDWR: case _AM_MMC_RDWR_EXCL: return get_disc_last_lsn_mmc(p_env); case _AM_IOCTL: @@ -455,6 +475,7 @@ eject_media_freebsd (void *p_user_data) switch (p_env->access_mode) { case _AM_CAM: + case _AM_MMC_RDWR: case _AM_MMC_RDWR_EXCL: return eject_media_freebsd_cam(p_env); case _AM_IOCTL: @@ -479,6 +500,63 @@ audio_stop_freebsd (void *p_user_data) return ioctl(p_env->gen.fd, CDIOCSTOP); } +/*! + Produce a text composed from the system SCSI address tuple according to + habits of Linux 2.4 and 2.6 : "Bus,Host,Channel,Target,Lun" and store + it in generic_img_private_t.scsi_tuple. + Channel has no meaning on FreeBSD. Expect it to be 0. It is only in + the text to avoid an unnecessary difference in format. + Bus and Host will always be the same. + To be accessed via cdio_get_arg("scsi-tuple-freebsd") or "scsi-tuple". + For simplicity the FreeBSD driver also replies on "scsi-tuple-linux". + Drivers which implement this code have to return 5 valid decimal numbers + separated by comma, or empty text if no such numbers are available. + @return 1=success , 0=failure +*/ +static int +set_scsi_tuple_freebsd(_img_private_t *env) +{ + int bus_no = -1, host_no = -1, channel_no = -1, target_no = -1, lun_no = -1; + int ret; + char tuple[160]; + + ret = obtain_scsi_adr_freebsd_cam(env->gen.source_name, + &bus_no, &host_no, &channel_no, + &target_no, &lun_no); + if (ret != 1) + return 0; + if (env->gen.scsi_tuple != NULL) + free (env->gen.scsi_tuple); + env->gen.scsi_tuple = NULL; + if (bus_no < 0 || host_no < 0 || channel_no < 0 || target_no < 0 || + lun_no < 0) { + env->gen.scsi_tuple = strdup(""); + return 0; + } + sprintf(tuple, "%d,%d,%d,%d,%d", + bus_no, host_no, channel_no, target_no, lun_no); + env->gen.scsi_tuple = strdup(tuple); + return 1; +} + +static bool +is_mmc_supported(void *user_data) +{ + _img_private_t *env = user_data; + switch (env->access_mode) { + case _AM_IOCTL: + case _AM_NONE: + return false; + case _AM_CAM: + case _AM_MMC_RDWR: + case _AM_MMC_RDWR_EXCL: + return true; + } + /* Not reached. */ + return false; +} + + /*! Return the value associated with the key "arg". */ @@ -495,11 +573,19 @@ get_arg_freebsd (void *user_data, const char key[]) return "ioctl"; case _AM_CAM: return "CAM"; + case _AM_MMC_RDWR: + return "MMC_RDWR"; case _AM_MMC_RDWR_EXCL: return "MMC_RDWR_EXCL"; case _AM_NONE: return "no access method"; } + } else if (strcmp (key, "scsi-tuple") == 0) { + if (env->gen.scsi_tuple == NULL) + set_scsi_tuple_freebsd(env); + return env->gen.scsi_tuple; + } else if (!strcmp (key, "mmc-supported?")) { + is_mmc_supported(user_data) ? "true" : "false"; } return NULL; } @@ -519,6 +605,7 @@ get_mcn_freebsd (const void *p_user_data) { switch (p_env->access_mode) { case _AM_CAM: + case _AM_MMC_RDWR: case _AM_MMC_RDWR_EXCL: return mmc_get_mcn(p_env->gen.cdio); case _AM_IOCTL: @@ -540,6 +627,7 @@ get_drive_cap_freebsd (const void *p_user_data, switch (p_env->access_mode) { case _AM_CAM: + case _AM_MMC_RDWR: case _AM_MMC_RDWR_EXCL: get_drive_cap_mmc (p_user_data, p_read_cap, p_write_cap, p_misc_cap); case _AM_IOCTL: @@ -571,12 +659,17 @@ run_mmc_cmd_freebsd( void *p_user_data, unsigned int i_timeout_ms, unsigned int i_buf, /*in/out*/ void *p_buf ) { const _img_private_t *p_env = p_user_data; + int ret; switch (p_env->access_mode) { case _AM_CAM: + case _AM_MMC_RDWR: case _AM_MMC_RDWR_EXCL: - return run_mmc_cmd_freebsd_cam( p_user_data, i_timeout_ms, i_cdb, p_cdb, - e_direction, i_buf, p_buf ); + ret = run_mmc_cmd_freebsd_cam( p_user_data, i_timeout_ms, i_cdb, p_cdb, + e_direction, i_buf, p_buf ); + if (ret != 0) + return DRIVER_OP_ERROR; + return 0; case _AM_IOCTL: return DRIVER_OP_UNSUPPORTED; case _AM_NONE: @@ -680,7 +773,7 @@ cdio_get_devices_freebsd (void) char drive[40]; char **drives = NULL; unsigned int num_drives=0; - bool exists=true; + bool exists = true, have_cam_drive = false; char c; /* Scan the system for CD-ROM drives. @@ -705,15 +798,31 @@ cdio_get_devices_freebsd (void) */ /* Scan SCSI and CAM devices */ + exists = true; for ( c='0'; exists && c <='9'; c++ ) { sprintf(drive, "/dev/cd%c%s", c, DEVICE_POSTFIX); exists = cdio_is_cdrom(drive, NULL); if ( exists ) { cdio_add_device_list(&drives, drive, &num_drives); + have_cam_drive = true; } } - /* Scan are ATAPI devices */ + /* Scan ATAPI devices */ + + /* ??? ts 9 Jan 2009 + For now i assume atapicam running if a cdN device was found. + man atapicam strongly discourages to mix usage of CAM and ATAPI device. + So on the risk to sabotage systems without atapicam but with real old + SCSI drives, i list no ATAPI addresses if there was a CAM/SCSI address. + + exists = !have_cam_drive; + + ts 13 Jan 2009 + Regrettably USB drives appear as SCSI drives. We rather need to determine + whether atapicam runs, or to make pairs of cd and acd. + + */ exists = true; for ( c='0'; exists && c <='9'; c++ ) { @@ -854,6 +963,108 @@ get_access_mode(const char *psz_source) } return "CAM"; } + + +/* Lock the inode associated to dev_fd and the inode associated to devname. + Return OS errno, number of pass device of dev_fd, locked fd to devname, + error message. + A return value of > 0 means success, <= 0 means failure. +*/ +static int freebsd_dev_lock(int dev_fd, char *devname, + int *os_errno, int *pass_dev_no, int *lock_fd, char msg[4096], + int flag) +{ + int lock_denied = 0, fd_stbuf_valid, name_stbuf_valid, i, pass_l = 100; + int max_retry = 3, tries; + struct stat fd_stbuf, name_stbuf; + char pass_name[16], *lock_name; + + *os_errno = 0; + *pass_dev_no = -1; + *lock_fd = -1; + msg[0] = 0; + + fd_stbuf_valid = !fstat(dev_fd, &fd_stbuf); + + /* Try to find name of pass device by inode number */ + lock_name = (char *) "effective device"; + if(fd_stbuf_valid) { + for (i = 0; i < pass_l; i++) { + sprintf(pass_name, "/dev/pass%d", i); + if (stat(pass_name, &name_stbuf) != -1) + if(fd_stbuf.st_ino == name_stbuf.st_ino && + fd_stbuf.st_dev == name_stbuf.st_dev) + break; + } + if (i < pass_l) { + lock_name = pass_name; + *pass_dev_no = i; + } + } + + name_stbuf_valid = !stat(devname, &name_stbuf); + for (tries= 0; tries <= max_retry; tries++) { + lock_denied = flock(dev_fd, LOCK_EX | LOCK_NB); + *os_errno = errno; + if (lock_denied) { + if (errno == EAGAIN && tries < max_retry) { + /* <<< debugging + fprintf(stderr, "\nlibcdio_DEBUG: EAGAIN , tries= %d\n", tries); + */ + usleep(2000000); + continue; + } + sprintf(msg, + "Device busy. flock(LOCK_EX) failed on %s of %s", + strlen(lock_name) > 2000 || *pass_dev_no < 0 ? + "pass device" : lock_name, + strlen(devname) > 2000 ? "drive" : devname); + return 0; + } + break; + } + +/* + fprintf(stderr, "libburn_DEBUG: flock obtained on %s of %s\n", + lock_name, devname); +*/ + + /* Eventually lock the official device node too */ + if (fd_stbuf_valid && name_stbuf_valid && + (fd_stbuf.st_ino != name_stbuf.st_ino || + fd_stbuf.st_dev != name_stbuf.st_dev)) { + + *lock_fd = open(devname, O_RDONLY); + if (*lock_fd == 0) { + close(*lock_fd); + *lock_fd = -1; + } if (*lock_fd > 0) { + for (tries = 0; tries <= max_retry; tries++) { + lock_denied = flock(*lock_fd, LOCK_EX | LOCK_NB); + if (lock_denied) { + if (errno == EAGAIN && tries < max_retry) { + usleep(2000000); + continue; + } + close(*lock_fd); + *lock_fd = -1; + sprintf(msg, "Device busy. flock(LOCK_EX) failed on %s", + strlen(devname) > 4000 ? "drive" : devname); + return 0; + } + break; + } + } + +/* + fprintf(stderr, "libburn_DEBUG: flock obtained on %s\n", + devname); +*/ + + } + return 1; +} + #endif /*HAVE_FREEBSD_CDROM*/ /*! @@ -882,6 +1093,7 @@ cdio_open_am_freebsd (const char *psz_orig_source_name, CdIo *ret; _img_private_t *_data; char *psz_source_name; + int open_access_mode; /* Access mode passed to cdio_generic_init. */ if (!psz_access_mode) psz_access_mode = get_access_mode(psz_orig_source_name); @@ -959,7 +1171,39 @@ cdio_open_am_freebsd (const char *psz_orig_source_name, ret = cdio_new ((void *)_data, &_funcs); if (ret == NULL) return NULL; - if (cdio_generic_init(_data, O_RDONLY)) + open_access_mode = 0; + if (_AM_MMC_RDWR == _data->access_mode) { + open_access_mode |= O_RDWR; + } else if (_AM_MMC_RDWR_EXCL == _data->access_mode) { + open_access_mode |= O_RDWR; + } else { + open_access_mode |= O_RDONLY; + } +/* + fprintf(stderr, + "libcdio_DEBUG: am = %d (MMC_RDWR_EXCL = %d), open = %d (O_RDWR = %d)\n", + _data->access_mode, _AM_MMC_RDWR_EXCL, open_access_mode, O_RDWR); +*/ + + if (cdio_generic_init(_data, open_access_mode)) { + if (_AM_MMC_RDWR_EXCL == _data->access_mode) { + int os_errno, pass_dev_no = -1, flock_fd = -1, lock_result; + char msg[4096]; + + lock_result = freebsd_dev_lock(_data->gen.fd, _data->gen.source_name, + &os_errno, &pass_dev_no, &flock_fd, msg, 0); + if (lock_result <= 0) { + cdio_warn ("%s", msg); + cdio_generic_free (_data); + return NULL; + } + /* One should rather keep this fd open until _data->gen.fd gets closed. + It eventually locks a device sibling of _data->gen.source_name. + */ + if (flock_fd > 0) + close(flock_fd); + } + if ( _data->access_mode == _AM_IOCTL ) { return ret; } else { @@ -970,7 +1214,7 @@ cdio_open_am_freebsd (const char *psz_orig_source_name, return NULL; } } - else { + } else { cdio_generic_free (_data); return NULL; } diff --git a/lib/driver/FreeBSD/freebsd.h b/lib/driver/FreeBSD/freebsd.h index 95aaf3b4..79be56e7 100644 --- a/lib/driver/FreeBSD/freebsd.h +++ b/lib/driver/FreeBSD/freebsd.h @@ -114,6 +114,7 @@ typedef enum { _AM_NONE, _AM_IOCTL, _AM_CAM, + _AM_MMC_RDWR, _AM_MMC_RDWR_EXCL, } access_mode_t; @@ -212,7 +213,7 @@ bool read_toc_freebsd_ioctl (_img_private_t *env); Return 0 if no error. */ -int run_mmc_cmd_freebsd_cam( const void *p_user_data, +int run_mmc_cmd_freebsd_cam( void *p_user_data, unsigned int i_timeout_ms, unsigned int i_cdb, const mmc_cdb_t *p_cdb, @@ -228,4 +229,12 @@ lsn_t get_disc_last_lsn_freebsd_ioctl (_img_private_t *_obj); bool init_freebsd_cam (_img_private_t *env); void free_freebsd_cam (void *user_data); +/** Try to obtain SCSI address tuple of path. + @return 1 is success , 0 is failure +*/ +int obtain_scsi_adr_freebsd_cam(char *path, + int *bus_no, int *host_no, int *channel_no, + int *target_no, int *lun_no); + + #endif /*HAVE_FREEBSD_CDROM*/ diff --git a/lib/driver/FreeBSD/freebsd_cam.c b/lib/driver/FreeBSD/freebsd_cam.c index 5ff439e7..a7e26873 100644 --- a/lib/driver/FreeBSD/freebsd_cam.c +++ b/lib/driver/FreeBSD/freebsd_cam.c @@ -1,7 +1,5 @@ /* - $Id: freebsd_cam.c,v 1.12 2008/04/21 18:30:20 karl Exp $ - - Copyright (C) 2004, 2005, 2008, 2009 Rocky Bernstein + Copyright (C) 2004, 2005, 2008, 2009, 2010 Rocky Bernstein 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 @@ -52,16 +50,17 @@ static const char _rcsid[] = "$Id: freebsd_cam.c,v 1.12 2008/04/21 18:30:20 karl Return 0 if no error. */ int -run_mmc_cmd_freebsd_cam( const void *p_user_data, unsigned int i_timeout_ms, +run_mmc_cmd_freebsd_cam( void *p_user_data, unsigned int i_timeout_ms, unsigned int i_cdb, const mmc_cdb_t *p_cdb, cdio_mmc_direction_t e_direction, unsigned int i_buf, /*in/out*/ void *p_buf ) { - const _img_private_t *p_env = p_user_data; - int i_status; + _img_private_t *p_env = p_user_data; + int i_status, sense_size; int direction = CAM_DEV_QFRZDIS; union ccb ccb; + p_env->gen.scsi_mmc_sense_valid = 0; if (!p_env || !p_env->cam) return -2; memset(&ccb, 0, sizeof(ccb)); @@ -71,6 +70,8 @@ run_mmc_cmd_freebsd_cam( const void *p_user_data, unsigned int i_timeout_ms, ccb.ccb_h.target_lun = p_env->cam->target_lun; ccb.ccb_h.timeout = i_timeout_ms; + if (SCSI_MMC_DATA_NONE == e_direction) + i_buf = 0; if (!i_buf) direction |= CAM_DIR_NONE; else @@ -94,13 +95,28 @@ run_mmc_cmd_freebsd_cam( const void *p_user_data, unsigned int i_timeout_ms, { return 0; } + + /* Record SCSI sense reply for API call mmc_last_cmd_sense(). + */ + sense_size = ccb.csio.sense_len; + if (sense_size > sizeof(p_env->gen.scsi_mmc_sense)) + sense_size = sizeof(p_env->gen.scsi_mmc_sense); + memcpy((void *) p_env->gen.scsi_mmc_sense, &ccb.csio.sense_data, sense_size); + p_env->gen.scsi_mmc_sense_valid = sense_size; + errno = EIO; i_status = ERRCODE(((unsigned char *)&ccb.csio.sense_data)); if (i_status == 0) i_status = -1; else CREAM_ON_ERRNO(((unsigned char *)&ccb.csio.sense_data)); - cdio_warn ("transport failed: %d", i_status); + +/* There are many harmless or intentional reasons why to get an SCSI + error condition. Higher levels should decide whether this is an incident + or just a normal outcome. + + cdio_warn ("scsi error condition detected : 0x%X", i_status); +*/ return i_status; } @@ -257,4 +273,243 @@ eject_media_freebsd_cam (_img_private_t *p_env) SCSI_MMC_DATA_WRITE, 0, &buf); } + + +/* This is a CAM based device enumerator. + Currently its only purpose is to eventually obtain the info needed for + cdio_get_arg("scsi-tuple") +*/ +/* Stemming from code in libburn/sg-freebsd.c , + originally contributed by Alexander Nedotsukov , + without copyright claim to libburn in October 2006. + Contributed by libburn and adapted to libcdio without copyright claim + in January 2010. +*/ + +struct burn_drive_enumeration_state { + int fd; + union ccb ccb; + unsigned int i; + int skip_device; +}; +typedef struct burn_drive_enumeration_state *burn_drive_enumerator_t; + + +/* Some helper functions for scsi_give_next_adr() */ + +static int sg_init_enumerator(burn_drive_enumerator_t *idx_) +{ + struct burn_drive_enumeration_state *idx; + int bufsize; + + idx = malloc(sizeof(*idx)); + if (idx == NULL) { + cdio_warn("cannot malloc memory for CAM based drive enumerator"); + return -1; + } + idx->skip_device = 0; + + if ((idx->fd = open(XPT_DEVICE, O_RDWR)) == -1) { + cdio_warn("could not open %s (errno = %d \"%s\")", + XPT_DEVICE, errno, strerror(errno)); + free(idx); + idx = NULL; + return -1; + } + + bzero(&(idx->ccb), sizeof(union ccb)); + + idx->ccb.ccb_h.path_id = CAM_XPT_PATH_ID; + idx->ccb.ccb_h.target_id = CAM_TARGET_WILDCARD; + idx->ccb.ccb_h.target_lun = CAM_LUN_WILDCARD; + + idx->ccb.ccb_h.func_code = XPT_DEV_MATCH; + bufsize = sizeof(struct dev_match_result) * 100; + idx->ccb.cdm.match_buf_len = bufsize; + idx->ccb.cdm.matches = (struct dev_match_result *)malloc(bufsize); + if (idx->ccb.cdm.matches == NULL) { + cdio_warn("cannot malloc memory for CAM enumerator matches"); + close(idx->fd); + free(idx); + return -1; + } + idx->ccb.cdm.num_matches = 0; + idx->i = idx->ccb.cdm.num_matches; /* to trigger buffer load */ + + /* + * We fetch all nodes, since we display most of them in the default + * case, and all in the verbose case. + */ + idx->ccb.cdm.num_patterns = 0; + idx->ccb.cdm.pattern_buf_len = 0; + + *idx_ = idx; + + return 1; +} + + +static void sg_destroy_enumerator(burn_drive_enumerator_t *idx_) +{ + struct burn_drive_enumeration_state *idx = *idx_; + + if(idx->fd != -1) + close(idx->fd); + + free(idx->ccb.cdm.matches); + free(idx); + + *idx_ = NULL; +} + + +static int sg_next_enumeration_buffer(burn_drive_enumerator_t *idx_) +{ + struct burn_drive_enumeration_state *idx = *idx_; + + /* + * We do the ioctl multiple times if necessary, in case there are + * more than 100 nodes in the EDT. + */ + if (ioctl(idx->fd, CAMIOCOMMAND, &(idx->ccb)) == -1) { + cdio_warn("error sending CAMIOCOMMAND ioctl, (errno = %d \"%s\")", + errno, strerror(errno)); + return -1; + } + + if ((idx->ccb.ccb_h.status != CAM_REQ_CMP) + || ((idx->ccb.cdm.status != CAM_DEV_MATCH_LAST) + && (idx->ccb.cdm.status != CAM_DEV_MATCH_MORE))) { + cdio_warn("got CAM error %#x, CDM error %d\n", + idx->ccb.ccb_h.status, idx->ccb.cdm.status); + return -1; + } + return 1; +} + + +/** Returns the next index object state and the next enumerated drive address. + @param idx An opaque handle. Make no own theories about it. + @param adr Takes the reply + @param adr_size Gives maximum size of reply including final 0 + @param initialize 1 = start new, + 0 = continue, use no other values for now + -1 = finish + @return 1 = reply is a valid address , 0 = no further address available + -1 = severe error (e.g. adr_size too small) +*/ +/* This would be the public interface of the enumerator. + In libcdio it is private for now. +*/ +static +int give_next_adr_freebsd_cam(burn_drive_enumerator_t *idx_, + char adr[], int adr_size, int initialize) +{ + struct burn_drive_enumeration_state *idx; + int ret; + + if (initialize == 1) { + ret = sg_init_enumerator(idx_); + if (ret<=0) + return ret; + } else if (initialize == -1) { + sg_destroy_enumerator(idx_); + return 0; + } + + idx = *idx_; + + do { + if (idx->i >= idx->ccb.cdm.num_matches) { + ret = sg_next_enumeration_buffer(idx_); + if (ret<=0) + return -1; + idx->i = 0; + } else + (idx->i)++; + + while (idx->i < idx->ccb.cdm.num_matches) { + switch (idx->ccb.cdm.matches[idx->i].type) { + case DEV_MATCH_BUS: + break; + case DEV_MATCH_DEVICE: { + struct device_match_result* result; + + result = &(idx->ccb.cdm.matches[idx->i].result.device_result); + if (result->flags & DEV_RESULT_UNCONFIGURED) + idx->skip_device = 1; + else + idx->skip_device = 0; + break; + } + case DEV_MATCH_PERIPH: { + struct periph_match_result* result; + + result = &(idx->ccb.cdm.matches[idx->i].result.periph_result); + + /* A specialized CD drive enumerator would have to test for + strcmp(result->periph_name, "cd") != 0 + rather than + strcmp(result->periph_name, "pass") == 0 + */ + if (idx->skip_device || + strcmp(result->periph_name, "pass") == 0) + break; + + ret = snprintf(adr, adr_size, "/dev/%s%d", + result->periph_name, result->unit_number); + if(ret >= adr_size) + return -1; + + /* Found next enumerable address */ + return 1; + + } + default: + /* fprintf(stderr, "unknown match type\n"); */ + break; + } + (idx->i)++; + } + } while ((idx->ccb.ccb_h.status == CAM_REQ_CMP) + && (idx->ccb.cdm.status == CAM_DEV_MATCH_MORE)); + + return 0; +} + + +/** Try to obtain SCSI address tuple of path. + @return 1 is success , 0 is failure +*/ +int obtain_scsi_adr_freebsd_cam(char *path, + int *bus_no, int *host_no, int *channel_no, + int *target_no, int *lun_no) +{ + burn_drive_enumerator_t idx; + int ret; + char buf[64]; + struct periph_match_result* result; + + ret = sg_init_enumerator(&idx); + if (ret <= 0) + return 0; + while(1) { + ret = give_next_adr_freebsd_cam(&idx, buf, sizeof(buf), 0); + if (ret <= 0) + break; + if (strcmp(path, buf) == 0) { + result = &(idx->ccb.cdm.matches[idx->i].result.periph_result); + *bus_no = result->path_id; + *host_no = result->path_id; + *channel_no = 0; + *target_no = result->target_id; + *lun_no = result->target_lun; + sg_destroy_enumerator(&idx); + return 1; + } + } + sg_destroy_enumerator(&idx); + return (0); +} + #endif /* HAVE_FREEBSD_CDROM */ diff --git a/lib/driver/FreeBSD/freebsd_ioctl.c b/lib/driver/FreeBSD/freebsd_ioctl.c index ffa7cfbf..69adcc04 100644 --- a/lib/driver/FreeBSD/freebsd_ioctl.c +++ b/lib/driver/FreeBSD/freebsd_ioctl.c @@ -47,6 +47,17 @@ cdio_is_cdrom_freebsd_ioctl(char *drive, char *mnttype) if ( !cdio_is_device_quiet_generic(drive) ) { return(false); } + + /* ts 13 Jan 2009 + The ioctl below seems to be of little significance if one does not insist + in readable media. + Various of its failures are not caused by not being a CD drive + but by unreadable media situations. A burn program must handle these + situations rather than refusing to see the drive. + */ + return(true); + +#ifndef Libcdio_on_freeBSD_unsuitable_code /* If it does exist, verify that it's an available CD-ROM */ cdfd = open(drive, (O_RDONLY|O_EXCL|O_NONBLOCK), 0); @@ -56,16 +67,41 @@ cdio_is_cdrom_freebsd_ioctl(char *drive, char *mnttype) ENODEV means there's no drive present. */ if ( cdfd >= 0 ) { - if ( ioctl(cdfd, CDIOREADTOCHEADER, &tochdr) != -1 ) { + int ret; + + ret = ioctl(cdfd, CDIOREADTOCHEADER, &tochdr); + +/* + fprintf(stderr, "libcdio_DEBUG: ioctl(\"%s\", (O_RDONLY|O_EXCL|O_NONBLOCK) = %d (errno= %d)\n", drive, ret, errno); +*/ + + if ( ret != -1 ) { + is_cd = true; + } else if ( errno == ENXIO ) { /* Device not configured */ + /* ts 9 Jan 2010 , FreeBSD 8.0 + This error is issued with CAM device cd0 if no media is loaded. + */ + is_cd = true; + } else if ( errno == EIO ) { /* I/O error */ + /* ts 9 Jan 2010 , FreeBSD 8.0 + This error is issued with ATAPI device acd0 if no media is loaded. + */ + is_cd = true; + } else if ( errno == EINVAL ) { /* Invalid argument */ + /* ts 13 Jan 2010 , FreeBSD 8.0 + This error is issued with USB device cd1 if a blank CD-RW is loaded. + */ is_cd = true; } close(cdfd); - } - /* Even if we can't read it, it might be mounted */ - else if ( mnttype && (strcmp(mnttype, "iso9660") == 0) ) { + } else if ( mnttype && (strcmp(mnttype, "iso9660") == 0) ) { + /* Even if we can't read it, it might be mounted */ is_cd = true; } return(is_cd); + +#endif /* Libcdio_on_freeBSD_unsuitable_codE */ + } /*! diff --git a/lib/driver/gnu_linux.c b/lib/driver/gnu_linux.c index fce8117f..3406873b 100644 --- a/lib/driver/gnu_linux.c +++ b/lib/driver/gnu_linux.c @@ -387,8 +387,6 @@ get_arg_linux (void *env, const char key[]) } } else if (!strcmp (key, "scsi-tuple")) { return _obj->gen.scsi_tuple; - } else if (!strcmp (key, "scsi-tuple-linux")) { - return _obj->gen.scsi_tuple; } return NULL; } diff --git a/test/driver/mmc.c b/test/driver/mmc.c index 136377fb..761d0177 100644 --- a/test/driver/mmc.c +++ b/test/driver/mmc.c @@ -566,6 +566,10 @@ tmmc_test(char *drive_path, int flag) insufficient access mode "IOCTL" */ static int emul_lack_of_wperm = 0; + /* If non-0 then demand that cdio_get_arg(,"scsi-tuple") return non-NULL */ + static int demand_scsi_tuple = 0; + + const char *scsi_tuple; old_log_level = cdio_loglevel_default; verbose = flag & 1; @@ -589,6 +593,44 @@ tmmc_test(char *drive_path, int flag) */ cdio_loglevel_default = CDIO_LOG_WARN; + /* Test availability of info for cdrecord style adresses . + */ + scsi_tuple = cdio_get_arg(p_cdio, "scsi-tuple"); + if (scsi_tuple == NULL) { + if (demand_scsi_tuple) { + fprintf(stderr, "Error: cdio_get_arg(\"scsi-tuple\") returns NULL.\n"); + {ret = 6; goto ex;} + } else if (flag & 1) + fprintf(stderr, "cdio_get_arg(\"scsi-tuple\") returns NULL.\n"); + } else if (flag & 1) + printf("Drive '%s' has cdio_get_arg(\"scsi-tuple\") = '%s'\n", + drive_path, scsi_tuple); + + + /* Test availability of sense reply in case of unready drive. + E.g. if the tray is already ejected. + */ + ret = tmmc_test_unit_ready(p_cdio, &sense_avail, sense, !!verbose); + if (ret != 0 && sense_avail < 18) { + fprintf(stderr, + "Error: Drive not ready. Only %d sense bytes. Expected >= 18.\n", + sense_avail); + {ret = 2; goto ex;} + } + /* Provoke sense reply by requesting inappropriate mode page 3Eh */ + ret = tmmc_mode_sense(p_cdio, &sense_avail, sense, + 0x3e, 0, alloc_len, buf, &buf_fill, !!verbose); + if (ret != 0 && sense_avail < 18) { + fprintf(stderr, + "Error: Deliberately illegal command yields only %d sense bytes. Expected >= 18.\n", + sense_avail); + {ret = 2; goto ex;} + } else if(ret == 0) { + fprintf(stderr, + "Warning: tmmc_mode_sense() cannot provoke failure by mode page 3Eh\n"); + fprintf(stderr, + "Hint: Consider to set in tmmc_test(): with_tray_dance = 1\n"); + } /* Test availability of sense reply in case of unready drive. E.g. if the tray is already ejected. @@ -687,6 +729,7 @@ ex:; int main(int argc, const char *argv[]) { + const char *scsi_tuple; CdIo_t *p_cdio; char **ppsz_drives=NULL; const char *psz_source = NULL; @@ -705,6 +748,8 @@ main(int argc, const char *argv[]) p_cdio = cdio_open(ppsz_drives[0], DRIVER_DEVICE); if (p_cdio) { + const char *psz_have_mmc = cdio_get_arg(p_cdio, "mmc-supported?"); + psz_source = cdio_get_arg(p_cdio, "source"); if (0 != strncmp(psz_source, ppsz_drives[0], strlen(ppsz_drives[0]))) { @@ -713,19 +758,29 @@ main(int argc, const char *argv[]) psz_source, ppsz_drives[0]); exit(1); } + + if (psz_have_mmc && 0 == strncmp("true", psz_have_mmc, sizeof("true"))) { + scsi_tuple = cdio_get_arg(p_cdio, "scsi-tuple"); + if (scsi_tuple == NULL) { + fprintf(stderr, "cdio_get_arg(\"scsi-tuple\") returns NULL.\n"); + exit(3); + } else if (cdio_loglevel_default == CDIO_LOG_DEBUG) + printf("Drive '%s' has cdio_get_arg(\"scsi-tuple\") = '%s'\n", + psz_source, scsi_tuple); + + /* Test the MMC enhancements of version 0.83 in december 2009 */ + ret = tmmc_test(ppsz_drives[0], + cdio_loglevel_default == CDIO_LOG_DEBUG); + if (ret != 0) exit(ret + 16); + } + + cdio_destroy(p_cdio); + } else { - fprintf(stderr, "cdio_open_linux('%s') failed\n", ppsz_drives[0]); + fprintf(stderr, "cdio_open('%s') failed\n", ppsz_drives[0]); exit(2); } - cdio_destroy(p_cdio); - - - /* Test the MMC enhancements of version 0.83 in december 2009 */ - ret = tmmc_test(ppsz_drives[0], cdio_loglevel_default == CDIO_LOG_DEBUG); - if (ret != 0) - exit(ret + 16); - cdio_free_device_list(ppsz_drives); return 0;