blob: 3ff20406759850031e3b11824c881daf375fb12d [file] [log] [blame]
Linus Walleij4f4082b2010-12-01 23:00:00 +00001/**
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 *
Linus Walleij549f49a2010-12-05 14:00:34 +00007 * 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"
Linus Walleij4f4082b2010-12-01 23:00:00 +000013 *
Linus Walleij549f49a2010-12-05 14:00:34 +000014 * 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.
Linus Walleij4f4082b2010-12-01 23:00:00 +000021 *
Linus Walleij34bb9c22012-06-28 23:51:44 +020022 * Copyright (C) 2011-2012 Linus Walleij <triad@df.lth.se>
Linus Walleij4f4082b2010-12-01 23:00:00 +000023 *
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 */
Linus Walleij549f49a2010-12-05 14:00:34 +000039#ifndef __linux__
40#error "This program should only be compiled for Linux!"
41#endif
42
Linus Walleij4f4082b2010-12-01 23:00:00 +000043#include <unistd.h>
44#include <stdlib.h>
45#include <stdio.h>
46#include <string.h>
Linus Walleij549f49a2010-12-05 14:00:34 +000047#include <syslog.h>
Linus Walleijcbd39992012-10-21 00:43:38 +020048#include <sys/types.h>
49#include <sys/stat.h>
50#include <dirent.h>
Linus Walleij549f49a2010-12-05 14:00:34 +000051#include <libmtp.h>
Linus Walleijcbd39992012-10-21 00:43:38 +020052#include <regex.h>
53#include <fcntl.h>
54
55enum ep_type {
56 OTHER_EP,
57 BULK_OUT_EP,
58 BULK_IN_EP,
59 INTERRUPT_IN_EP,
60 INTERRUPT_OUT_EP,
61};
62
63static 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
133static 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
162static 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);
Marcus Meissner58edbd92015-04-09 23:06:31 +0200188 if (ret) {
189 closedir(dir);
Linus Walleijcbd39992012-10-21 00:43:38 +0200190 return -1;
Marcus Meissner58edbd92015-04-09 23:06:31 +0200191 }
Linus Walleijcbd39992012-10-21 00:43:38 +0200192
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
233static 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 */
Marcus Meissner8031a6c2015-12-06 21:04:05 +0100251 ret = regcomp(&r, "^[0-9]+-[0-9]+(\\.[0-9])*\\:[0-9]+\\.[0-9]+$", REG_EXTENDED | REG_NOSUB);
Marcus Meissner73018732015-10-04 13:29:11 +0200252 if (ret) {
253 closedir(dir);
Linus Walleijcbd39992012-10-21 00:43:38 +0200254 return -1;
Marcus Meissner73018732015-10-04 13:29:11 +0200255 }
Linus Walleijcbd39992012-10-21 00:43:38 +0200256
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}
Linus Walleij4f4082b2010-12-01 23:00:00 +0000284
285int main (int argc, char **argv)
286{
287 char *fname;
Linus Walleij549f49a2010-12-05 14:00:34 +0000288 int busno;
289 int devno;
290 int ret;
Linus Walleij4f4082b2010-12-01 23:00:00 +0000291
Linus Walleij549f49a2010-12-05 14:00:34 +0000292 if (argc < 4) {
293 syslog(LOG_INFO, "need device path, busnumber, device number as argument\n");
294 printf("0");
295 exit(0);
Linus Walleij4f4082b2010-12-01 23:00:00 +0000296 }
Linus Walleij549f49a2010-12-05 14:00:34 +0000297
Linus Walleij4f4082b2010-12-01 23:00:00 +0000298 fname = argv[1];
Linus Walleij549f49a2010-12-05 14:00:34 +0000299 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
Linus Walleijcbd39992012-10-21 00:43:38 +0200304 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);
Linus Walleij549f49a2010-12-05 14:00:34 +0000312 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);
Linus Walleij4f4082b2010-12-01 23:00:00 +0000321}