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