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@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 
get_ep_type(char * path)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 
has_3_ep(char * path)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 
check_interface(char * sysfspath)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 
check_sysfs(char * sysfspath)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 
main(int argc,char ** argv)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