/** * \file mtp-probe.c * Program to probe newly connected device interfaces from * userspace to determine if they are MTP devices, used for * udev rules. * * Invoke the program from udev to check it for MTP signatures, * e.g. * ATTR{bDeviceClass}=="ff", * PROGRAM="/mtp-probe /sys$env{DEVPATH} $attr{busnum} $attr{devnum}", * RESULT=="1", ENV{ID_MTP_DEVICE}="1", ENV{ID_MEDIA_PLAYER}="1", * SYMLINK+="libmtp-%k", MODE="666" * * Is you issue this before testing your /var/log/messages * will be more verbose: * * udevadm control --log-priority=debug * * Exits with status code 1 if the device is an MTP device, * else exits with 0. * * Copyright (C) 2011-2012 Linus Walleij * * This library 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 of the License, or (at your option) any later version. * * This library 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 library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #ifndef __linux__ #error "This program should only be compiled for Linux!" #endif #include #include #include #include #include #include #include #include #include #include #include enum ep_type { OTHER_EP, BULK_OUT_EP, BULK_IN_EP, INTERRUPT_IN_EP, INTERRUPT_OUT_EP, }; static enum ep_type get_ep_type(char *path) { char pbuf[FILENAME_MAX]; int len = strlen(path); int fd; char buf[128]; int bread; int is_out = 0; int is_in = 0; int is_bulk = 0; int is_interrupt = 0; int i; strcpy(pbuf, path); pbuf[len++] = '/'; /* Check the type */ strncpy(pbuf + len, "type", FILENAME_MAX - len); pbuf[FILENAME_MAX - 1] = '\0'; /* Sentinel */ fd = open(pbuf, O_RDONLY); if (fd < 0) return OTHER_EP; bread = read(fd, buf, sizeof(buf)); close(fd); if (bread < 2) return OTHER_EP; for (i = 0; i < bread; i++) if(buf[i] == 0x0d || buf[i] == 0x0a) buf[i] = '\0'; if (!strcmp(buf, "Bulk")) is_bulk = 1; if (!strcmp(buf, "Interrupt")) is_interrupt = 1; /* Check the direction */ strncpy(pbuf + len, "direction", FILENAME_MAX - len); pbuf[FILENAME_MAX - 1] = '\0'; /* Sentinel */ fd = open(pbuf, O_RDONLY); if (fd < 0) return OTHER_EP; bread = read(fd, buf, sizeof(buf)); close(fd); if (bread < 2) return OTHER_EP; for (i = 0; i < bread; i++) if(buf[i] == 0x0d || buf[i] == 0x0a) buf[i] = '\0'; if (!strcmp(buf, "in")) is_in = 1; if (!strcmp(buf, "out")) is_out = 1; if (is_bulk && is_in) return BULK_IN_EP; if (is_bulk && is_out) return BULK_OUT_EP; if (is_interrupt && is_in) return INTERRUPT_IN_EP; if (is_interrupt && is_out) return INTERRUPT_OUT_EP; return OTHER_EP; } static int has_3_ep(char *path) { char pbuf[FILENAME_MAX]; int len = strlen(path); int fd; char buf[128]; int bread; strcpy(pbuf, path); pbuf[len++] = '/'; strncpy(pbuf + len, "bNumEndpoints", FILENAME_MAX - len); pbuf[FILENAME_MAX - 1] = '\0'; /* Sentinel */ fd = open(pbuf, O_RDONLY); if (fd < 0) return -1; /* Read all contents to buffer */ bread = read(fd, buf, sizeof(buf)); close(fd); if (bread < 2) return 0; /* 0x30, 0x33 = "03", maybe we should parse it? */ if (buf[0] == 0x30 && buf[1] == 0x33) return 1; return 0; } static int check_interface(char *sysfspath) { char dirbuf[FILENAME_MAX]; int len = strlen(sysfspath); DIR *dir; struct dirent *dent; regex_t r; int ret; int bulk_out_ep_found = 0; int bulk_in_ep_found = 0; int interrupt_in_ep_found = 0; ret = has_3_ep(sysfspath); if (ret <= 0) return ret; /* Yes it has three endpoints ... look even closer! */ dir = opendir(sysfspath); if (!dir) return -1; strcpy(dirbuf, sysfspath); dirbuf[len++] = '/'; /* Check for dirs that identify endpoints */ ret = regcomp(&r, "^ep_[0-9a-f]+$", REG_EXTENDED | REG_NOSUB); if (ret) { closedir(dir); return -1; } while ((dent = readdir(dir))) { struct stat st; /* No need to check those beginning with a period */ if (dent->d_name[0] == '.') continue; strncpy(dirbuf + len, dent->d_name, FILENAME_MAX - len); dirbuf[FILENAME_MAX - 1] = '\0'; /* Sentinel */ ret = lstat(dirbuf, &st); if (ret) continue; if (S_ISDIR(st.st_mode) && !regexec(&r, dent->d_name, 0, 0, 0)) { enum ep_type ept; ept = get_ep_type(dirbuf); if (ept == BULK_OUT_EP) bulk_out_ep_found = 1; else if (ept == BULK_IN_EP) bulk_in_ep_found = 1; else if (ept == INTERRUPT_IN_EP) interrupt_in_ep_found = 1; } } regfree(&r); closedir(dir); /* * If this is fulfilled the interface is an MTP candidate */ if (bulk_out_ep_found && bulk_in_ep_found && interrupt_in_ep_found) { return 1; } return 0; } static int check_sysfs(char *sysfspath) { char dirbuf[FILENAME_MAX]; int len = strlen(sysfspath); DIR *dir; struct dirent *dent; regex_t r; int ret; int look_closer = 0; dir = opendir(sysfspath); if (!dir) return -1; strcpy(dirbuf, sysfspath); dirbuf[len++] = '/'; /* Check for dirs that identify interfaces */ ret = regcomp(&r, "^[0-9]+-[0-9]+(\\.[0-9])*\\:[0-9]+\\.[0-9]+$", REG_EXTENDED | REG_NOSUB); if (ret) { closedir(dir); return -1; } while ((dent = readdir(dir))) { struct stat st; int ret; /* No need to check those beginning with a period */ if (dent->d_name[0] == '.') continue; strncpy(dirbuf + len, dent->d_name, FILENAME_MAX - len); dirbuf[FILENAME_MAX - 1] = '\0'; /* Sentinel */ ret = lstat(dirbuf, &st); if (ret) continue; /* Look closer at dirs that may be interfaces */ if (S_ISDIR(st.st_mode)) { if (!regexec(&r, dent->d_name, 0, 0, 0)) if (check_interface(dirbuf) > 0) /* potential MTP interface! */ look_closer = 1; } } regfree(&r); closedir(dir); return look_closer; } int main (int argc, char **argv) { char *fname; int busno; int devno; int ret; if (argc < 4) { syslog(LOG_INFO, "need device path, busnumber, device number as argument\n"); printf("0"); exit(0); } fname = argv[1]; busno = atoi(argv[2]); devno = atoi(argv[3]); syslog(LOG_INFO, "checking bus %d, device %d: \"%s\"\n", busno, devno, fname); ret = check_sysfs(fname); /* * This means that regular directory check either agrees that this may be a * MTP device, or that it doesn't know (failed). In that case, kick the deeper * check inside LIBMTP. */ if (ret != 0) ret = LIBMTP_Check_Specific_Device(busno, devno); if (ret) { syslog(LOG_INFO, "bus: %d, device: %d was an MTP device\n", busno, devno); printf("1"); } else { syslog(LOG_INFO, "bus: %d, device: %d was not an MTP device\n", busno, devno); printf("0"); } exit(0); }