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