Logo Search packages:      
Sourcecode: libgphoto2 version File versions  Download package

linux.c

/* SCSI commands to USB Mass storage devices port library for Linux
 * 
 *   Copyright (c) 2010 Hans de Goede <hdegoede@redhat.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser 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
 */
#include "config.h"
#include <gphoto2/gphoto2-port-library.h>

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#ifdef HAVE_FCNTL_H
# include <fcntl.h>
#endif
#ifdef HAVE_LIMITS_H
# include <limits.h>
#endif
#include <sys/stat.h>
#include <sys/types.h>
#ifdef HAVE_SYS_PARAM_H
# include <sys/param.h>
#endif
#ifdef HAVE_SYS_IOCTL_H
# include <sys/ioctl.h>
#endif
#ifdef HAVE_SCSI_SG_H
# include <scsi/sg.h>
#endif
#ifdef HAVE_LOCKDEV
#  include <lockdev.h>
#endif

#include <gphoto2/gphoto2-port-result.h>
#include <gphoto2/gphoto2-port-log.h>
#include <gphoto2/gphoto2-port.h>

#ifdef ENABLE_NLS
#  include <libintl.h>
#  undef _
#  define _(String) dgettext (GETTEXT_PACKAGE, String)
#  ifdef gettext_noop
#    define N_(String) gettext_noop (String)
#  else
#    define N_(String) (String)
#  endif 
#else
#  define textdomain(String) (String)
#  define gettext(String) (String)
#  define dgettext(Domain,Message) (Message)
#  define dcgettext(Domain,Message,Type) (Message)
#  define bindtextdomain(Domain,Directory) (Domain)
#  define _(String) (String)
#  define N_(String) (String)
#endif

#define CHECK(result) {int r=(result); if (r<0) return (r);}

struct _GPPortPrivateLibrary {
      int fd;       /* Device handle */
};

GPPortType
gp_port_library_type () 
{
      return GP_PORT_USB_SCSI;
}

static int
gp_port_usbscsi_lock (GPPort *port, const char *path)
{
#ifdef HAVE_LOCKDEV
      int pid;

      gp_log (GP_LOG_DEBUG, "gphoto2-port-usbscsi",
            "Trying to lock '%s'...", path);

      pid = dev_lock (path);
      if (pid) {
            if (port) {
                  if (pid > 0)
                        gp_port_set_error (port, _("Device '%s' is "
                              "locked by pid %d"), path, pid);
                  else
                        gp_port_set_error (port, _("Device '%s' could "
                              "not be locked (dev_lock returned "
                              "%d)"), path, pid);
            }
            return GP_ERROR_IO_LOCK;
      }
#else
# ifdef __GCC__
#  warning No locking library found. 
#  warning You will run into problems if you use
#  warning gphoto2 with a usbscsi picframe in 
#  warning combination with Konqueror (KDE) or Nautilus (GNOME).
#  warning This will *not* concern USB cameras.
# endif
#endif

      return GP_OK;
}

static int
gp_port_usbscsi_unlock (GPPort *port, const char *path)
{
#ifdef HAVE_LOCKDEV
      int pid;

      pid = dev_unlock (path, 0);
      if (pid) {
            if (port) {
                  if (pid > 0)
                        gp_port_set_error (port, _("Device '%s' could "
                              "not be unlocked as it is locked by "
                              "pid %d."), path, pid);
                  else
                        gp_port_set_error (port, _("Device '%s' could "
                              "not be unlocked (dev_unlock "
                              "returned %d)"), path, pid);
            }
            return GP_ERROR_IO_LOCK;
      }
#endif /* !HAVE_LOCKDEV */

      return GP_OK;
}

static const char *
gp_port_usbscsi_resolve_symlink (const char *link)
{
      ssize_t ret;
      static char path[PATH_MAX + 1];
      char *slash, buf[PATH_MAX + 1];
      struct stat st;
      int len;

      snprintf (path, sizeof(path), "%s", link);

      do {
            ret = readlink (path, buf, PATH_MAX);
            if (ret < 0)
                  return NULL;
            buf[ret] = 0;

            slash = strrchr (path, '/');
            if (buf[0] == '/' || slash == NULL) {
                  snprintf (path, sizeof(path), "%s", buf);
            } else {
                  *slash = 0;
                  len = strlen (path);
                  snprintf (path + len, sizeof (path) - len, "/%s", buf);
            }

            if (stat (path, &st))
                  return NULL;
      } while (S_ISLNK(st.st_mode));

      return path;
}

