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
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);
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
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 */
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
285int 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