From dd85e37ac80b902a672cac57b7b6ebca275a5de9 Mon Sep 17 00:00:00 2001 From: Thomas Schmitt Date: Sat, 19 Dec 2009 14:34:48 +0000 Subject: [PATCH] Experimental SCSI transport adapter via GNU libcdio --- Makefile.am | 2 + configure.ac | 24 +- libburn/os-libcdio.h | 62 +++++ libburn/os.h | 10 + libburn/sg-freebsd-port.c | 16 +- libburn/sg-libcdio.c | 565 ++++++++++++++++++++++++++++++++++++++ libburn/sg.c | 6 + 7 files changed, 676 insertions(+), 9 deletions(-) create mode 100644 libburn/os-libcdio.h create mode 100644 libburn/sg-libcdio.c diff --git a/Makefile.am b/Makefile.am index 05275b0..90caf23 100644 --- a/Makefile.am +++ b/Makefile.am @@ -201,8 +201,10 @@ EXTRA_DIST = \ cdrskin/cleanup.c \ libburn/os-freebsd.h \ libburn/os-linux.h \ + libburn/os-libcdio.h \ libburn/sg-freebsd.c \ libburn/sg-linux.c \ + libburn/sg-libcdio.c \ COPYING \ NEWS \ ChangeLog \ diff --git a/configure.ac b/configure.ac index c96b479..7d104b7 100644 --- a/configure.ac +++ b/configure.ac @@ -206,10 +206,30 @@ else LIBBURN_DVD_OBS_64K= echo "disabled write size default 64 KB on DVD and BD" fi -dnl Avoid the need for libburn_libburn_la_CFLAGS in Makefile.am (ugly .o names) -dnl ### AC_SUBST(LIBBURN_DVD_OBS_64K) CFLAGS="$CFLAGS $LIBBURN_DVD_OBS_64K" +dnl ts A91218 +AC_ARG_ENABLE(libcdio, +[ --enable-libcdio Enable EXPERIMENTAL use of libcdio as system adapter, default=no], + , enable_libcdio=no) +if test x$enable_libcdio = xyes; then +dnl Check whether there is libcdio-devel and libcdio-runtime. +dnl If not, erase this macro + LIBCDIO_DEF="-DLibburn_use_libcdiO" +dnl The empty yes case obviously causes -lacl to be linked + AC_CHECK_HEADER(cdio/cdio.h, AC_CHECK_LIB(cdio, cdio_open, , LIBCDIO_DEF= ), LIBCDIO_DEF= ) +else + LIBCDIO_DEF= +fi +if test x$LIBCDIO_DEF = x +then + LIBCDIO_DEF= +else + echo "enabled EXPERIMENTAL use of libcdio as system adapter" + CFLAGS="$CFLAGS $LIBCDIO_DEF" +fi + + dnl Add compiler-specific flags diff --git a/libburn/os-libcdio.h b/libburn/os-libcdio.h new file mode 100644 index 0000000..6eb675d --- /dev/null +++ b/libburn/os-libcdio.h @@ -0,0 +1,62 @@ + +/* os-dummy.h + Operating system specific libburn definitions and declarations. Included + by os.h in case of compilation for + Unknown POSIX like systems + with GNU libcdio MMC transport adapter sg-libcdio.c + + Copyright (C) 2009 Thomas Schmitt , provided under GPL +*/ + + +/** List of all signals which shall be caught by signal handlers and trigger + a graceful abort of libburn. (See man 7 signal.) +*/ +/* Once as system defined macros */ +#define BURN_OS_SIGNAL_MACRO_LIST \ + SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGABRT, \ + SIGFPE, SIGSEGV, SIGPIPE, SIGALRM, SIGTERM, \ + SIGUSR1, SIGUSR2, SIGXCPU, SIGTSTP, SIGTTIN, \ + SIGTTOU + +/* Once as text 1:1 list of strings for messages and interpreters */ +#define BURN_OS_SIGNAL_NAME_LIST \ + "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGABRT", \ + "SIGFPE", "SIGSEGV", "SIGPIPE", "SIGALRM", "SIGTERM", \ + "SIGUSR1", "SIGUSR2", "SIGXCPU", "SIGTSTP", "SIGTTIN", \ + "SIGTTOU" + +/* The number of above list items */ +#define BURN_OS_SIGNAL_COUNT 16 + +/** To list all signals which shall surely not be caught */ +#define BURN_OS_NON_SIGNAL_MACRO_LIST \ +SIGKILL, SIGCHLD, SIGSTOP + +/* The number of above list items */ +#define BURN_OS_NON_SIGNAL_COUNT 3 + + +/* The maximum size for a (SCSI) i/o transaction */ +/* Important : MUST be at least 32768 ! */ +#define BURN_OS_TRANSPORT_BUFFER_SIZE 65536 + + +/* To hold the position of the most recently delivered address from + device enumeration. +*/ +struct burn_drive_enumerator_struct { + char **ppsz_cd_drives; + char **pos; +}; + +#define BURN_OS_DEFINE_DRIVE_ENUMERATOR_T \ +typedef struct burn_drive_enumerator_struct burn_drive_enumerator_t; + + +/* The list of operating system dependent elements in struct burn_drive. + Usually they are initialized in sg-*.c:enumerate_common(). +*/ +#define BURN_OS_TRANSPORT_DRIVE_ELEMENTS \ + void *p_cdio; /* actually a pointer to CdIo_t */ \ + diff --git a/libburn/os.h b/libburn/os.h index 9758a30..f283a4c 100644 --- a/libburn/os.h +++ b/libburn/os.h @@ -13,6 +13,15 @@ Operating system case distinction */ + +#ifdef Libburn_use_libcdiO + + +/* -------------------------- X/Open with GNU libcdio ---------------------- */ +#include "os-libcdio.h" + + +#else #ifdef __FreeBSD__ @@ -37,6 +46,7 @@ #endif /* ! __linux */ #endif /* ! __FreeBSD__ */ +#endif /* ! Libburn_use_libcdiO */ #endif /* ! BURN_OS_H_INCLUDED */ diff --git a/libburn/sg-freebsd-port.c b/libburn/sg-freebsd-port.c index e317212..9a3adec 100644 --- a/libburn/sg-freebsd-port.c +++ b/libburn/sg-freebsd-port.c @@ -323,7 +323,7 @@ try_item:; /* This spaghetti loop keeps the number of tabs small */ int scsi_enumerate_drives(void) { burn_drive_enumerator_t idx; - int initialize = 1; + int initialize = 1, ret; char buf[64]; while(1) { @@ -338,6 +338,7 @@ int scsi_enumerate_drives(void) idx.result->target_lun); } sg_give_next_adr(&idx, buf, sizeof(buf), -1); + return 1; } @@ -360,11 +361,12 @@ int sg_drive_is_open(struct burn_drive * d) */ int sg_grab(struct burn_drive *d) { - int count; struct cam_device *cam; - if(d->cam != NULL) - return 0; + if(d->cam != NULL) { + d->released = 0; + return 1; + } cam = cam_open_device(d->devname, O_RDWR); if (cam == NULL) { @@ -515,7 +517,7 @@ int sg_obtain_scsi_adr(char *path, int *bus_no, int *host_no, int *channel_no, int *target_no, int *lun_no) { burn_drive_enumerator_t idx; - int initialize = 1; + int initialize = 1, ret; char buf[64]; struct periph_match_result* result; @@ -524,7 +526,7 @@ int sg_obtain_scsi_adr(char *path, int *bus_no, int *host_no, int *channel_no, initialize = 0; if (ret <= 0) break; - if (strcmp(adr, buf) != 0) + if (strcmp(path, buf) != 0) continue; result = &(idx->ccb.cdm.matches[i].result.periph_result); *bus_no = result->path_id; @@ -546,7 +548,7 @@ int sg_obtain_scsi_adr(char *path, int *bus_no, int *host_no, int *channel_no, int sg_is_enumerable_adr(char* adr) { burn_drive_enumerator_t idx; - int initialize = 1; + int initialize = 1, ret; char buf[64]; while(1) { diff --git a/libburn/sg-libcdio.c b/libburn/sg-libcdio.c new file mode 100644 index 0000000..ba767cb --- /dev/null +++ b/libburn/sg-libcdio.c @@ -0,0 +1,565 @@ +/* -*- indent-tabs-mode: t; tab-width: 8; c-basic-offset: 8; -*- */ + +/* + +This is the main operating system dependent SCSI part of libburn. It implements +the transport level aspects of SCSI control and command i/o. + +Present implementation: GNU libcdio , for X/Open compliant operating systems + + +PORTING: + +Porting libburn typically will consist of adding a new operating system case +to the following switcher files: + os.h Operating system specific libburn definitions and declarations. + sg.c Operating system dependent transport level modules. +and of deriving the following system specific files from existing examples: + os-*.h Included by os.h. You will need some general system knowledge + about signals and knowledge about the storage object needs of your + transport level module sg-*.c. + + sg-*.c This source module. You will need special system knowledge about + how to detect all potentially available drives, how to open them, + eventually how to exclusively reserve them, how to perform + SCSI transactions, how to inquire the (pseudo-)SCSI driver. + You will not need to care about CD burning, MMC or other high-level + SCSI aspects. + +Said sg-*.c operations are defined by a public function interface, which has +to be implemented in a way that provides libburn with the desired services: + +sg_give_next_adr() iterates over the set of potentially useful drive + address strings. + +scsi_enumerate_drives() brings all available, not-whitelist-banned, and + accessible drives into libburn's list of drives. + +sg_drive_is_open() tells wether libburn has the given drive in use. + +sg_grab() opens the drive for SCSI commands and ensures + undisturbed access. + +sg_release() closes a drive opened by sg_grab() + +sg_issue_command() sends a SCSI command to the drive, receives reply, + and evaluates wether the command succeeded or shall + be retried or finally failed. + +sg_obtain_scsi_adr() tries to obtain SCSI address parameters. + +burn_os_stdio_capacity() estimates the emulated media space of stdio-drives. + +burn_os_open_track_src() opens a disk file in a way that allows best + throughput with file reading and/or SCSI write command + transmission. + +burn_os_alloc_buffer() allocates a memory area that is suitable for file + descriptors issued by burn_os_open_track_src(). + The buffer size may be rounded up for alignment + reasons. + +burn_os_free_buffer() delete a buffer obtained by burn_os_alloc_buffer(). + +Porting hints are marked by the text "PORTING:". +Send feedback to libburn-hackers@pykix.org . + +*/ + + +/** PORTING : ------- OS dependent headers and definitions ------ */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef Libburn_os_has_statvfS +#include +#endif /* Libburn_os_has_stavtfS */ + + +#include +#include + + +/** PORTING : ------ libburn portable headers and definitions ----- */ + +#include "transport.h" +#include "drive.h" +#include "sg.h" +#include "spc.h" +/* collides with symbols of + #include "mmc.h" +*/ +#include "sbc.h" +#include "debug.h" +#include "toc.h" +#include "util.h" + +#include "libdax_msgs.h" +extern struct libdax_msgs *libdax_messenger; + + +/* is in portable part of libburn */ +int burn_drive_is_banned(char *device_address); + + +/* Whether to log SCSI commands: + bit0= log in /tmp/libburn_sg_command_log + bit1= log to stderr + bit2= flush every line +*/ +extern int burn_sg_log_scsi; + + +/* ------------------------------------------------------------------------ */ +/* PORTING: Private functions. Port only if needed by public functions */ +/* (Public functions are listed below) */ +/* ------------------------------------------------------------------------ */ + + +static int sg_close_drive(struct burn_drive * d) +{ + CdIo_t *p_cdio; + + if (d->p_cdio != NULL) { + p_cdio = (CdIo_t *) d->p_cdio; + cdio_destroy(p_cdio); + d->p_cdio = NULL; + } + return 0; +} + + +/* ----------------------------------------------------------------------- */ +/* PORTING: Private functions which contain publicly needed functionality. */ +/* Their portable part must be performed. So it is probably best */ +/* to replace the non-portable part and to call these functions */ +/* in your port, too. */ +/* ----------------------------------------------------------------------- */ + + +/** Wraps a detected drive into libburn structures and hands it over to + libburn drive list. +*/ +static void enumerate_common(char *fname, int bus_no, int host_no, + int channel_no, int target_no, int lun_no) +{ + int ret; + struct burn_drive out; + + /* General libburn drive setup */ + burn_setup_drive(&out, fname); + + /* This transport adapter uses SCSI-family commands and models + (seems the adapter would know better than its boss, if ever) */ + ret = burn_scsi_setup_drive(&out, bus_no, host_no, channel_no, + target_no, lun_no, 0); + if (ret <= 0) + return; + + /* PORTING: ------------------- non portable part --------------- */ + + /* Transport adapter is libcdio */ + /* Adapter specific handles and data */ + out.p_cdio = NULL; + + /* PORTING: ---------------- end of non portable part ------------ */ + + /* Adapter specific functions with standardized names */ + out.grab = sg_grab; + out.release = sg_release; + out.drive_is_open = sg_drive_is_open; + out.issue_command = sg_issue_command; + /* Finally register drive and inquire drive information */ + burn_drive_finish_enum(&out); +} + + +/* ------------------------------------------------------------------------ */ +/* PORTING: Public functions. These MUST be ported. */ +/* ------------------------------------------------------------------------ */ + + +/** Returns the next index number and the next enumerated drive address. + The enumeration has to cover all available and accessible drives. It is + allowed to return addresses of drives which are not available but under + some (even exotic) circumstances could be available. It is on the other + hand allowed, only to hand out addresses which can really be used right + in the moment of this call. (This implementation chooses the latter.) + @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) +*/ +int sg_give_next_adr(burn_drive_enumerator_t *idx, + char adr[], int adr_size, int initialize) +{ + if (initialize == 1) { + idx->pos = idx->ppsz_cd_drives = + cdio_get_devices(DRIVER_DEVICE); + if (idx->ppsz_cd_drives == NULL) + return 0; + } else if (initialize == -1) { + if (*(idx->ppsz_cd_drives) != NULL) + cdio_free_device_list(idx->ppsz_cd_drives); + idx->ppsz_cd_drives = NULL; + } + if (idx->pos == NULL) + return 0; + if (*(idx->pos) == NULL) + return 0; + if (strlen(*(idx->pos)) >= adr_size) + return -1; + strcpy(adr, *(idx->pos)); + (idx->pos)++; + return 1; +} + + +/** Brings all available, not-whitelist-banned, and accessible drives into + libburn's list of drives. +*/ +int scsi_enumerate_drives(void) +{ + burn_drive_enumerator_t idx; + int initialize = 1, ret; + char buf[64]; + + while(1) { + ret = sg_give_next_adr(&idx, buf, sizeof(buf), initialize); + initialize = 0; + if (ret <= 0) + break; + if (burn_drive_is_banned(buf)) + continue; + + /* >>> try to obtain bus,host,channel,target,lun */; + + enumerate_common(buf, -1, -1, -1, -1, -1); + } + sg_give_next_adr(&idx, buf, sizeof(buf), -1); + return 1; +} + + +/** Tells wether libburn has the given drive in use or exclusively reserved. + If it is "open" then libburn will eventually call sg_release() on it when + it is time to give up usage resp. reservation. +*/ +/** Published as burn_drive.drive_is_open() */ +int sg_drive_is_open(struct burn_drive * d) +{ + return (d->p_cdio != NULL); +} + + +/** Opens the drive for SCSI commands and - if burn activities are prone + to external interference on your system - obtains an exclusive access lock + on the drive. (Note: this is not physical tray locking.) + A drive that has been opened with sg_grab() will eventually be handed + over to sg_release() for closing and unreserving. +*/ +int sg_grab(struct burn_drive *d) +{ + CdIo_t *p_cdio; + + if(d->p_cdio != NULL) { + d->released = 0; + return 1; + } + p_cdio = cdio_open(d->devname, DRIVER_DEVICE); + + if (p_cdio == NULL) { + libdax_msgs_submit(libdax_messenger, d->global_index, + 0x00020003, + LIBDAX_MSGS_SEV_SORRY, LIBDAX_MSGS_PRIO_HIGH, + "Could not grab drive", 0/*os_errno*/, 0); + return 0; + } + d->p_cdio = p_cdio; + d->released = 0; + return 1; +} + + +/** PORTING: Is mainly about the call to sg_close_drive() and whether it + implements the demanded functionality. +*/ +/** Gives up the drive for SCSI commands and releases eventual access locks. + (Note: this is not physical tray locking.) +*/ +int sg_release(struct burn_drive *d) +{ + if (d->p_cdio == NULL) { + burn_print(1, "release an ungrabbed drive. die\n"); + return 0; + } + sg_close_drive(d); + return 0; +} + + +/** Sends a SCSI command to the drive, receives reply and evaluates wether + the command succeeded or shall be retried or finally failed. + Returned SCSI errors shall not lead to a return value indicating failure. + The callers get notified by c->error. An SCSI failure which leads not to + a retry shall be notified via scsi_notify_error(). + The Libburn_log_sg_commandS facility might be of help when problems with + a drive have to be examined. It shall stay disabled for normal use. + @return: 1 success , <=0 failure +*/ +int sg_issue_command(struct burn_drive *d, struct command *c) +{ + int i_status; + unsigned int dxfer_len; + static FILE *fp = NULL; + mmc_cdb_t cdb = {{0, }}; + cdio_mmc_direction_t e_direction; + CdIo_t *p_cdio; + char msg[160]; + + c->error = 0; + if (d->p_cdio == NULL) { + return 0; + } + p_cdio = (CdIo_t *) d->p_cdio; + if (burn_sg_log_scsi & 1) { + if (fp == NULL) { + fp= fopen("/tmp/libburn_sg_command_log", "a"); + fprintf(fp, + "\n-----------------------------------------\n"); + } + } + if (burn_sg_log_scsi & 3) + scsi_log_cmd(c,fp,0); + + memcpy(cdb.field, c->opcode, c->oplen); + if (c->dir == TO_DRIVE) { + dxfer_len = c->page->bytes; + e_direction = SCSI_MMC_DATA_WRITE; + } else if (c->dir == FROM_DRIVE) { + if (c->dxfer_len >= 0) + dxfer_len = c->dxfer_len; + else + dxfer_len = BUFFER_SIZE; + e_direction = SCSI_MMC_DATA_READ; + /* touch page so we can use valgrind */ + memset(c->page->data, 0, BUFFER_SIZE); + } else { + dxfer_len = 0; +#ifdef SCSI_MMC_HAS_DIR_NONE + e_direction = SCSI_MMC_DATA_NONE; +#else + e_direction = SCSI_MMC_DATA_READ; +#endif + } + + /* >>> longer timeout , sg-linux has 200 s */ + + /* >>> retry-loop */ + + i_status = mmc_run_cmd(p_cdio, 10000, &cdb, e_direction, + dxfer_len, c->page->data); + + if (i_status == 0) + return 1; + + /* >>> One would need to get info about the nature of failure + SCSI SK,ASC,ASCQ would be nice. + One would need to distinguish between drive error and + transport error. + */; + +/* This is for failure of the transport mechanism itself: + + libdax_msgs_submit(libdax_messenger, + d->global_index, 0x0002010c, + LIBDAX_MSGS_SEV_FATAL, LIBDAX_MSGS_PRIO_HIGH, + "Failed to transfer command to drive", + errno, 0); + sg_close_drive(d); + d->released = 1; + d->busy = BURN_DRIVE_IDLE; +*/ + + /* >>> end retry-loop */ + + + if (c->opcode[0] != 0x00) { + sprintf(msg, "SCSI command %2.2Xh failed", + (unsigned int) c->opcode[0]); + libdax_msgs_submit(libdax_messenger, + d->global_index, 0x0002010f, + LIBDAX_MSGS_SEV_DEBUG, LIBDAX_MSGS_PRIO_HIGH, + msg, errno, 0); + } + + /* 2 04 00 LOGICAL UNIT NOT READY, CAUSE NOT REPORTABLE */ + c->sense[2] = 0x02; + c->sense[12] = 0x04; + c->sense[13] = 0x00; + + c->error = 1; + return -1; +} + + +/** Tries to obtain SCSI address parameters. + @return 1 is success , 0 is failure +*/ +int sg_obtain_scsi_adr(char *path, int *bus_no, int *host_no, int *channel_no, + int *target_no, int *lun_no) +{ + + /* >>> any chance to get them from libcdio ? */; + + *bus_no = *host_no = *channel_no = *target_no = *lun_no = -1; + return (0); +} + + +/** Tells wether a text is a persistent address as listed by the enumeration + functions. +*/ +int sg_is_enumerable_adr(char* adr) +{ + burn_drive_enumerator_t idx; + int initialize = 1, ret; + char buf[64]; + + while(1) { + ret = sg_give_next_adr(&idx, buf, sizeof(buf), initialize); + initialize = 0; + if (ret <= 0) + break; + if (strcmp(adr, buf) == 0) { + sg_give_next_adr(&idx, buf, sizeof(buf), -1); + return 1; + } + } + sg_give_next_adr(&idx, buf, sizeof(buf), -1); + return (0); +} + + +/** Estimate the potential payload capacity of a file address. + @param path The address of the file to be examined. If it does not + exist yet, then the directory will be inquired. + @param bytes The pointed value gets modified, but only if an estimation is + possible. + @return -2 = cannot perform necessary operations on file object + -1 = neither path nor dirname of path exist + 0 = could not estimate size capacity of file object + 1 = estimation has been made, bytes was set +*/ +int burn_os_stdio_capacity(char *path, off_t *bytes) +{ + struct stat stbuf; + +#ifdef Libburn_os_has_statvfS + struct statvfs vfsbuf; +#endif + + char testpath[4096], *cpt; + long blocks; + off_t add_size = 0; + + testpath[0] = 0; + blocks = *bytes / 512; + if (stat(path, &stbuf) == -1) { + strcpy(testpath, path); + cpt = strrchr(testpath, '/'); + if(cpt == NULL) + strcpy(testpath, "."); + else if(cpt == testpath) + testpath[1] = 0; + else + *cpt = 0; + if (stat(testpath, &stbuf) == -1) + return -1; + +#ifdef Libburn_if_this_was_linuX + + } else if(S_ISBLK(stbuf.st_mode)) { + fd = open(path, open_mode); + if (fd == -1) + return -2; + ret = ioctl(fd, BLKGETSIZE, &blocks); + close(fd); + if (ret == -1) + return -2; + *bytes = ((off_t) blocks) * (off_t) 512; + +#endif /* Libburn_if_this_was_linuX */ + + } else if(S_ISREG(stbuf.st_mode)) { + add_size = stbuf.st_blocks * (off_t) 512; + strcpy(testpath, path); + } else + return 0; + + if (testpath[0]) { + +#ifdef Libburn_os_has_statvfS + + if (statvfs(testpath, &vfsbuf) == -1) + return -2; + *bytes = add_size + ((off_t) vfsbuf.f_bsize) * + (off_t) vfsbuf.f_bavail; + +#else /* Libburn_os_has_statvfS */ + + return 0; + +#endif /* ! Libburn_os_has_stavtfS */ + + } + return 1; +} + + +/* ts A91122 : an interface to open(O_DIRECT) or similar OS tricks. */ + +#ifdef Libburn_read_o_direcT + + /* No special O_DIRECT-like precautions are implemented here */ + +#endif /* Libburn_read_o_direcT */ + + +int burn_os_open_track_src(char *path, int open_flags, int flag) +{ + int fd; + + fd = open(path, open_flags); + return fd; +} + + +void *burn_os_alloc_buffer(size_t amount, int flag) +{ + void *buf = NULL; + + buf = calloc(1, amount); + return buf; +} + + +int burn_os_free_buffer(void *buffer, size_t amount, int flag) +{ + if (buffer == NULL) + return 0; + free(buffer); + return 1; +} + diff --git a/libburn/sg.c b/libburn/sg.c index d527643..5dac767 100644 --- a/libburn/sg.c +++ b/libburn/sg.c @@ -5,6 +5,11 @@ */ +#ifdef Libburn_use_libcdiO + +#include "sg-libcdio.c" + +#else #ifdef __FreeBSD__ #include "sg-freebsd.c" @@ -38,4 +43,5 @@ static int intentional_compiler_warning(void) #endif /* ! __linux */ #endif /* ! __FreeBSD__ */ +#endif /* ! Libburn_use_libcdiO */