static int 
gp_port_usbscsi_get_usb_id (const char *sg,
      unsigned short *vendor_id, unsigned short *product_id)
{
      FILE *f;
      char c, *s, buf[32], path[PATH_MAX + 1];
      char *xpath;

      snprintf (path, sizeof (path), "/sys/class/scsi_generic/%s", sg);
      xpath = gp_port_usbscsi_resolve_symlink(path);
      if (xpath) {
            snprintf (path, sizeof (path), "%s/../../../../../modalias",
                    gp_port_usbscsi_resolve_symlink(path));
      } else {
            /* older kernels, perhaps also works on newer ones */
            snprintf (path, sizeof (path), "/sys/class/scsi_generic/%s/device/../../../modalias", sg);
      }

      f = fopen (path, "r");
      if (!f)
            return GP_ERROR_IO_SUPPORTED_USB;

      s = fgets (buf, sizeof(buf), f);
      fclose (f);

      if (!s)
            return GP_ERROR_IO_SUPPORTED_USB;

      if (sscanf (s, "usb:v%4hxp%4hx%c", vendor_id, product_id, &c) != 3 ||
          c != 'd')
            return GP_ERROR_IO_SUPPORTED_USB;

      return GP_OK;
}

int
gp_port_library_list (GPPortInfoList *list)
{
      DIR *dir;
      struct dirent *dirent;
      GPPortInfo info;
      unsigned short vendor_id, product_id;

      dir = opendir ("/sys/class/scsi_generic");
      if (dir == NULL)
            return GP_OK;

      while ((dirent = readdir (dir))) {
            if (gp_port_usbscsi_get_usb_id (dirent->d_name,
                        &vendor_id, &product_id) != GP_OK)
                  continue; /* Not a usb device */

            info.type = GP_PORT_USB_SCSI;
            snprintf (info.path, sizeof (info.path),
                    "usbscsi:/dev/%s",
                    dirent->d_name);
            snprintf (info.name, sizeof (info.name),
                    _("USB Mass Storage raw SCSI"));
            CHECK (gp_port_info_list_append (list, info))
      }
      closedir (dir);
      return GP_OK;
}

static int
gp_port_usbscsi_init (GPPort *port)
{
      port->pl = calloc (1, sizeof (GPPortPrivateLibrary));
      if (!port->pl)
            return GP_ERROR_NO_MEMORY;

      port->pl->fd = -1;

      return GP_OK;
}

static int
gp_port_usbscsi_exit (GPPort *port)
{
      if (!port)
            return GP_ERROR_BAD_PARAMETERS;

      if (port->pl) {
            free (port->pl);
            port->pl = NULL;
      }

      return GP_OK;
}

static int
gp_port_usbscsi_open (GPPort *port)
{
      int result, i;
      const int max_tries = 5;
      const char *path = port->settings.usbscsi.path;

      result = gp_port_usbscsi_lock (port, path);
      if (result != GP_OK) {
            for (i = 0; i < max_tries; i++) {
                  result = gp_port_usbscsi_lock (port, path);
                  if (result == GP_OK)
                        break;
                  gp_log (GP_LOG_DEBUG, "gphoto2-port-usbscsi",
                        "Failed to get a lock, trying again...");
                  sleep (1);
            }
            CHECK (result)
      }
      port->pl->fd = open (path, O_RDWR);
      if (port->pl->fd == -1) {
            gp_port_usbscsi_unlock (port, path);
            gp_port_set_error (port, _("Failed to open '%s' (%m)."), path);
            return GP_ERROR_IO;
      }

      return GP_OK;
}

static int
gp_port_usbscsi_close (GPPort *port)
{
      if (!port || port->pl->fd == -1)
            return GP_OK;

      if (close (port->pl->fd) == -1) {
            gp_port_set_error (port, _("Could not close "
                  "'%s' (%m)."), port->settings.usbscsi.path);
            return GP_ERROR_IO;
      }
      port->pl->fd = -1;

      CHECK (gp_port_usbscsi_unlock (port,
                              port->settings.usbscsi.path))

      return GP_OK;
}

