Home | History | Annotate | Download | only in util
      1 /**
      2  * \file mtp-probe.c
      3  * Program to probe newly connected device interfaces from
      4  * userspace to determine if they are MTP devices, used for
      5  * udev rules.
      6  *
      7  * Invoke the program from udev to check it for MTP signatures,
      8  * e.g.
      9  * ATTR{bDeviceClass}=="ff",
     10  * PROGRAM="<path>/mtp-probe /sys$env{DEVPATH} $attr{busnum} $attr{devnum}",
     11  * RESULT=="1", ENV{ID_MTP_DEVICE}="1", ENV{ID_MEDIA_PLAYER}="1",
     12  * SYMLINK+="libmtp-%k", MODE="666"
     13  *
     14  * Is you issue this before testing your /var/log/messages
     15  * will be more verbose:
     16  *
     17  * udevadm control --log-priority=debug
     18  *
     19  * Exits with status code 1 if the device is an MTP device,
     20  * else exits with 0.
     21  *
     22  * Copyright (C) 2011-2012 Linus Walleij <triad (at) df.lth.se>
     23  *
     24  * This library is free software; you can redistribute it and/or
     25  * modify it under the terms of the GNU Lesser General Public
     26  * License as published by the Free Software Foundation; either
     27  * version 2 of the License, or (at your option) any later version.
     28  *
     29  * This library is distributed in the hope that it will be useful,
     30  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     31  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     32  * Lesser General Public License for more details.
     33  *
     34  * You should have received a copy of the GNU Lesser General Public
     35  * License along with this library; if not, write to the
     36  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
     37  * Boston, MA 02111-1307, USA.
     38  */
     39 #ifndef __linux__
     40 #error "This program should only be compiled for Linux!"
     41 #endif
     42 
     43 #include <unistd.h>
     44 #include <stdlib.h>
     45 #include <stdio.h>
     46 #include <string.h>
     47 #include <syslog.h>
     48 #include <sys/types.h>
     49 #include <sys/stat.h>
     50 #include <dirent.h>
     51 #include <libmtp.h>
     52 #include <regex.h>
     53 #include <fcntl.h>
     54 
     55 enum ep_type {
     56   OTHER_EP,
     57   BULK_OUT_EP,
     58   BULK_IN_EP,
     59   INTERRUPT_IN_EP,
     60   INTERRUPT_OUT_EP,
     61 };
     62 
     63 static enum ep_type get_ep_type(char *path)
     64 {
     65   char pbuf[FILENAME_MAX];
     66   int len = strlen(path);
     67   int fd;
     68   char buf[128];
     69   int bread;
     70   int is_out = 0;
     71   int is_in = 0;
     72   int is_bulk = 0;
     73   int is_interrupt = 0;
     74   int i;
     75 
     76   strcpy(pbuf, path);
     77   pbuf[len++] = '/';
     78 
     79   /* Check the type */
     80   strncpy(pbuf + len, "type", FILENAME_MAX - len);
     81   pbuf[FILENAME_MAX - 1] = '\0'; /* Sentinel */
     82 
     83   fd = open(pbuf, O_RDONLY);
     84   if (fd < 0)
     85     return OTHER_EP;
     86   bread = read(fd, buf, sizeof(buf));
     87   close(fd);
     88   if (bread < 2)
     89     return OTHER_EP;
     90 
     91   for (i = 0; i < bread; i++)
     92     if(buf[i] == 0x0d || buf[i] == 0x0a)
     93       buf[i] = '\0';
     94 
     95   if (!strcmp(buf, "Bulk"))
     96     is_bulk = 1;
     97   if (!strcmp(buf, "Interrupt"))
     98     is_interrupt = 1;
     99 
    100   /* Check the direction */
    101   strncpy(pbuf + len, "direction", FILENAME_MAX - len);
    102   pbuf[FILENAME_MAX - 1] = '\0'; /* Sentinel */
    103 
    104   fd = open(pbuf, O_RDONLY);
    105   if (fd < 0)
    106     return OTHER_EP;
    107   bread = read(fd, buf, sizeof(buf));
    108   close(fd);
    109   if (bread < 2)
    110     return OTHER_EP;
    111 
    112   for (i = 0; i < bread; i++)
    113     if(buf[i] == 0x0d || buf[i] == 0x0a)
    114       buf[i] = '\0';
    115 
    116   if (!strcmp(buf, "in"))
    117     is_in = 1;
    118   if (!strcmp(buf, "out"))
    119     is_out = 1;
    120 
    121   if (is_bulk && is_in)
    122     return BULK_IN_EP;
    123   if (is_bulk && is_out)
    124     return BULK_OUT_EP;
    125   if (is_interrupt && is_in)
    126     return INTERRUPT_IN_EP;
    127   if (is_interrupt && is_out)
    128     return INTERRUPT_OUT_EP;
    129 
    130   return OTHER_EP;
    131 }
    132 
    133 static int has_3_ep(char *path)
    134 {
    135   char pbuf[FILENAME_MAX];
    136   int len = strlen(path);
    137   int fd;
    138   char buf[128];
    139   int bread;
    140 
    141   strcpy(pbuf, path);
    142   pbuf[len++] = '/';
    143   strncpy(pbuf + len, "bNumEndpoints", FILENAME_MAX - len);
    144   pbuf[FILENAME_MAX - 1] = '\0'; /* Sentinel */
    145 
    146   fd = open(pbuf, O_RDONLY);
    147   if (fd < 0)
    148     return -1;
    149   /* Read all contents to buffer */
    150   bread = read(fd, buf, sizeof(buf));
    151   close(fd);
    152   if (bread < 2)
    153     return 0;
    154 
    155   /* 0x30, 0x33 = "03", maybe we should parse it? */
    156   if (buf[0] == 0x30 && buf[1] == 0x33)
    157     return 1;
    158 
    159   return 0;
    160 }
    161 
    162 static int check_interface(char *sysfspath)
    163 {
    164   char dirbuf[FILENAME_MAX];
    165   int len = strlen(sysfspath);
    166   DIR *dir;
    167   struct dirent *dent;
    168   regex_t r;
    169   int ret;
    170   int bulk_out_ep_found = 0;
    171   int bulk_in_ep_found = 0;
    172   int interrupt_in_ep_found = 0;
    173 
    174   ret = has_3_ep(sysfspath);
    175   if (ret <= 0)
    176     return ret;
    177 
    178   /* Yes it has three endpoints ... look even closer! */
    179   dir = opendir(sysfspath);
    180   if (!dir)
    181     return -1;
    182 
    183   strcpy(dirbuf, sysfspath);
    184   dirbuf[len++] = '/';
    185 
    186   /* Check for dirs that identify endpoints */
    187   ret = regcomp(&r, "^ep_[0-9a-f]+$", REG_EXTENDED | REG_NOSUB);
    188   if (ret) {
    189     closedir(dir);
    190     return -1;
    191   }
    192 
    193   while ((dent = readdir(dir))) {
    194     struct stat st;
    195 
    196     /* No need to check those beginning with a period */
    197     if (dent->d_name[0] == '.')
    198       continue;
    199 
    200     strncpy(dirbuf + len, dent->d_name, FILENAME_MAX - len);
    201     dirbuf[FILENAME_MAX - 1] = '\0'; /* Sentinel */
    202     ret = lstat(dirbuf, &st);
    203     if (ret)
    204       continue;
    205     if (S_ISDIR(st.st_mode) && !regexec(&r, dent->d_name, 0, 0, 0)) {
    206       enum ep_type ept;
    207 
    208       ept = get_ep_type(dirbuf);
    209       if (ept == BULK_OUT_EP)
    210 	bulk_out_ep_found = 1;
    211       else if (ept == BULK_IN_EP)
    212 	bulk_in_ep_found = 1;
    213       else if (ept == INTERRUPT_IN_EP)
    214 	interrupt_in_ep_found = 1;
    215     }
    216   }
    217 
    218   regfree(&r);
    219   closedir(dir);
    220 
    221   /*
    222    * If this is fulfilled the interface is an MTP candidate
    223    */
    224   if (bulk_out_ep_found &&
    225       bulk_in_ep_found &&
    226       interrupt_in_ep_found) {
    227     return 1;
    228   }
    229 
    230   return 0;
    231 }
    232 
    233 static int check_sysfs(char *sysfspath)
    234 {
    235   char dirbuf[FILENAME_MAX];
    236   int len = strlen(sysfspath);
    237   DIR *dir;
    238   struct dirent *dent;
    239   regex_t r;
    240   int ret;
    241   int look_closer = 0;
    242 
    243   dir = opendir(sysfspath);
    244   if (!dir)
    245     return -1;
    246 
    247   strcpy(dirbuf, sysfspath);
    248   dirbuf[len++] = '/';
    249 
    250   /* Check for dirs that identify interfaces */
    251   ret = regcomp(&r, "^[0-9]+-[0-9]+(\\.[0-9])*\\:[0-9]+\\.[0-9]+$", REG_EXTENDED | REG_NOSUB);
    252   if (ret) {
    253     closedir(dir);
    254     return -1;
    255   }
    256 
    257   while ((dent = readdir(dir))) {
    258     struct stat st;
    259     int ret;
    260 
    261     /* No need to check those beginning with a period */
    262     if (dent->d_name[0] == '.')
    263       continue;
    264 
    265     strncpy(dirbuf + len, dent->d_name, FILENAME_MAX - len);
    266     dirbuf[FILENAME_MAX - 1] = '\0'; /* Sentinel */
    267     ret = lstat(dirbuf, &st);
    268     if (ret)
    269       continue;
    270 
    271     /* Look closer at dirs that may be interfaces */
    272     if (S_ISDIR(st.st_mode)) {
    273       if (!regexec(&r, dent->d_name, 0, 0, 0))
    274       if (check_interface(dirbuf) > 0)
    275 	/* potential MTP interface! */
    276 	look_closer = 1;
    277     }
    278   }
    279 
    280   regfree(&r);
    281   closedir(dir);
    282   return look_closer;
    283 }
    284 
    285 int main (int argc, char **argv)
    286 {
    287   char *fname;
    288   int busno;
    289   int devno;
    290   int ret;
    291 
    292   if (argc < 4) {
    293     syslog(LOG_INFO, "need device path, busnumber, device number as argument\n");
    294     printf("0");
    295     exit(0);
    296   }
    297 
    298   fname = argv[1];
    299   busno = atoi(argv[2]);
    300   devno = atoi(argv[3]);
    301 
    302   syslog(LOG_INFO, "checking bus %d, device %d: \"%s\"\n", busno, devno, fname);
    303 
    304   ret = check_sysfs(fname);
    305   /*
    306    * This means that regular directory check either agrees that this may be a
    307    * MTP device, or that it doesn't know (failed). In that case, kick the deeper
    308    * check inside LIBMTP.
    309    */
    310   if (ret != 0)
    311     ret = LIBMTP_Check_Specific_Device(busno, devno);
    312   if (ret) {
    313     syslog(LOG_INFO, "bus: %d, device: %d was an MTP device\n", busno, devno);
    314     printf("1");
    315   } else {
    316     syslog(LOG_INFO, "bus: %d, device: %d was not an MTP device\n", busno, devno);
    317     printf("0");
    318   }
    319 
    320   exit(0);
    321 }
    322