USB lock timeout, allow non-locking USB hotplug, cosmetics
[tomato.git] / release / src / router / shared / misc.c
blob16cf6abc1f5a646676f3ba3b54f01f7ea6d64af0
1 /*
3 Tomato Firmware
4 Copyright (C) 2006-2009 Jonathan Zarate
6 */
7 #include <string.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <unistd.h>
11 #include <ctype.h> // !!TB
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> //!!TB
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"
31 #if 0
32 #define _dprintf cprintf
33 #else
34 #define _dprintf(args...) do { } while(0)
35 #endif
38 int get_wan_proto(void)
40 const char *names[] = { // order must be synced with def at shared.h
41 "static",
42 "dhcp",
43 "l2tp",
44 "pppoe",
45 "pptp",
46 NULL
48 int i;
49 const char *p;
51 p = nvram_safe_get("wan_proto");
52 for (i = 0; names[i] != NULL; ++i) {
53 if (strcmp(p, names[i]) == 0) return i + 1;
55 return WP_DISABLED;
58 int using_dhcpc(void)
60 switch (get_wan_proto()) {
61 case WP_DHCP:
62 case WP_L2TP:
63 return 1;
65 return 0;
68 int wl_client(void)
70 return ((nvram_match("wl_mode", "sta")) || (nvram_match("wl_mode", "wet")));
73 void notice_set(const char *path, const char *format, ...)
75 char p[256];
76 char buf[2048];
77 va_list args;
79 va_start(args, format);
80 vsnprintf(buf, sizeof(buf), format, args);
81 va_end(args);
83 mkdir("/var/notice", 0755);
84 snprintf(p, sizeof(p), "/var/notice/%s", path);
85 f_write_string(p, buf, 0, 0);
86 if (buf[0]) syslog(LOG_INFO, "notice[%s]: %s", path, buf);
90 // #define _x_dprintf(args...) syslog(LOG_DEBUG, args);
91 #define _x_dprintf(args...) do { } while (0);
93 int check_wanup(void)
95 int up = 0;
96 int proto;
97 char buf1[64];
98 char buf2[64];
99 const char *name;
100 int f;
101 struct ifreq ifr;
103 proto = get_wan_proto();
104 if (proto == WP_DISABLED) return 0;
106 if ((proto == WP_PPTP) || (proto == WP_L2TP) || (proto == WP_PPPOE)) {
107 if (f_read_string("/tmp/ppp/link", buf1, sizeof(buf1)) > 0) {
108 // contains the base name of a file in /var/run/ containing pid of a daemon
109 snprintf(buf2, sizeof(buf2), "/var/run/%s.pid", buf1);
110 if (f_read_string(buf2, buf1, sizeof(buf1)) > 0) {
111 name = psname(atoi(buf1), buf2, sizeof(buf2));
112 if (proto == WP_PPPOE) {
113 if (strcmp(name, "pppoecd") == 0) up = 1;
115 else {
116 if (strcmp(name, "pppd") == 0) up = 1;
119 else {
120 _dprintf("%s: error reading %s\n", __FUNCTION__, buf2);
122 if (!up) {
123 unlink("/tmp/ppp/link");
124 _x_dprintf("required daemon not found, assuming link is dead\n");
127 else {
128 _x_dprintf("%s: error reading %s\n", __FUNCTION__, "/tmp/ppp/link");
131 else if (!nvram_match("wan_ipaddr", "0.0.0.0")) {
132 up = 1;
134 else {
135 _x_dprintf("%s: default !up\n", __FUNCTION__);
138 if ((up) && ((f = socket(AF_INET, SOCK_DGRAM, 0)) >= 0)) {
139 strlcpy(ifr.ifr_name, nvram_safe_get("wan_iface"), sizeof(ifr.ifr_name));
140 if (ioctl(f, SIOCGIFFLAGS, &ifr) < 0) {
141 up = 0;
142 _x_dprintf("%s: SIOCGIFFLAGS\n", __FUNCTION__);
144 close(f);
145 if ((ifr.ifr_flags & IFF_UP) == 0) {
146 up = 0;
147 _x_dprintf("%s: !IFF_UP\n", __FUNCTION__);
151 return up;
155 const dns_list_t *get_dns(void)
157 static dns_list_t dns;
158 char s[512];
159 int n;
160 int i, j;
161 struct in_addr ia;
162 char d[4][16];
164 dns.count = 0;
166 strlcpy(s, nvram_safe_get("wan_dns"), sizeof(s));
167 if ((nvram_match("dns_addget", "1")) || (s[0] == 0)) {
168 n = strlen(s);
169 snprintf(s + n, sizeof(s) - n, " %s", nvram_safe_get("wan_get_dns"));
172 n = sscanf(s, "%15s %15s %15s %15s", d[0], d[1], d[2], d[3]);
173 for (i = 0; i < n; ++i) {
174 if (inet_pton(AF_INET, d[i], &ia) > 0) {
175 for (j = dns.count - 1; j >= 0; --j) {
176 if (dns.dns[j].s_addr == ia.s_addr) break;
178 if (j < 0) {
179 dns.dns[dns.count++].s_addr = ia.s_addr;
180 if (dns.count == 3) break;
185 return &dns;
188 // -----------------------------------------------------------------------------
190 void set_action(int a)
192 int r = 3;
193 while (f_write("/var/lock/action", &a, sizeof(a), 0, 0) != sizeof(a)) {
194 sleep(1);
195 if (--r == 0) return;
197 if (a != ACT_IDLE) sleep(2);
200 int check_action(void)
202 int a;
203 int r = 3;
205 while (f_read("/var/lock/action", &a, sizeof(a)) != sizeof(a)) {
206 sleep(1);
207 if (--r == 0) return ACT_UNKNOWN;
209 return a;
212 int wait_action_idle(int n)
214 while (n-- > 0) {
215 if (check_action() == ACT_IDLE) return 1;
216 sleep(1);
218 return 0;
221 // -----------------------------------------------------------------------------
223 const char *get_wanip(void)
225 const char *p;
227 if (!check_wanup()) return "0.0.0.0";
228 switch (get_wan_proto()) {
229 case WP_DISABLED:
230 return "0.0.0.0";
231 case WP_PPTP:
232 p = "pptp_get_ip";
233 break;
234 case WP_L2TP:
235 p = "l2tp_get_ip";
236 break;
237 default:
238 p = "wan_ipaddr";
239 break;
241 return nvram_safe_get(p);
244 long get_uptime(void)
246 struct sysinfo si;
247 sysinfo(&si);
248 return si.uptime;
251 int get_radio(void)
253 uint32 n;
255 return (wl_ioctl(nvram_safe_get("wl_ifname"), WLC_GET_RADIO, &n, sizeof(n)) == 0) &&
256 ((n & WL_RADIO_SW_DISABLE) == 0);
259 void set_radio(int on)
261 uint32 n;
263 #ifndef WL_BSS_INFO_VERSION
264 #error WL_BSS_INFO_VERSION
265 #endif
267 #if WL_BSS_INFO_VERSION >= 108
268 n = on ? (WL_RADIO_SW_DISABLE << 16) : ((WL_RADIO_SW_DISABLE << 16) | 1);
269 wl_ioctl(nvram_safe_get("wl_ifname"), WLC_SET_RADIO, &n, sizeof(n));
270 if (!on) {
271 led(LED_WLAN, 0);
272 led(LED_DIAG, 0);
274 #else
275 n = on ? 0 : WL_RADIO_SW_DISABLE;
276 wl_ioctl(nvram_safe_get("wl_ifname"), WLC_SET_RADIO, &n, sizeof(n));
277 if (!on) {
278 led(LED_DIAG, 0);
280 #endif
283 int nvram_get_int(const char *key)
285 return atoi(nvram_safe_get(key));
289 long nvram_xget_long(const char *name, long min, long max, long def)
291 const char *p;
292 char *e;
293 long n;
295 p = nvram_get(name);
296 if ((p != NULL) && (*p != 0)) {
297 n = strtol(p, &e, 0);
298 if ((e != p) && ((*e == 0) || (*e == ' ')) && (n > min) && (n < max)) {
299 return n;
302 return def;
306 int nvram_get_file(const char *key, const char *fname, int max)
308 int n;
309 char *p;
310 char *b;
311 int r;
313 r = 0;
314 p = nvram_safe_get(key);
315 n = strlen(p);
316 if (n <= max) {
317 if ((b = malloc(base64_decoded_len(n) + 128)) != NULL) {
318 n = base64_decode(p, b, n);
319 if (n > 0) r = (f_write(fname, b, n, 0, 0644) == n);
320 free(b);
323 return r;
325 char b[2048];
326 int n;
327 char *p;
329 p = nvram_safe_get(key);
330 n = strlen(p);
331 if (n <= max) {
332 n = base64_decode(p, b, n);
333 if (n > 0) return (f_write(fname, b, n, 0, 0700) == n);
335 return 0;
339 int nvram_set_file(const char *key, const char *fname, int max)
341 char *in;
342 char *out;
343 long len;
344 int n;
345 int r;
347 if ((len = f_size(fname)) > max) return 0;
348 max = (int)len;
349 r = 0;
350 if (f_read_alloc(fname, &in, max) == max) {
351 if ((out = malloc(base64_encoded_len(max) + 128)) != NULL) {
352 n = base64_encode(in, out, max);
353 out[n] = 0;
354 nvram_set(key, out);
355 free(out);
356 r = 1;
358 free(in);
360 return r;
362 char a[2048];
363 char b[4096];
364 int n;
366 if (((n = f_read(fname, &a, sizeof(a))) > 0) && (n <= max)) {
367 n = base64_encode(a, b, n);
368 b[n] = 0;
369 nvram_set(key, b);
370 return 1;
372 return 0;
376 int nvram_contains_word(const char *key, const char *word)
378 return (find_word(nvram_safe_get(key), word) != NULL);
381 int nvram_is_empty(const char *key)
383 char *p;
384 return (((p = nvram_get(key)) == NULL) || (*p == 0));
387 void nvram_commit_x(void)
389 if (!nvram_get_int("debug_nocommit")) nvram_commit();
392 int connect_timeout(int fd, const struct sockaddr *addr, socklen_t len, int timeout)
394 fd_set fds;
395 struct timeval tv;
396 int flags;
397 int n;
398 int r;
400 if (((flags = fcntl(fd, F_GETFL, 0)) < 0) ||
401 (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0)) {
402 _dprintf("%s: error in F_*ETFL %d\n", __FUNCTION__, fd);
403 return -1;
406 if (connect(fd, addr, len) < 0) {
407 // _dprintf("%s: connect %d = <0\n", __FUNCTION__, fd);
409 if (errno != EINPROGRESS) {
410 _dprintf("%s: error in connect %d errno=%d\n", __FUNCTION__, fd, errno);
411 return -1;
414 while (1) {
415 tv.tv_sec = timeout;
416 tv.tv_usec = 0;
417 FD_ZERO(&fds);
418 FD_SET(fd, &fds);
419 r = select(fd + 1, NULL, &fds, NULL, &tv);
420 if (r == 0) {
421 _dprintf("%s: timeout in select %d\n", __FUNCTION__, fd);
422 return -1;
424 else if (r < 0) {
425 if (errno != EINTR) {
426 _dprintf("%s: error in select %d\n", __FUNCTION__, fd);
427 return -1;
429 // loop
431 else {
432 r = 0;
433 n = sizeof(r);
434 if ((getsockopt(fd, SOL_SOCKET, SO_ERROR, &r, &n) < 0) || (r != 0)) {
435 _dprintf("%s: error in SO_ERROR %d\n", __FUNCTION__, fd);
436 return -1;
438 break;
443 if (fcntl(fd, F_SETFL, flags) < 0) {
444 _dprintf("%s: error in F_*ETFL %d\n", __FUNCTION__, fd);
445 return -1;
448 // _dprintf("%s: OK %d\n", __FUNCTION__, fd);
449 return 0;
453 int time_ok(void)
455 return time(0) > Y2K;
459 // -----------------------------------------------------------------------------
460 //!!TB - USB Support
462 /* Serialize using fcntl() calls
465 #ifndef USB_LOCK_TIMEOUT
466 #define USB_LOCK_TIMEOUT 8
467 #endif
469 int usb_lock(void)
471 if (nvram_get_int("usb_nolock"))
472 return -1;
474 const char fn[] = "/var/lock/usb.lock";
475 struct flock lock;
476 int lockfd = -1;
478 if ((lockfd = open(fn, O_CREAT | O_RDWR, 0666)) < 0)
479 goto lock_error;
481 memset(&lock, 0, sizeof(lock));
482 lock.l_type = F_WRLCK;
483 lock.l_pid = getpid();
484 alarm(USB_LOCK_TIMEOUT);
486 if (fcntl(lockfd, F_SETLKW, &lock) < 0) {
487 close(lockfd);
488 alarm(0);
489 goto lock_error;
492 alarm(0);
493 return lockfd;
494 lock_error:
495 // No proper error processing
496 syslog(LOG_DEBUG, "Error %d locking %s, proceeding anyway", errno, fn);
497 return -1;
500 void usb_unlock(int lockfd)
502 if (lockfd >= 0) {
503 close(lockfd);
507 char *detect_fs_type(char *device)
509 int fd;
510 unsigned char buf[4096];
512 if ((fd = open(device, O_RDONLY)) < 0)
513 return NULL;
515 if (read(fd, buf, sizeof(buf)) != sizeof(buf))
517 close(fd);
518 return NULL;
521 close(fd);
523 /* first check for mbr */
524 if (*device && device[strlen(device) - 1] > '9' &&
525 buf[510] == 0x55 && buf[511] == 0xAA && /* signature */
526 ((buf[0x1be] | buf[0x1ce] | buf[0x1de] | buf[0x1ee]) & 0x7f) == 0) /* boot flags */
528 return "mbr";
530 /* detect swap */
531 else if (memcmp(buf + 4086, "SWAPSPACE2", 10) == 0 ||
532 memcmp(buf + 4086, "SWAP-SPACE", 10) == 0)
534 return "swap";
536 /* detect ext2/3 */
537 else if (buf[0x438] == 0x53 && buf[0x439] == 0xEF)
539 return ((buf[0x460] & 0x0008 /* JOURNAL_DEV */) != 0 ||
540 (buf[0x45c] & 0x0004 /* HAS_JOURNAL */) != 0) ? "ext3" : "ext2";
542 /* detect ntfs */
543 else if (buf[510] == 0x55 && buf[511] == 0xAA && /* signature */
544 memcmp(buf + 3, "NTFS ", 8) == 0)
546 return "ntfs";
548 /* detect vfat */
549 else if (buf[510] == 0x55 && buf[511] == 0xAA && /* signature */
550 buf[11] == 0 && buf[12] >= 1 && buf[12] <= 8 /* sector size 512 - 4096 */ &&
551 buf[13] != 0 && (buf[13] & (buf[13] - 1)) == 0) /* sectors per cluster */
553 return "vfat";
556 return NULL;
560 /* Execute a function for each disc partition on the specified controller.
562 * Directory /dev/discs/ looks like this:
563 * disc0 -> ../scsi/host0/bus0/target0/lun0/
564 * disc1 -> ../scsi/host1/bus0/target0/lun0/
565 * disc2 -> ../scsi/host2/bus0/target0/lun0/
566 * disc3 -> ../scsi/host2/bus0/target0/lun1/
568 * Scsi host 2 supports multiple drives.
569 * Scsi host 0 & 1 support one drive.
571 * For attached drives, like this. If not attached, there is no "part#" item.
572 * Here, only one drive, with 2 partitions, is plugged in.
573 * /dev/discs/disc0/disc
574 * /dev/discs/disc0/part1
575 * /dev/discs/disc0/part2
576 * /dev/discs/disc1/disc
577 * /dev/discs/disc2/disc
579 * Which is the same as:
580 * /dev/scsi/host0/bus0/target0/lun0/disc
581 * /dev/scsi/host0/bus0/target0/lun0/part1
582 * /dev/scsi/host0/bus0/target0/lun0/part2
583 * /dev/scsi/host1/bus0/target0/lun0/disc
584 * /dev/scsi/host2/bus0/target0/lun0/disc
585 * /dev/scsi/host2/bus0/target0/lun1/disc
587 * Implementation notes:
588 * Various mucking about with a disc that just got plugged in or unplugged
589 * will make the scsi subsystem try a re-validate, and read the partition table of the disc.
590 * This will make sure the partitions show up.
592 * It appears to try to do the revalidate and re-read & update the partition
593 * information when this code does the "readdir of /dev/discs/disc0/?". If the
594 * disc has any mounted partitions the revalidate will be rejected. So the
595 * current partition info will remain. On an unplug event, when it is doing the
596 * readdir's, it will try to do the revalidate as we are doing the readdir's.
597 * But luckily they'll be rejected, otherwise the later partitions will disappear as
598 * soon as we get the first one.
599 * But be very careful! If something goes not exactly right, the partition entries
600 * will disappear before we've had a chance to unmount from them.
602 * To avoid this automatic revalidation, we go through /proc/partitions looking for the partitions
603 * that /dev/discs point to. That will avoid the implicit revalidate attempt.
604 * Which means that we had better do it ourselves. An ioctl BLKRRPART does just that.
607 * If host < 0, do all hosts. If >= 0, it is the host number to do.
608 * When_to_update, flags:
609 * 0x01 = before reading partition info
610 * 0x02 = after reading partition info
614 /* So as not to include linux/fs.h, let's explicitly do this here. */
615 #ifndef BLKRRPART
616 #define BLKRRPART _IO(0x12,95) /* re-read partition table */
617 #endif
619 int exec_for_host(int host, int when_to_update, uint flags, host_exec func)
621 DIR *usb_dev_disc;
622 char bfr[128]; /* Will be: /dev/discs/disc# */
623 char link[256]; /* Will be: ../scsi/host#/bus0/target0/lun# that bfr links to. */
624 /* When calling the func, will be: /dev/discs/disc#/part# */
625 char bfr2[128]; /* Will be: /dev/discs/disc#/disc for the BLKRRPART. */
626 int fd;
627 char *cp;
628 int len;
629 int host_no; /* SCSI controller/host # */
630 int disc_num; /* Disc # */
631 int part_num; /* Parition # */
632 struct dirent *dp;
633 FILE *prt_fp;
634 char *mp; /* Ptr to after any leading ../ path */
635 int siz;
636 char line[256];
637 int result = 0;
639 flags |= EFH_1ST_HOST;
640 if ((usb_dev_disc = opendir(DEV_DISCS_ROOT))) {
641 while ((dp = readdir(usb_dev_disc))) {
642 sprintf(bfr, "%s/%s", DEV_DISCS_ROOT, dp->d_name);
643 if (strncmp(dp->d_name, "disc", 4) != 0)
644 continue;
646 disc_num = atoi(dp->d_name + 4);
647 len = readlink(bfr, link, sizeof(link) - 1);
648 if (len < 0)
649 continue;
651 link[len] = 0;
652 cp = strstr(link, "/scsi/host");
653 if (!cp)
654 continue;
656 host_no = atoi(cp + 10);
657 if (host >= 0 && host_no != host)
658 continue;
660 /* We have found a disc that is on this controller.
661 * Loop thru all the partitions on this disc.
662 * The new way, reading thru /proc/partitions.
664 mp = link;
665 if ((cp = strstr(link, "../")) != NULL)
666 mp = cp + 3;
667 siz = strlen(mp);
669 if (when_to_update & 0x01) {
670 sprintf(bfr2, "%s/disc", bfr); /* Prepare for BLKRRPART */
671 if ((fd = open(bfr2, O_RDONLY | O_NONBLOCK)) >= 0) {
672 ioctl(fd, BLKRRPART);
673 close(fd);
677 flags |= EFH_1ST_DISC;
678 if (func && (prt_fp = fopen("/proc/partitions", "r"))) {
679 while (fgets(line, sizeof(line) - 2, prt_fp)) {
680 if (sscanf(line, " %*s %*s %*s %s", bfr2) == 1) {
681 if ((cp = strstr(bfr2, "/part")) && strncmp(bfr2, mp, siz) == 0) {
682 part_num = atoi(cp + 5);
683 sprintf(line, "%s/part%d", bfr, part_num);
684 result = (*func)(line, host_no, disc_num, part_num, flags) || result;
685 flags &= ~(EFH_1ST_HOST | EFH_1ST_DISC);
689 fclose(prt_fp);
692 if (when_to_update & 0x02) {
693 sprintf(bfr2, "%s/disc", bfr); /* Prepare for BLKRRPART */
694 if ((fd = open(bfr2, O_RDONLY | O_NONBLOCK)) >= 0) {
695 ioctl(fd, BLKRRPART);
696 close(fd);
700 closedir(usb_dev_disc);
702 return result;
705 /* Stolen from the e2fsprogs/ismounted.c.
706 * Find wherever 'file' (actually: device) is mounted.
707 * Either the exact same device-name, or another device-name.
708 * The latter is detected by comparing the rdev or dev&inode.
709 * So aliasing won't fool us---we'll still find if it's mounted.
710 * Return its mnt entry.
711 * In particular, the caller would look at the mnt->mountpoint.
713 struct mntent *findmntent(char *file)
715 struct mntent *mnt;
716 struct stat st_buf;
717 dev_t file_dev=0, file_rdev=0;
718 ino_t file_ino=0;
719 FILE *f;
721 if ((f = setmntent("/proc/mounts", "r")) == NULL)
722 return NULL;
724 if (stat(file, &st_buf) == 0) {
725 if (S_ISBLK(st_buf.st_mode)) {
726 file_rdev = st_buf.st_rdev;
727 } else {
728 file_dev = st_buf.st_dev;
729 file_ino = st_buf.st_ino;
732 while ((mnt = getmntent(f)) != NULL) {
733 if (strcmp(file, mnt->mnt_fsname) == 0)
734 break;
736 if (stat(mnt->mnt_fsname, &st_buf) == 0) {
737 if (S_ISBLK(st_buf.st_mode)) {
738 if (file_rdev && (file_rdev == st_buf.st_rdev))
739 break;
740 } else {
741 if (file_dev && ((file_dev == st_buf.st_dev) &&
742 (file_ino == st_buf.st_ino)))
743 break;
748 endmntent(f);
749 return mnt;
753 /****************************************************/
754 /* Use busybox routines to get labels for fat & ext */
755 /* Probe for label the same way that mount does. */
756 /****************************************************/
758 #define VOLUME_ID_LABEL_SIZE 64
759 #define VOLUME_ID_UUID_SIZE 36
760 #define SB_BUFFER_SIZE 0x11000
762 struct volume_id {
763 int fd;
764 int error;
765 size_t sbbuf_len;
766 size_t seekbuf_len;
767 uint8_t *sbbuf;
768 uint8_t *seekbuf;
769 uint64_t seekbuf_off;
770 char label[VOLUME_ID_LABEL_SIZE+1];
771 char uuid[VOLUME_ID_UUID_SIZE+1];
774 extern void *volume_id_get_buffer(struct volume_id *id, uint64_t off, size_t len);
775 extern void volume_id_free_buffer(struct volume_id *id);
776 extern int volume_id_probe_ext(struct volume_id *id);
777 extern int volume_id_probe_vfat(struct volume_id *id);
778 extern int volume_id_probe_linux_swap(struct volume_id *id);
780 /* Put the label in *label.
781 * Return 0 if no label found, NZ if there is a label.
783 int find_label(char *dev_name, char *label)
785 struct volume_id id;
787 memset(&id, 0x00, sizeof(id));
788 label[0] = '\0';
789 if ((id.fd = open(dev_name, O_RDONLY)) < 0)
790 return 0;
792 if (volume_id_probe_vfat(&id) == 0)
793 goto ret;
794 volume_id_get_buffer(&id, 0, SB_BUFFER_SIZE);
795 if (volume_id_probe_ext(&id) == 0 || volume_id_probe_linux_swap(&id) == 0) { }
796 volume_id_free_buffer(&id);
798 ret:
799 if (id.label[0] != '\0')
800 strcpy(label, id.label);
801 close(id.fd);
802 return(label[0] != '\0');
805 void *xmalloc(size_t siz)
807 return (malloc(siz));
810 void *xrealloc(void *old, size_t size)
812 return realloc(old, size);
815 void volume_id_set_uuid() {}
817 ssize_t full_read(int fd, void *buf, size_t len)
819 return read(fd, buf, len);