usbmodeswitch: Updated to v.1.2.6 from shibby's branch.
[tomato.git] / release / src / router / shared / usb.c
blob46a0e57a48c19ad7c768ff536e9a965122a7aef2
1 /*
3 Tomato Firmware
4 USB Support Module
6 */
7 #include <string.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <unistd.h>
11 #include <ctype.h>
12 #include <fcntl.h>
13 #include <sys/stat.h>
14 #include <stdarg.h>
15 #include <syslog.h>
16 #include <sys/ioctl.h>
17 #include <net/if.h>
18 #include <dirent.h>
19 #include <sys/socket.h>
20 #include <arpa/inet.h>
21 #include <sys/sysinfo.h>
22 #include <sys/types.h>
24 #include <bcmnvram.h>
25 #include <bcmdevs.h>
26 #include <wlutils.h>
28 #include "shutils.h"
29 #include "shared.h"
32 /* Serialize using fcntl() calls
35 int file_lock(char *tag)
37 char fn[64];
38 struct flock lock;
39 int lockfd = -1;
40 pid_t lockpid;
42 sprintf(fn, "/var/lock/%s.lock", tag);
43 if ((lockfd = open(fn, O_CREAT | O_RDWR, 0666)) < 0)
44 goto lock_error;
46 pid_t pid = getpid();
47 if (read(lockfd, &lockpid, sizeof(pid_t))) {
48 // check if we already hold a lock
49 if (pid == lockpid) {
50 // don't close the file here as that will release all locks
51 return -1;
55 memset(&lock, 0, sizeof(lock));
56 lock.l_type = F_WRLCK;
57 lock.l_pid = pid;
59 if (fcntl(lockfd, F_SETLKW, &lock) < 0) {
60 close(lockfd);
61 goto lock_error;
64 lseek(lockfd, 0, SEEK_SET);
65 write(lockfd, &pid, sizeof(pid_t));
66 return lockfd;
67 lock_error:
68 // No proper error processing
69 syslog(LOG_DEBUG, "Error %d locking %s, proceeding anyway", errno, fn);
70 return -1;
73 void file_unlock(int lockfd)
75 if (lockfd >= 0) {
76 ftruncate(lockfd, 0);
77 close(lockfd);
82 /* Execute a function for each disc partition on the specified controller.
84 * Directory /dev/discs/ looks like this:
85 * disc0 -> ../scsi/host0/bus0/target0/lun0/
86 * disc1 -> ../scsi/host1/bus0/target0/lun0/
87 * disc2 -> ../scsi/host2/bus0/target0/lun0/
88 * disc3 -> ../scsi/host2/bus0/target0/lun1/
90 * Scsi host 2 supports multiple drives.
91 * Scsi host 0 & 1 support one drive.
93 * For attached drives, like this. If not attached, there is no "part#" item.
94 * Here, only one drive, with 2 partitions, is plugged in.
95 * /dev/discs/disc0/disc
96 * /dev/discs/disc0/part1
97 * /dev/discs/disc0/part2
98 * /dev/discs/disc1/disc
99 * /dev/discs/disc2/disc
101 * Which is the same as:
102 * /dev/scsi/host0/bus0/target0/lun0/disc
103 * /dev/scsi/host0/bus0/target0/lun0/part1
104 * /dev/scsi/host0/bus0/target0/lun0/part2
105 * /dev/scsi/host1/bus0/target0/lun0/disc
106 * /dev/scsi/host2/bus0/target0/lun0/disc
107 * /dev/scsi/host2/bus0/target0/lun1/disc
109 * Implementation notes:
110 * Various mucking about with a disc that just got plugged in or unplugged
111 * will make the scsi subsystem try a re-validate, and read the partition table of the disc.
112 * This will make sure the partitions show up.
114 * It appears to try to do the revalidate and re-read & update the partition
115 * information when this code does the "readdir of /dev/discs/disc0/?". If the
116 * disc has any mounted partitions the revalidate will be rejected. So the
117 * current partition info will remain. On an unplug event, when it is doing the
118 * readdir's, it will try to do the revalidate as we are doing the readdir's.
119 * But luckily they'll be rejected, otherwise the later partitions will disappear as
120 * soon as we get the first one.
121 * But be very careful! If something goes not exactly right, the partition entries
122 * will disappear before we've had a chance to unmount from them.
124 * To avoid this automatic revalidation, we go through /proc/partitions looking for the partitions
125 * that /dev/discs point to. That will avoid the implicit revalidate attempt.
127 * If host < 0, do all hosts. If >= 0, it is the host number to do.
131 /* check if the block device has no partition */
132 int is_no_partition(const char *discname)
134 FILE *procpt;
135 char line[128], ptname[32];
136 int ma, mi, sz;
137 int count = 0;
139 if ((procpt = fopen("/proc/partitions", "r"))) {
140 while (fgets(line, sizeof(line), procpt)) {
141 if (sscanf(line, " %d %d %d %[^\n ]", &ma, &mi, &sz, ptname) != 4)
142 continue;
143 if (strstr(ptname, discname))
144 count++;
148 return (count == 1);
151 int exec_for_host(int host, int obsolete, uint flags, host_exec func)
153 DIR *usb_dev_disc;
154 char bfr[256]; /* Will be: /dev/discs/disc# */
155 char ptname[32];/* Will be: discDN_PN */
156 char dsname[16];/* Will be: discDN */
157 int host_no; /* SCSI controller/host # */
158 struct dirent *dp;
159 FILE *prt_fp;
160 int siz;
161 char line[256];
162 int result = 0;
164 flags |= EFH_1ST_HOST;
166 #ifdef LINUX26
167 char hostbuf[16];
168 DIR *dir_host;
171 * Scsi block devices in kernel 2.6 (for attached devices) can be found as
172 * /sys/bus/scsi/devices/<host_no>:x:x:x/block:[sda|sdb|...]
174 if ((usb_dev_disc = opendir("/sys/bus/scsi/devices"))) {
175 sprintf(hostbuf, "%d:", host);
177 while ((dp = readdir(usb_dev_disc))) {
178 if (host >= 0 && strncmp(dp->d_name, hostbuf, strlen(hostbuf)) != 0)
179 continue;
180 if (sscanf(dp->d_name, "%d:%*s:%*s:%*s", &host_no) != 1)
181 continue;
182 sprintf(bfr, "/sys/bus/scsi/devices/%s", dp->d_name);
183 if ((dir_host = opendir(bfr))) {
184 while ((dp = readdir(dir_host))) {
185 if (strncmp(dp->d_name, "block:", 6) != 0)
186 continue;
187 strncpy(dsname, dp->d_name + 6, sizeof(dsname));
188 siz = strlen(dsname);
190 flags |= EFH_1ST_DISC;
191 if (func && (prt_fp = fopen("/proc/partitions", "r"))) {
192 while (fgets(line, sizeof(line) - 2, prt_fp)) {
193 if (sscanf(line, " %*s %*s %*s %s", ptname) == 1) {
194 if (strncmp(ptname, dsname, siz) == 0) {
195 if ((strcmp(ptname, dsname) == 0) && !is_no_partition(dsname))
196 continue;
197 sprintf(line, "/dev/%s", ptname);
198 result = (*func)(line, host_no, dsname, ptname, flags) || result;
199 flags &= ~(EFH_1ST_HOST | EFH_1ST_DISC);
203 fclose(prt_fp);
206 closedir(dir_host);
209 closedir(usb_dev_disc);
212 #else /* !LINUX26 */
213 char link[256]; /* Will be: ../scsi/host#/bus0/target0/lun# that bfr links to. */
214 /* When calling the func, will be: /dev/discs/disc#/part# */
215 char bfr2[128]; /* Will be: /dev/discs/disc#/disc for the BLKRRPART. */
216 char *cp;
217 int len;
218 int disc_num; /* Disc # */
219 int part_num; /* Parition # */
220 char *mp; /* Ptr to after any leading ../ path */
222 if ((usb_dev_disc = opendir(DEV_DISCS_ROOT))) {
223 while ((dp = readdir(usb_dev_disc))) {
224 sprintf(bfr, "%s/%s", DEV_DISCS_ROOT, dp->d_name);
225 if (strncmp(dp->d_name, "disc", 4) != 0)
226 continue;
228 disc_num = atoi(dp->d_name + 4);
229 len = readlink(bfr, link, sizeof(link) - 1);
230 if (len < 0)
231 continue;
233 link[len] = 0;
234 cp = strstr(link, "/scsi/host");
235 if (!cp)
236 continue;
238 host_no = atoi(cp + 10);
239 if (host >= 0 && host_no != host)
240 continue;
242 /* We have found a disc that is on this controller.
243 * Loop thru all the partitions on this disc.
244 * The new way, reading thru /proc/partitions.
246 mp = link;
247 if ((cp = strstr(link, "../")) != NULL)
248 mp = cp + 3;
249 siz = strlen(mp);
251 flags |= EFH_1ST_DISC;
252 if (func && (prt_fp = fopen("/proc/partitions", "r"))) {
253 while (fgets(line, sizeof(line) - 2, prt_fp)) {
254 if (sscanf(line, " %*s %*s %*s %s", bfr2) == 1 &&
255 strncmp(bfr2, mp, siz) == 0)
257 if ((cp = strstr(bfr2, "/part"))) {
258 part_num = atoi(cp + 5);
259 sprintf(line, "%s/part%d", bfr, part_num);
260 sprintf(dsname, "disc%d", disc_num);
261 sprintf(ptname, "disc%d_%d", disc_num, part_num);
263 else if ((cp = strstr(bfr2, "/disc"))) {
264 *(++cp) = 0;
265 if (!is_no_partition(bfr2))
266 continue;
267 sprintf(line, "%s/disc", bfr);
268 sprintf(dsname, "disc%d", disc_num);
269 strcpy(ptname, dsname);
271 else {
272 continue;
274 result = (*func)(line, host_no, dsname, ptname, flags) || result;
275 flags &= ~(EFH_1ST_HOST | EFH_1ST_DISC);
278 fclose(prt_fp);
281 closedir(usb_dev_disc);
284 #endif /* LINUX26 */
286 return result;
289 /* Concept taken from the e2fsprogs/ismounted.c.
290 * Find wherever 'file' (actually: device) is mounted.
291 * Either the exact same device-name, or another device-name.
292 * The latter is detected by comparing the rdev or dev&inode.
293 * So aliasing won't fool us---we'll still find if it's mounted.
294 * Return its mnt entry.
295 * In particular, the caller would look at the mnt->mountpoint.
297 * Find the matching devname(s) in mounts or swaps.
298 * If func is supplied, call it for each match. If not, return mnt on the first match.
301 static inline int is_same_device(char *fsname, dev_t file_rdev, dev_t file_dev, ino_t file_ino)
303 struct stat st_buf;
305 if (stat(fsname, &st_buf) == 0) {
306 if (S_ISBLK(st_buf.st_mode)) {
307 if (file_rdev && (file_rdev == st_buf.st_rdev))
308 return 1;
310 else {
311 if (file_dev && ((file_dev == st_buf.st_dev) &&
312 (file_ino == st_buf.st_ino)))
313 return 1;
314 /* Check for [swap]file being on the device. */
315 if (file_dev == 0 && file_ino == 0 && file_rdev == st_buf.st_dev)
316 return 1;
319 return 0;
323 struct mntent *findmntents(char *file, int swp, int (*func)(struct mntent *mnt, uint flags), uint flags)
325 struct mntent *mnt;
326 struct stat st_buf;
327 dev_t file_dev=0, file_rdev=0;
328 ino_t file_ino=0;
329 FILE *f;
331 if ((f = setmntent(swp ? "/proc/swaps": "/proc/mounts", "r")) == NULL)
332 return NULL;
334 if (stat(file, &st_buf) == 0) {
335 if (S_ISBLK(st_buf.st_mode)) {
336 file_rdev = st_buf.st_rdev;
338 else {
339 file_dev = st_buf.st_dev;
340 file_ino = st_buf.st_ino;
343 while ((mnt = getmntent(f)) != NULL) {
344 /* Always ignore rootfs mount */
345 if (strcmp(mnt->mnt_fsname, "rootfs") == 0)
346 continue;
348 if (strcmp(file, mnt->mnt_fsname) == 0 ||
349 strcmp(file, mnt->mnt_dir) == 0 ||
350 is_same_device(mnt->mnt_fsname, file_rdev , file_dev, file_ino)) {
351 if (func == NULL)
352 break;
353 (*func)(mnt, flags);
357 endmntent(f);
358 return mnt;
362 //#define SAME_AS_KERNEL
363 /* Simulate a hotplug event, as if a USB storage device
364 * got plugged or unplugged.
365 * Either use a hardcoded program name, or the same
366 * hotplug program that the kernel uses for a real event.
368 void add_remove_usbhost(char *host, int add)
370 setenv("ACTION", add ? "add" : "remove", 1);
371 setenv("SCSI_HOST", host, 1);
372 setenv("PRODUCT", host, 1);
373 setenv("INTERFACE", "TOMATO/0", 1);
374 #ifdef SAME_AS_KERNEL
375 char pgm[256] = "/sbin/hotplug usb";
376 char *p;
377 int fd = open("/proc/sys/kernel/hotplug", O_RDONLY);
378 if (fd) {
379 if (read(fd, pgm, sizeof(pgm) - 5) >= 0) {
380 if ((p = strchr(pgm, '\n')) != NULL)
381 *p = 0;
382 strcat(pgm, " usb");
384 close(fd);
386 system(pgm);
387 #else
388 // don't use value from /proc/sys/kernel/hotplug
389 // since it may be overriden by a user.
390 system("/sbin/hotplug usb");
391 #endif
392 unsetenv("INTERFACE");
393 unsetenv("PRODUCT");
394 unsetenv("SCSI_HOST");
395 unsetenv("ACTION");
399 /****************************************************/
400 /* Use busybox routines to get labels for fat & ext */
401 /* Probe for label the same way that mount does. */
402 /****************************************************/
404 #define VOLUME_ID_LABEL_SIZE 64
405 #define VOLUME_ID_UUID_SIZE 36
406 #define SB_BUFFER_SIZE 0x11000
408 struct volume_id {
409 int fd;
410 int error;
411 size_t sbbuf_len;
412 size_t seekbuf_len;
413 uint8_t *sbbuf;
414 uint8_t *seekbuf;
415 uint64_t seekbuf_off;
416 char label[VOLUME_ID_LABEL_SIZE+1];
417 char uuid[VOLUME_ID_UUID_SIZE+1];
420 extern void volume_id_set_uuid();
421 extern void *volume_id_get_buffer();
422 extern void volume_id_free_buffer();
423 extern int volume_id_probe_ext();
424 extern int volume_id_probe_vfat();
425 extern int volume_id_probe_ntfs();
426 extern int volume_id_probe_linux_swap();
428 /* Put the label in *label and uuid in *uuid.
429 * Return fstype if determined.
431 char *find_label_or_uuid(char *dev_name, char *label, char *uuid)
433 struct volume_id id;
434 char *fstype = NULL;
436 memset(&id, 0x00, sizeof(id));
437 if (label) *label = 0;
438 if (uuid) *uuid = 0;
439 if ((id.fd = open(dev_name, O_RDONLY)) < 0)
440 return NULL;
442 volume_id_get_buffer(&id, 0, SB_BUFFER_SIZE);
444 if (!id.error && volume_id_probe_linux_swap(&id) == 0)
445 fstype = "swap";
446 else if (!id.error && volume_id_probe_vfat(&id) == 0)
447 fstype = "vfat";
448 else if (!id.error && volume_id_probe_ext(&id) == 0)
449 fstype = ((id.sbbuf[0x460] & 0x0008 /* JOURNAL_DEV */) != 0 ||
450 (id.sbbuf[0x45c] & 0x0004 /* HAS_JOURNAL */) != 0) ? "ext3" : "ext2";
451 else if (!id.error && volume_id_probe_ntfs(&id) == 0)
452 fstype = "ntfs";
453 else if (!id.error)
454 fstype = "unknown";
456 volume_id_free_buffer(&id);
457 if (label && (*id.label != 0))
458 strcpy(label, id.label);
459 if (uuid && (*id.uuid != 0))
460 strcpy(uuid, id.uuid);
461 close(id.fd);
462 return (fstype);
465 void *xmalloc(size_t siz)
467 return (malloc(siz));
470 void *xrealloc(void *old, size_t size)
472 return realloc(old, size);
475 ssize_t full_read(int fd, void *buf, size_t len)
477 return read(fd, buf, len);