321 lines
7.3 KiB
C
321 lines
7.3 KiB
C
/**
|
|
* \file mtp-probe.c
|
|
* Program to probe newly connected device interfaces from
|
|
* userspace to determine if they are MTP devices, used for
|
|
* udev rules.
|
|
*
|
|
* Invoke the program from udev to check it for MTP signatures,
|
|
* e.g.
|
|
* ATTR{bDeviceClass}=="ff",
|
|
* PROGRAM="<path>/mtp-probe /sys$env{DEVPATH} $attr{busnum} $attr{devnum}",
|
|
* RESULT=="1", ENV{ID_MTP_DEVICE}="1", ENV{ID_MEDIA_PLAYER}="1",
|
|
* SYMLINK+="libmtp-%k", MODE="666"
|
|
*
|
|
* Is you issue this before testing your /var/log/messages
|
|
* will be more verbose:
|
|
*
|
|
* udevadm control --log-priority=debug
|
|
*
|
|
* Exits with status code 1 if the device is an MTP device,
|
|
* else exits with 0.
|
|
*
|
|
* Copyright (C) 2011-2012 Linus Walleij <triad@df.lth.se>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, USA.
|
|
*/
|
|
#ifndef __linux__
|
|
#error "This program should only be compiled for Linux!"
|
|
#endif
|
|
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <syslog.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <dirent.h>
|
|
#include <libmtp.h>
|
|
#include <regex.h>
|
|
#include <fcntl.h>
|
|
|
|
enum ep_type {
|
|
OTHER_EP,
|
|
BULK_OUT_EP,
|
|
BULK_IN_EP,
|
|
INTERRUPT_IN_EP,
|
|
INTERRUPT_OUT_EP,
|
|
};
|
|
|
|
static enum ep_type get_ep_type(char *path)
|
|
{
|
|
char pbuf[FILENAME_MAX];
|
|
int len = strlen(path);
|
|
int fd;
|
|
char buf[128];
|
|
int bread;
|
|
int is_out = 0;
|
|
int is_in = 0;
|
|
int is_bulk = 0;
|
|
int is_interrupt = 0;
|
|
int i;
|
|
|
|
strcpy(pbuf, path);
|
|
pbuf[len++] = '/';
|
|
|
|
/* Check the type */
|
|
strncpy(pbuf + len, "type", FILENAME_MAX - len);
|
|
pbuf[FILENAME_MAX - 1] = '\0'; /* Sentinel */
|
|
|
|
fd = open(pbuf, O_RDONLY);
|
|
if (fd < 0)
|
|
return OTHER_EP;
|
|
bread = read(fd, buf, sizeof(buf));
|
|
close(fd);
|
|
if (bread < 2)
|
|
return OTHER_EP;
|
|
|
|
for (i = 0; i < bread; i++)
|
|
if(buf[i] == 0x0d || buf[i] == 0x0a)
|
|
buf[i] = '\0';
|
|
|
|
if (!strcmp(buf, "Bulk"))
|
|
is_bulk = 1;
|
|
if (!strcmp(buf, "Interrupt"))
|
|
is_interrupt = 1;
|
|
|
|
/* Check the direction */
|
|
strncpy(pbuf + len, "direction", FILENAME_MAX - len);
|
|
pbuf[FILENAME_MAX - 1] = '\0'; /* Sentinel */
|
|
|
|
fd = open(pbuf, O_RDONLY);
|
|
if (fd < 0)
|
|
return OTHER_EP;
|
|
bread = read(fd, buf, sizeof(buf));
|
|
close(fd);
|
|
if (bread < 2)
|
|
return OTHER_EP;
|
|
|
|
for (i = 0; i < bread; i++)
|
|
if(buf[i] == 0x0d || buf[i] == 0x0a)
|
|
buf[i] = '\0';
|
|
|
|
if (!strcmp(buf, "in"))
|
|
is_in = 1;
|
|
if (!strcmp(buf, "out"))
|
|
is_out = 1;
|
|
|
|
if (is_bulk && is_in)
|
|
return BULK_IN_EP;
|
|
if (is_bulk && is_out)
|
|
return BULK_OUT_EP;
|
|
if (is_interrupt && is_in)
|
|
return INTERRUPT_IN_EP;
|
|
if (is_interrupt && is_out)
|
|
return INTERRUPT_OUT_EP;
|
|
|
|
return OTHER_EP;
|
|
}
|
|
|
|
static int has_3_ep(char *path)
|
|
{
|
|
char pbuf[FILENAME_MAX];
|
|
int len = strlen(path);
|
|
int fd;
|
|
char buf[128];
|
|
int bread;
|
|
|
|
strcpy(pbuf, path);
|
|
pbuf[len++] = '/';
|
|
strncpy(pbuf + len, "bNumEndpoints", FILENAME_MAX - len);
|
|
pbuf[FILENAME_MAX - 1] = '\0'; /* Sentinel */
|
|
|
|
fd = open(pbuf, O_RDONLY);
|
|
if (fd < 0)
|
|
return -1;
|
|
/* Read all contents to buffer */
|
|
bread = read(fd, buf, sizeof(buf));
|
|
close(fd);
|
|
if (bread < 2)
|
|
return 0;
|
|
|
|
/* 0x30, 0x33 = "03", maybe we should parse it? */
|
|
if (buf[0] == 0x30 && buf[1] == 0x33)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int check_interface(char *sysfspath)
|
|
{
|
|
char dirbuf[FILENAME_MAX];
|
|
int len = strlen(sysfspath);
|
|
DIR *dir;
|
|
struct dirent *dent;
|
|
regex_t r;
|
|
int ret;
|
|
int bulk_out_ep_found = 0;
|
|
int bulk_in_ep_found = 0;
|
|
int interrupt_in_ep_found = 0;
|
|
|
|
ret = has_3_ep(sysfspath);
|
|
if (ret <= 0)
|
|
return ret;
|
|
|
|
/* Yes it has three endpoints ... look even closer! */
|
|
dir = opendir(sysfspath);
|
|
if (!dir)
|
|
return -1;
|
|
|
|
strcpy(dirbuf, sysfspath);
|
|
dirbuf[len++] = '/';
|
|
|
|
/* Check for dirs that identify endpoints */
|
|
ret = regcomp(&r, "^ep_[0-9a-f]+$", REG_EXTENDED | REG_NOSUB);
|
|
if (ret) {
|
|
closedir(dir);
|
|
return -1;
|
|
}
|
|
|
|
while ((dent = readdir(dir))) {
|
|
struct stat st;
|
|
|
|
/* No need to check those beginning with a period */
|
|
if (dent->d_name[0] == '.')
|
|
continue;
|
|
|
|
strncpy(dirbuf + len, dent->d_name, FILENAME_MAX - len);
|
|
dirbuf[FILENAME_MAX - 1] = '\0'; /* Sentinel */
|
|
ret = lstat(dirbuf, &st);
|
|
if (ret)
|
|
continue;
|
|
if (S_ISDIR(st.st_mode) && !regexec(&r, dent->d_name, 0, 0, 0)) {
|
|
enum ep_type ept;
|
|
|
|
ept = get_ep_type(dirbuf);
|
|
if (ept == BULK_OUT_EP)
|
|
bulk_out_ep_found = 1;
|
|
else if (ept == BULK_IN_EP)
|
|
bulk_in_ep_found = 1;
|
|
else if (ept == INTERRUPT_IN_EP)
|
|
interrupt_in_ep_found = 1;
|
|
}
|
|
}
|
|
|
|
regfree(&r);
|
|
closedir(dir);
|
|
|
|
/*
|
|
* If this is fulfilled the interface is an MTP candidate
|
|
*/
|
|
if (bulk_out_ep_found &&
|
|
bulk_in_ep_found &&
|
|
interrupt_in_ep_found) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int check_sysfs(char *sysfspath)
|
|
{
|
|
char dirbuf[FILENAME_MAX];
|
|
int len = strlen(sysfspath);
|
|
DIR *dir;
|
|
struct dirent *dent;
|
|
regex_t r;
|
|
int ret;
|
|
int look_closer = 0;
|
|
|
|
dir = opendir(sysfspath);
|
|
if (!dir)
|
|
return -1;
|
|
|
|
strcpy(dirbuf, sysfspath);
|
|
dirbuf[len++] = '/';
|
|
|
|
/* Check for dirs that identify interfaces */
|
|
ret = regcomp(&r, "^[0-9]+-[0-9]+(\\.[0-9])*\\:[0-9]+\\.[0-9]+$", REG_EXTENDED | REG_NOSUB);
|
|
if (ret) {
|
|
closedir(dir);
|
|
return -1;
|
|
}
|
|
|
|
while ((dent = readdir(dir))) {
|
|
struct stat st;
|
|
int ret;
|
|
|
|
/* No need to check those beginning with a period */
|
|
if (dent->d_name[0] == '.')
|
|
continue;
|
|
|
|
strncpy(dirbuf + len, dent->d_name, FILENAME_MAX - len);
|
|
dirbuf[FILENAME_MAX - 1] = '\0'; /* Sentinel */
|
|
ret = lstat(dirbuf, &st);
|
|
if (ret)
|
|
continue;
|
|
|
|
/* Look closer at dirs that may be interfaces */
|
|
if (S_ISDIR(st.st_mode)) {
|
|
if (!regexec(&r, dent->d_name, 0, 0, 0))
|
|
if (check_interface(dirbuf) > 0)
|
|
/* potential MTP interface! */
|
|
look_closer = 1;
|
|
}
|
|
}
|
|
|
|
regfree(&r);
|
|
closedir(dir);
|
|
return look_closer;
|
|
}
|
|
|
|
int main (int argc, char **argv)
|
|
{
|
|
char *fname;
|
|
int busno;
|
|
int devno;
|
|
int ret;
|
|
|
|
if (argc < 4) {
|
|
syslog(LOG_INFO, "need device path, busnumber, device number as argument\n");
|
|
printf("0");
|
|
exit(0);
|
|
}
|
|
|
|
fname = argv[1];
|
|
busno = atoi(argv[2]);
|
|
devno = atoi(argv[3]);
|
|
|
|
syslog(LOG_INFO, "checking bus %d, device %d: \"%s\"\n", busno, devno, fname);
|
|
|
|
ret = check_sysfs(fname);
|
|
/*
|
|
* This means that regular directory check either agrees that this may be a
|
|
* MTP device, or that it doesn't know (failed). In that case, kick the deeper
|
|
* check inside LIBMTP.
|
|
*/
|
|
if (ret != 0)
|
|
ret = LIBMTP_Check_Specific_Device(busno, devno);
|
|
if (ret) {
|
|
syslog(LOG_INFO, "bus: %d, device: %d was an MTP device\n", busno, devno);
|
|
printf("1");
|
|
} else {
|
|
syslog(LOG_INFO, "bus: %d, device: %d was not an MTP device\n", busno, devno);
|
|
printf("0");
|
|
}
|
|
|
|
exit(0);
|
|
}
|