static int gp_port_usbscsi_send_scsi_cmd (GPPort *port, int to_dev, char *cmd,
      int cmd_size, char *sense, int sense_size, char *data, int data_size)
{
#ifdef HAVE_SCSI_SG_H
      sg_io_hdr_t io_hdr;

      if (!port)
            return GP_ERROR_BAD_PARAMETERS;

      /* The device needs to be opened for that operation */
      if (port->pl->fd == -1)
            CHECK (gp_port_usbscsi_open (port))

      memset(sense, 0, sense_size);
      memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
      if (to_dev) {
            io_hdr.dxfer_direction = SG_DXFER_TO_DEV;
      } else {
            memset (data, 0, data_size);
            io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
      }
      io_hdr.interface_id = 'S';
      io_hdr.cmdp = (unsigned char *)cmd;
      io_hdr.cmd_len = cmd_size;
      io_hdr.sbp = (unsigned char *)sense;
      io_hdr.mx_sb_len = sense_size;
      io_hdr.dxferp = (unsigned char *)data;
      io_hdr.dxfer_len = data_size;
      io_hdr.timeout = 500;

      if (ioctl (port->pl->fd, SG_IO, &io_hdr) < 0)
      {
            gp_port_set_error (port, _("Could not send scsi command to: "
                  "'%s' (%m)."), port->settings.usbscsi.path);
            return GP_ERROR_IO;
      }
      /* https://secure.wikimedia.org/wikipedia/en/wiki/Key_Code_Qualifier */
      if (sense[0] != 0) {
            gp_log(GP_LOG_DEBUG,"send_scsi_cmd","Request Sense reports:");
            if ((sense[0]&0x7f)!=0x70) {
                  gp_log(GP_LOG_DEBUG,"send_scsi_cmd","\tInvalid header.");
                  return GP_ERROR_IO;
            }
            gp_log(GP_LOG_DEBUG,"send_scsi_cmd","\tCurrent command read filemark: %s",(sense[2]&0x80)?"yes":"no");
            gp_log(GP_LOG_DEBUG,"send_scsi_cmd","\tEarly warning passed: %s",(sense[2]&0x40)?"yes":"no");
            gp_log(GP_LOG_DEBUG,"send_scsi_cmd","\tIncorrect blocklengt: %s",(sense[2]&0x20)?"yes":"no");
            gp_log(GP_LOG_DEBUG,"send_scsi_cmd","\tSense Key: %d",sense[2]&0xf);
            if (sense[0]&0x80)
                  gp_log(GP_LOG_DEBUG,"send_scsi_cmd","\tResidual Length: %d",sense[3]*0x1000000+sense[4]*0x10000+sense[5]*0x100+sense[6]);
            gp_log(GP_LOG_DEBUG,"send_scsi_cmd","\tAdditional Sense Length: %d",sense[7]);
            gp_log(GP_LOG_DEBUG,"send_scsi_cmd","\tAdditional Sense Code: %d",sense[12]);
            gp_log(GP_LOG_DEBUG,"send_scsi_cmd","\tAdditional Sense Code Qualifier: %d",sense[13]);
            if (sense[15]&0x80) {
                  gp_log(GP_LOG_DEBUG,"send_scsi_cmd","\tIllegal Param is in %s",(sense[15]&0x40)?"the CDB":"the Data Out Phase");
                  if (sense[15]&0x8) {
                        gp_log(GP_LOG_DEBUG,"send_scsi_cmd","Pointer at %d, bit %d",sense[16]*256+sense[17],sense[15]&0x7);
                  }
            }
      }
      return GP_OK;
#else
      return GP_ERROR_NOT_SUPPORTED;
#endif
}

static int
gp_port_usbscsi_update (GPPort *port)
{
      if (!port)
            return GP_ERROR_BAD_PARAMETERS;

      memcpy (&port->settings, &port->settings_pending,
            sizeof (port->settings));

      return GP_OK;
}

static int
gp_port_usbscsi_find_device(GPPort *port, int idvendor, int idproduct)
{
      unsigned short vendor_id, product_id;
      const char *sg;

      if (!port)
            return GP_ERROR_BAD_PARAMETERS;

      sg = strrchr (port->settings.usbscsi.path, '/');
      if (!sg)
            return GP_ERROR_BAD_PARAMETERS;
      sg++;

      CHECK (gp_port_usbscsi_get_usb_id (sg, &vendor_id, &product_id))
      if (vendor_id != idvendor || product_id != idproduct)
            return GP_ERROR_IO_USB_FIND;

      return GP_OK;
}

GPPortOperations *
gp_port_library_operations ()
{
      GPPortOperations *ops;

      ops = malloc (sizeof (GPPortOperations));
      if (!ops)
            return (NULL);
      memset (ops, 0, sizeof (GPPortOperations)); 

      ops->init   = gp_port_usbscsi_init;
      ops->exit   = gp_port_usbscsi_exit;
      ops->open   = gp_port_usbscsi_open;
      ops->close  = gp_port_usbscsi_close;
      ops->send_scsi_cmd = gp_port_usbscsi_send_scsi_cmd;
      ops->update = gp_port_usbscsi_update;
      ops->find_device = gp_port_usbscsi_find_device;

      return (ops);
}

Generated by  Doxygen 1.6.0   Back to index