diff options
Diffstat (limited to 'core/fs')
-rw-r--r-- | core/fs/chdir.c | 5 | ||||
-rw-r--r-- | core/fs/diskio.c | 9 | ||||
-rw-r--r-- | core/fs/fs.c | 12 | ||||
-rw-r--r-- | core/fs/lib/searchconfig.c | 3 | ||||
-rw-r--r-- | core/fs/pxe/core.c | 207 | ||||
-rw-r--r-- | core/fs/pxe/dhcp_option.c | 10 | ||||
-rw-r--r-- | core/fs/pxe/dnsresolv.c | 321 | ||||
-rw-r--r-- | core/fs/pxe/ftp.c | 280 | ||||
-rw-r--r-- | core/fs/pxe/ftp_readdir.c | 141 | ||||
-rw-r--r-- | core/fs/pxe/gpxeurl.c | 88 | ||||
-rw-r--r-- | core/fs/pxe/http.c | 400 | ||||
-rw-r--r-- | core/fs/pxe/http_readdir.c | 471 | ||||
-rw-r--r-- | core/fs/pxe/idle.c | 83 | ||||
-rw-r--r-- | core/fs/pxe/isr.c | 278 | ||||
-rw-r--r-- | core/fs/pxe/portnum.c | 68 | ||||
-rw-r--r-- | core/fs/pxe/pxe.c | 1027 | ||||
-rw-r--r-- | core/fs/pxe/pxe.h | 158 | ||||
-rw-r--r-- | core/fs/pxe/tcp.c | 78 | ||||
-rw-r--r-- | core/fs/pxe/tftp.c | 443 | ||||
-rw-r--r-- | core/fs/pxe/tftp.h | 54 | ||||
-rw-r--r-- | core/fs/pxe/url.h | 33 | ||||
-rw-r--r-- | core/fs/pxe/urlparse.c | 223 | ||||
-rw-r--r-- | core/fs/readdir.c | 3 |
23 files changed, 3037 insertions, 1358 deletions
diff --git a/core/fs/chdir.c b/core/fs/chdir.c index 5d3a545f..276ea11c 100644 --- a/core/fs/chdir.c +++ b/core/fs/chdir.c @@ -2,6 +2,7 @@ #include <stdbool.h> #include <string.h> #include <dprintf.h> +#include <fcntl.h> #include "fs.h" #include "cache.h" @@ -65,7 +66,7 @@ __export size_t realpath(char *dst, const char *src, size_t bufsize) if (this_fs->fs_ops->realpath) { s = this_fs->fs_ops->realpath(this_fs, dst, src, bufsize); } else { - rv = searchdir(src); + rv = searchdir(src, O_RDONLY); if (rv < 0) { dprintf("realpath: searchpath failure\n"); return -1; @@ -97,7 +98,7 @@ __export int chdir(const char *src) return this_fs->fs_ops->chdir(this_fs, src); /* Otherwise it is a "conventional filesystem" */ - rv = searchdir(src); + rv = searchdir(src, O_RDONLY|O_DIRECTORY); if (rv < 0) return rv; diff --git a/core/fs/diskio.c b/core/fs/diskio.c index 66838161..60defd3e 100644 --- a/core/fs/diskio.c +++ b/core/fs/diskio.c @@ -7,6 +7,7 @@ #include <fs.h> #include <disk.h> #include <ilog2.h> +#include <minmax.h> #define RETRY_COUNT 6 @@ -396,24 +397,20 @@ struct disk *disk_init(uint8_t devno, bool cdrom, sector_t part_start, return &disk; } - /* * Initialize the device structure. - * - * NOTE: the disk cache needs to be revamped to support multiple devices... */ struct device * device_init(uint8_t devno, bool cdrom, sector_t part_start, uint16_t bsHeads, uint16_t bsSecPerTrack, uint32_t MaxTransfer) { static struct device dev; - static __hugebss char diskcache[128*1024]; dev.disk = disk_init(devno, cdrom, part_start, bsHeads, bsSecPerTrack, MaxTransfer); - dev.cache_data = diskcache; - dev.cache_size = sizeof diskcache; + dev.cache_size = 128*1024; + dev.cache_data = malloc(dev.cache_size); return &dev; } diff --git a/core/fs/fs.c b/core/fs/fs.c index 2c10fe95..1cb4b00a 100644 --- a/core/fs/fs.c +++ b/core/fs/fs.c @@ -3,6 +3,7 @@ #include <stdbool.h> #include <string.h> #include <unistd.h> +#include <fcntl.h> #include <dprintf.h> #include "core.h" #include "dev.h" @@ -138,7 +139,7 @@ size_t pmapi_read_file(uint16_t *handle, void *buf, size_t sectors) return bytes_read; } -int searchdir(const char *name) +int searchdir(const char *name, int flags) { static char root_name[] = "/"; struct file *file; @@ -155,7 +156,7 @@ int searchdir(const char *name) /* if we have ->searchdir method, call it */ if (file->fs->fs_ops->searchdir) { - file->fs->fs_ops->searchdir(name, file); + file->fs->fs_ops->searchdir(name, flags, file); if (file->inode) return file_to_handle(file); @@ -336,7 +337,7 @@ err_no_close: return -1; } -__export int open_file(const char *name, struct com32_filedata *filedata) +__export int open_file(const char *name, int flags, struct com32_filedata *filedata) { int rv; struct file *file; @@ -345,7 +346,7 @@ __export int open_file(const char *name, struct com32_filedata *filedata) dprintf("open_file %s\n", name); mangle_name(mangled_name, name); - rv = searchdir(mangled_name); + rv = searchdir(mangled_name, flags); if (rv < 0) return rv; @@ -399,9 +400,6 @@ void fs_init(com32sys_t *regs) /* ops is a ptr list for several fs_ops */ const struct fs_ops **ops = (const struct fs_ops **)regs->eax.l; - /* Initialize malloc() */ - mem_init(); - /* Default name for the root directory */ fs.cwd_name[0] = '/'; diff --git a/core/fs/lib/searchconfig.c b/core/fs/lib/searchconfig.c index b2a964e8..bb1dabf9 100644 --- a/core/fs/lib/searchconfig.c +++ b/core/fs/lib/searchconfig.c @@ -1,4 +1,5 @@ #include <dprintf.h> +#include <fcntl.h> #include <stdio.h> #include <string.h> #include <core.h> @@ -30,7 +31,7 @@ int search_dirs(struct com32_filedata *filedata, if (realpath(realname, namebuf, FILENAME_MAX) == (size_t)-1) continue; dprintf("Config search: %s\n", realname); - if (open_file(realname, filedata) >= 0) { + if (open_file(realname, O_RDONLY, filedata) >= 0) { chdir(sd); return 0; /* Got it */ } diff --git a/core/fs/pxe/core.c b/core/fs/pxe/core.c new file mode 100644 index 00000000..9246f669 --- /dev/null +++ b/core/fs/pxe/core.c @@ -0,0 +1,207 @@ +#include <syslinux/pxe_api.h> +#include <lwip/api.h> +#include <lwip/tcpip.h> +#include <lwip/dns.h> +#include <core.h> +#include <net.h> +#include "pxe.h" + +/** + * Open a socket + * + * @param:socket, the socket to open + * @param:proto, the protocol of the new connection + * + * @out: error code, 0 on success, -1 on failure + */ +int net_core_open(struct pxe_pvt_inode *socket, enum net_core_proto proto) +{ + struct net_private *priv = &socket->private; + enum netconn_type type; + int err; + + switch (proto) { + case NET_CORE_TCP: + type = NETCONN_TCP; + break; + case NET_CORE_UDP: + type = NETCONN_UDP; + break; + default: + type = NETCONN_INVALID; + break; + } + + priv->conn = netconn_new(type); + if (!priv->conn) + return -1; + + priv->conn->recv_timeout = 15; /* A 15 ms recv timeout... */ + err = netconn_bind(priv->conn, NULL, 0); + if (err) { + printf("netconn_bind error %d\n", err); + return -1; + } + + return 0; +} + +/** + * Close a socket + * + * @param:socket, the socket to open + */ +void net_core_close(struct pxe_pvt_inode *socket) +{ + struct net_private *priv = &socket->private; + + if (priv->conn) { + netconn_delete(priv->conn); + priv->conn = NULL; + } +} + +/** + * Establish a connection on an open socket + * + * @param:socket, the open socket + * @param:ip, the ip address + * @param:port, the port number, host-byte order + */ +void net_core_connect(struct pxe_pvt_inode *socket, uint32_t ip, + uint16_t port) +{ + struct net_private *priv = &socket->private; + struct ip_addr addr; + + addr.addr = ip; + netconn_connect(priv->conn, &addr, port); +} + +/** + * Tear down a connection on an open socket + * + * @param:socket, the open socket + */ +void net_core_disconnect(struct pxe_pvt_inode *socket) +{ + struct net_private *priv = &socket->private; + netconn_disconnect(priv->conn); +} + +/** + * Read data from the network stack + * + * @param:socket, the open socket + * @param:buf, location of buffer to store data + * @param:buf_len, size of buffer + + * @out: src_ip, ip address of the data source + * @out: src_port, port number of the data source, host-byte order + */ +int net_core_recv(struct pxe_pvt_inode *socket, void *buf, uint16_t *buf_len, + uint32_t *src_ip, uint16_t *src_port) +{ + struct net_private *priv = &socket->private; + struct netbuf *nbuf; + u16_t nbuf_len; + int err; + + err = netconn_recv(priv->conn, &nbuf); + if (err) + return err; + + if (!nbuf) + return -1; + + *src_ip = netbuf_fromaddr(nbuf)->addr; + *src_port = netbuf_fromport(nbuf); + + netbuf_first(nbuf); /* XXX needed? */ + nbuf_len = netbuf_len(nbuf); + if (nbuf_len <= *buf_len) + netbuf_copy(nbuf, buf, nbuf_len); + else + nbuf_len = 0; /* impossible mtu < PKTBUF_SIZE */ + netbuf_delete(nbuf); + + *buf_len = nbuf_len; + return 0; +} + +/** + * Send a UDP packet. + * + * @param:socket, the open socket + * @param:data, data buffer to send + * @param:len, size of data bufer + */ +void net_core_send(struct pxe_pvt_inode *socket, const void *data, size_t len) +{ + struct netconn *conn = socket->private.conn; + struct netbuf *nbuf; + void *pbuf; + int err; + + nbuf = netbuf_new(); + if (!nbuf) { + printf("netbuf allocation error\n"); + return; + } + + pbuf = netbuf_alloc(nbuf, len); + if (!pbuf) { + printf("pbuf allocation error\n"); + goto out; + } + + memcpy(pbuf, data, len); + + err = netconn_send(conn, nbuf); + if (err) { + printf("netconn_send error %d\n", err); + goto out; + } + +out: + netbuf_delete(nbuf); +} + +/** + * Network stack-specific initialization + */ +void net_core_init(void) +{ + int err; + int i; + + http_bake_cookies(); + + /* Initialize lwip */ + tcpip_init(NULL, NULL); + + /* Start up the undi driver for lwip */ + err = undiif_start(IPInfo.myip, IPInfo.netmask, IPInfo.gateway); + if (err) { + printf("undiif driver failed to start: %d\n", err); + kaboom(); + } + + for (i = 0; i < DNS_MAX_SERVERS; i++) { + /* Transfer the DNS information to lwip */ + dns_setserver(i, (struct ip_addr *)&dns_server[i]); + } +} + +void probe_undi(void) +{ + /* Probe UNDI information */ + pxe_call(PXENV_UNDI_GET_INFORMATION, &pxe_undi_info); + pxe_call(PXENV_UNDI_GET_IFACE_INFO, &pxe_undi_iface); + + printf("UNDI: baseio %04x int %d MTU %d type %d \"%s\" flags 0x%x\n", + pxe_undi_info.BaseIo, pxe_undi_info.IntNumber, + pxe_undi_info.MaxTranUnit, pxe_undi_info.HwType, + pxe_undi_iface.IfaceType, pxe_undi_iface.ServiceFlags); +} + diff --git a/core/fs/pxe/dhcp_option.c b/core/fs/pxe/dhcp_option.c index f63d4a91..75827ff7 100644 --- a/core/fs/pxe/dhcp_option.c +++ b/core/fs/pxe/dhcp_option.c @@ -2,6 +2,7 @@ #include <string.h> #include <core.h> #include <sys/cpu.h> +#include <lwip/opt.h> /* DNS_MAX_SERVERS */ #include "pxe.h" char LocalDomain[256]; @@ -48,13 +49,8 @@ static void dns_servers(const void *data, int opt_len) static void local_domain(const void *data, int opt_len) { - char buffer[256]; - char *ld = LocalDomain; - - memcpy(buffer, data, opt_len); - buffer[opt_len] = 0; - - dns_mangle(&ld, buffer); + memcpy(LocalDomain, data, opt_len); + LocalDomain[opt_len] = 0; } static void vendor_encaps(const void *data, int opt_len) diff --git a/core/fs/pxe/dnsresolv.c b/core/fs/pxe/dnsresolv.c index 184bacbd..afb9e219 100644 --- a/core/fs/pxe/dnsresolv.c +++ b/core/fs/pxe/dnsresolv.c @@ -2,6 +2,8 @@ #include <string.h> #include <core.h> #include "pxe.h" +#include "lwip/api.h" +#include "lwip/dns.h" /* DNS CLASS values we care about */ #define CLASS_IN 1 @@ -48,295 +50,82 @@ struct dnsrr { uint32_t dns_server[DNS_MAX_SERVERS] = {0, }; - -/* - * Turn a string in _src_ into a DNS "label set" in _dst_; returns the - * number of dots encountered. On return, *dst is updated. - */ -int dns_mangle(char **dst, const char *p) -{ - char *q = *dst; - char *count_ptr; - char c; - int dots = 0; - - count_ptr = q; - *q++ = 0; - - while (1) { - c = *p++; - if (c == 0 || c == ':' || c == '/') - break; - if (c == '.') { - dots++; - count_ptr = q; - *q++ = 0; - continue; - } - - *count_ptr += 1; - *q++ = c; - } - - if (*count_ptr) - *q++ = 0; - - /* update the strings */ - *dst = q; - return dots; -} - - /* - * Compare two sets of DNS labels, in _s1_ and _s2_; the one in _s2_ - * is allowed pointers relative to a packet in buf. + * parse the ip_str and return the ip address with *res. + * return true if the whole string was consumed and the result + * was valid. * */ -static bool dns_compare(const void *s1, const void *s2, const void *buf) +static bool parse_dotquad(const char *ip_str, uint32_t *res) { - const uint8_t *q = s1; - const uint8_t *p = s2; - unsigned int c0, c1; - - while (1) { - c0 = p[0]; - if (c0 >= 0xc0) { - /* Follow pointer */ - c1 = p[1]; - p = (const uint8_t *)buf + ((c0 - 0xc0) << 8) + c1; - } else if (c0) { - c0++; /* Include the length byte */ - if (memcmp(q, p, c0)) - return false; - q += c0; - p += c0; - } else { - return *q == 0; - } - } -} - -/* - * Copy a DNS label into a buffer, considering the possibility that we might - * have to follow pointers relative to "buf". - * Returns a pointer to the first free byte *after* the terminal null. - */ -static void *dns_copylabel(void *dst, const void *src, const void *buf) -{ - uint8_t *q = dst; - const uint8_t *p = src; - unsigned int c0, c1; + const char *p = ip_str; + uint8_t part = 0; + uint32_t ip = 0; + int i; + + for (i = 0; i < 4; i++) { + while (is_digit(*p)) { + part = part * 10 + *p - '0'; + p++; + } + if (i != 3 && *p != '.') + return false; - while (1) { - c0 = p[0]; - if (c0 >= 0xc0) { - /* Follow pointer */ - c1 = p[1]; - p = (const uint8_t *)buf + ((c0 - 0xc0) << 8) + c1; - } else if (c0) { - c0++; /* Include the length byte */ - memcpy(q, p, c0); - p += c0; - q += c0; - } else { - *q++ = 0; - return q; - } + ip = (ip << 8) | part; + part = 0; + p++; } -} - -/* - * Skip past a DNS label set in DS:SI - */ -static char *dns_skiplabel(char *label) -{ - uint8_t c; + p--; - while (1) { - c = *label++; - if (c >= 0xc0) - return ++label; /* pointer is two bytes */ - if (c == 0) - return label; - label += c; - } + *res = htonl(ip); + return *p == '\0'; } /* - * Actual resolver function - * Points to a null-terminated or :-terminated string in _name_ - * and returns the ip addr in _ip_ if it exists and can be found. - * If _ip_ = 0 on exit, the lookup failed. _name_ will be updated + * Actual resolver function. * - * XXX: probably need some caching here. + * Points to a null-terminated in _name_ and returns the ip addr in + * _ip_ if it exists and can be found. If _ip_ = 0 on exit, the + * lookup failed. _name_ will be updated */ __export uint32_t dns_resolv(const char *name) { - static char __lowmem DNSSendBuf[PKTBUF_SIZE]; - static char __lowmem DNSRecvBuf[PKTBUF_SIZE]; - char *p; - int err; - int dots; - int same; - int rd_len; - int ques, reps; /* number of questions and replies */ - uint8_t timeout; - const uint8_t *timeout_ptr = TimeoutTable; - uint32_t oldtime; - uint32_t srv; - uint32_t *srv_ptr; - struct dnshdr *hd1 = (struct dnshdr *)DNSSendBuf; - struct dnshdr *hd2 = (struct dnshdr *)DNSRecvBuf; - struct dnsquery *query; - struct dnsrr *rr; - static __lowmem struct s_PXENV_UDP_WRITE udp_write; - static __lowmem struct s_PXENV_UDP_READ udp_read; - uint16_t local_port; - uint32_t result = 0; - - /* Make sure we have at least one valid DNS server */ - if (!dns_server[0]) + err_t err; + struct ip_addr ip; + char fullname[512]; + + /* + * Return failure on an empty input... this can happen during + * some types of URL parsing, and this is the easiest place to + * check for it. + */ + if (!name || !*name) return 0; - /* Get a local port number */ - local_port = get_port(); - - /* First, fill the DNS header struct */ - hd1->id++; /* New query ID */ - hd1->flags = htons(0x0100); /* Recursion requested */ - hd1->qdcount = htons(1); /* One question */ - hd1->ancount = 0; /* No answers */ - hd1->nscount = 0; /* No NS */ - hd1->arcount = 0; /* No AR */ - - p = DNSSendBuf + sizeof(struct dnshdr); - dots = dns_mangle(&p, name); /* store the CNAME */ - - if (!dots) { - p--; /* Remove final null */ - /* Uncompressed DNS label set so it ends in null */ - p = stpcpy(p, LocalDomain); - } - - /* Fill the DNS query packet */ - query = (struct dnsquery *)p; - query->qtype = htons(TYPE_A); - query->qclass = htons(CLASS_IN); - p += sizeof(struct dnsquery); - - /* Now send it to name server */ - timeout_ptr = TimeoutTable; - timeout = *timeout_ptr++; - srv_ptr = dns_server; - while (timeout) { - srv = *srv_ptr++; - if (!srv) { - srv_ptr = dns_server; - srv = *srv_ptr++; - } - - udp_write.status = 0; - udp_write.ip = srv; - udp_write.gw = gateway(srv); - udp_write.src_port = local_port; - udp_write.dst_port = DNS_PORT; - udp_write.buffer_size = p - DNSSendBuf; - udp_write.buffer = FAR_PTR(DNSSendBuf); - err = pxe_call(PXENV_UDP_WRITE, &udp_write); - if (err || udp_write.status) - continue; - - oldtime = jiffies(); - do { - if (jiffies() - oldtime >= timeout) - goto again; - - udp_read.status = 0; - udp_read.src_ip = srv; - udp_read.dest_ip = IPInfo.myip; - udp_read.s_port = DNS_PORT; - udp_read.d_port = local_port; - udp_read.buffer_size = PKTBUF_SIZE; - udp_read.buffer = FAR_PTR(DNSRecvBuf); - err = pxe_call(PXENV_UDP_READ, &udp_read); - } while (err || udp_read.status || hd2->id != hd1->id); + /* If it is a valid dot quad, just return that value */ + if (parse_dotquad(name, &ip.addr)) + return ip.addr; - if ((hd2->flags ^ 0x80) & htons(0xf80f)) - goto badness; - - ques = htons(hd2->qdcount); /* Questions */ - reps = htons(hd2->ancount); /* Replies */ - p = DNSRecvBuf + sizeof(struct dnshdr); - while (ques--) { - p = dns_skiplabel(p); /* Skip name */ - p += 4; /* Skip question trailer */ - } - - /* Parse the replies */ - while (reps--) { - same = dns_compare(DNSSendBuf + sizeof(struct dnshdr), - p, DNSRecvBuf); - p = dns_skiplabel(p); - rr = (struct dnsrr *)p; - rd_len = ntohs(rr->rdlength); - if (same && ntohs(rr->class) == CLASS_IN) { - switch (ntohs(rr->type)) { - case TYPE_A: - if (rd_len == 4) { - result = *(uint32_t *)rr->rdata; - goto done; - } - break; - case TYPE_CNAME: - dns_copylabel(DNSSendBuf + sizeof(struct dnshdr), - rr->rdata, DNSRecvBuf); - /* - * We should probably rescan the packet from the top - * here, and technically we might have to send a whole - * new request here... - */ - break; - default: - break; - } - } - - /* not the one we want, try next */ - p += sizeof(struct dnsrr) + rd_len; - } - - badness: - /* - * - ; We got back no data from this server. - ; Unfortunately, for a recursive, non-authoritative - ; query there is no such thing as an NXDOMAIN reply, - ; which technically means we can't draw any - ; conclusions. However, in practice that means the - ; domain doesn't exist. If this turns out to be a - ; problem, we may want to add code to go through all - ; the servers before giving up. - - ; If the DNS server wasn't capable of recursion, and - ; isn't capable of giving us an authoritative reply - ; (i.e. neither AA or RA set), then at least try a - ; different setver... - */ - if (hd2->flags == htons(0x480)) - continue; - - break; /* failed */ + /* Make sure we have at least one valid DNS server */ + if (!dns_getserver(0).addr) + return 0; - again: - continue; + /* Is it a local (unqualified) domain name? */ + if (!strchr(name, '.') && LocalDomain[0]) { + snprintf(fullname, sizeof fullname, "%s.%s", name, LocalDomain); + name = fullname; } -done: - free_port(local_port); /* Return port number to the free pool */ + err = netconn_gethostbyname(name, &ip); + if (err) + return 0; - return result; + return ip.addr; } +/* + * the one should be called from ASM file + */ void pm_pxe_dns_resolv(com32sys_t *regs) { const char *name = MK_PTR(regs->ds, regs->esi.w[0]); diff --git a/core/fs/pxe/ftp.c b/core/fs/pxe/ftp.c new file mode 100644 index 00000000..5d5a6f03 --- /dev/null +++ b/core/fs/pxe/ftp.c @@ -0,0 +1,280 @@ +/* ----------------------------------------------------------------------- * + * + * Copyright 2009-2011 Intel Corporation; author: H. Peter Anvin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston MA 02110-1301, USA; either version 2 of the License, or + * (at your option) any later version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +/* + * ftp.c + */ +#include <ctype.h> +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <minmax.h> +#include <sys/cpu.h> +#include <netinet/in.h> +#include <lwip/api.h> +#include "core.h" +#include "fs.h" +#include "pxe.h" +#include "thread.h" +#include "url.h" + +static int ftp_cmd_response(struct inode *inode, const char *cmd, + const char *cmd_arg, + uint8_t *pasv_data, int *pn_ptr) +{ + struct pxe_pvt_inode *socket = PVT(inode); + int c; + int pos, code; + int pb, pn; + bool ps; + bool first_line, done; + err_t err; + char cmd_buf[4096]; + int cmd_len; + const char *p; + char *q; + + if (cmd) { + cmd_len = strlcpy(cmd_buf, cmd, sizeof cmd_buf); + if (cmd_len >= sizeof cmd_buf - 3) + return -1; + q = cmd_buf + cmd_len; + + if (cmd_arg) { + p = cmd_arg; + + *q++ = ' '; + cmd_len++; + while (*p) { + if (++cmd_len < sizeof cmd_buf) *q++ = *p; + if (*p == '\r') + if (++cmd_len < sizeof cmd_buf) *q++ = '\0'; + p++; + } + + if (cmd_len >= sizeof cmd_buf - 2) + return -1; + } + + *q++ = '\r'; + *q++ = '\n'; + cmd_len += 2; + + err = netconn_write(socket->private.conn, cmd_buf, cmd_len, NETCONN_COPY); + if (err) + return -1; + } + + pos = code = pn = pb = 0; + ps = false; + first_line = true; + done = false; + + while ((c = pxe_getc(inode)) >= 0) { + if (c == '\n') { + if (done) { + if (pn) { + pn += ps; + if (pn_ptr) + *pn_ptr = pn; + } + return code; + } + pos = code = 0; + first_line = false; + continue; + } + + switch (pos++) { + case 0: + case 1: + case 2: + if (c < '0' || c > '9') { + if (first_line) + return -1; + else + pos = 4; /* Skip this line */ + } else { + code = (code*10) + (c - '0'); + } + break; + + case 3: + pn = pb = 0; + ps = false; + if (c == ' ') + done = true; + else if (c == '-') + done = false; + else if (first_line) + return -1; + else + done = false; + break; + + default: + if (pasv_data) { + if (c >= '0' && c <= '9') { + pb = (pb*10) + (c-'0'); + if (pn < 6) + pasv_data[pn] = pb; + ps = true; + } else if (c == ',') { + pn++; + pb = 0; + ps = false; + } else if (pn) { + pn += ps; + if (pn_ptr) + *pn_ptr = pn; + pn = pb = 0; + ps = false; + } + } + break; + } + } + + return -1; +} + +static void ftp_free(struct inode *inode) +{ + struct pxe_pvt_inode *socket = PVT(inode); + + if (socket->ctl) { + tcp_close_file(socket->ctl); + free_socket(socket->ctl); + socket->ctl = NULL; + } + tcp_close_file(inode); +} + +static void ftp_close_file(struct inode *inode) +{ + struct pxe_pvt_inode *socket = PVT(inode); + struct pxe_pvt_inode *ctlsock; + int resp; + + ctlsock = socket->ctl ? PVT(socket->ctl) : NULL; + if (ctlsock->private.conn) { + resp = ftp_cmd_response(socket->ctl, "QUIT", NULL, NULL, NULL); + while (resp == 226) { + resp = ftp_cmd_response(socket->ctl, NULL, NULL, NULL, NULL); + } + } + ftp_free(inode); +} + +static const struct pxe_conn_ops ftp_conn_ops = { + .fill_buffer = tcp_fill_buffer, + .close = ftp_close_file, + .readdir = ftp_readdir, +}; + +void ftp_open(struct url_info *url, int flags, struct inode *inode, + const char **redir) +{ + struct pxe_pvt_inode *socket = PVT(inode); + struct pxe_pvt_inode *ctlsock; + struct ip_addr addr; + uint8_t pasv_data[6]; + int pasv_bytes; + int resp; + err_t err; + + (void)redir; /* FTP does not redirect */ + + inode->size = 0; + + if (!url->port) + url->port = 21; + + url_unescape(url->path, 0); + + socket->ops = &ftp_conn_ops; + + /* Set up the control connection */ + socket->ctl = alloc_inode(inode->fs, 0, sizeof(struct pxe_pvt_inode)); + if (!socket->ctl) + return; + ctlsock = PVT(socket->ctl); + ctlsock->ops = &tcp_conn_ops; /* The control connection is just TCP */ + ctlsock->private.conn = netconn_new(NETCONN_TCP); + if (!ctlsock->private.conn) + goto err_free; + addr.addr = url->ip; + err = netconn_connect(ctlsock->private.conn, &addr, url->port); + if (err) + goto err_delete; + + do { + resp = ftp_cmd_response(socket->ctl, NULL, NULL, NULL, NULL); + } while (resp == 120); + if (resp != 220) + goto err_disconnect; + + if (!url->user) + url->user = "anonymous"; + if (!url->passwd) + url->passwd = "syslinux@"; + + resp = ftp_cmd_response(socket->ctl, "USER", url->user, NULL, NULL); + if (resp != 202 && resp != 230) { + if (resp != 331) + goto err_disconnect; + + resp = ftp_cmd_response(socket->ctl, "PASS", url->passwd, NULL, NULL); + if (resp != 230) + goto err_disconnect; + } + + if (!(flags & O_DIRECTORY)) { + resp = ftp_cmd_response(socket->ctl, "TYPE", "I", NULL, NULL); + if (resp != 200) + goto err_disconnect; + } + + resp = ftp_cmd_response(socket->ctl, "PASV", NULL, pasv_data, &pasv_bytes); + if (resp != 227 || pasv_bytes != 6) + goto err_disconnect; + + socket->private.conn = netconn_new(NETCONN_TCP); + if (!socket->private.conn) + goto err_disconnect; + err = netconn_connect(socket->private.conn, (struct ip_addr *)&pasv_data[0], + ntohs(*(uint16_t *)&pasv_data[4])); + if (err) + goto err_disconnect; + + resp = ftp_cmd_response(socket->ctl, + (flags & O_DIRECTORY) ? "LIST" : "RETR", + url->path, NULL, NULL); + if (resp != 125 && resp != 150) + goto err_disconnect; + + inode->size = -1; + return; /* Sucess! */ + +err_disconnect: + if (ctlsock->private.conn) + netconn_write(ctlsock->private.conn, "QUIT\r\n", 6, NETCONN_NOCOPY); + if (socket->private.conn) + netconn_delete(socket->private.conn); + if (ctlsock->private.buf) + netbuf_delete(ctlsock->private.buf); +err_delete: + if (ctlsock->private.conn) + netconn_delete(ctlsock->private.conn); +err_free: + free_socket(socket->ctl); +} diff --git a/core/fs/pxe/ftp_readdir.c b/core/fs/pxe/ftp_readdir.c new file mode 100644 index 00000000..6b87f77e --- /dev/null +++ b/core/fs/pxe/ftp_readdir.c @@ -0,0 +1,141 @@ +/* ----------------------------------------------------------------------- * + * + * Copyright 2011 Intel Corporation; author: H. Peter Anvin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston MA 02110-1301, USA; either version 2 of the License, or + * (at your option) any later version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +/* + * ftp_readdir.c + */ +#include <inttypes.h> +#include <string.h> +#include <stdlib.h> +#include <ctype.h> +#include <dprintf.h> +#include "pxe.h" + +static int dirtype(char type) +{ + switch (type) { + case 'f': + return DT_FIFO; + case 'c': + return DT_CHR; + case 'd': + return DT_DIR; + case 'b': + return DT_BLK; + case '-': + case '0' ... '9': /* Some DOS FTP stacks */ + return DT_REG; + case 'l': + return DT_LNK; + case 's': + return DT_SOCK; + default: + return DT_UNKNOWN; + } +} + +int ftp_readdir(struct inode *inode, struct dirent *dirent) +{ + char bufs[2][FILENAME_MAX + 1]; + int nbuf = 0; + char *buf = bufs[nbuf]; + char *p = buf; + char *name = NULL; + char type; + int c; + int dt; + bool was_cr = false; + bool first = true; + + for (;;) { + type = 0; + + for (;;) { + c = pxe_getc(inode); + if (c == -1) + return -1; /* Nothing else there */ + + if (c == '\r') { + was_cr = true; + continue; + } + if (was_cr) { + if (c == '\n') { + if (!name) { + *p = '\0'; + name = buf; + } + break; /* End of line */ + } + else if (c == '\0') + c = '\r'; + } + was_cr = false; + + if (c == ' ' || c == '\t') { + if (!name) { + *p = '\0'; + if (first) { + if (p == buf) { + /* Name started with whitespace - skip line */ + name = buf; + } else if ((p = strchr(buf, ';'))) { + /* VMS/Multinet format */ + if (p > buf+4 && !memcmp(p-4, ".DIR", 4)) { + type = 'd'; + p -= 4; + } else { + type = 'f'; + } + *p = '\0'; + name = buf; + } else { + type = buf[0]; + } + first = false; + } else { + /* Not the first word */ + if ((type >= '0' && type <= '9') && + !strcmp(buf, "<DIR>")) { + /* Some DOS FTP servers */ + type = 'd'; + } else if (type == 'l' && !strcmp(buf, "->")) { + /* The name was the previous word */ + name = bufs[nbuf ^ 1]; + } + } + nbuf ^= 1; + p = buf = bufs[nbuf]; + } + } else { + if (!name && p < buf + FILENAME_MAX) + *p++ = c; + } + } + + dt = dirtype(type); + if (dt != DT_UNKNOWN) { + size_t len = strlen(name); + + if (len <= NAME_MAX) { + dirent->d_type = dt; + dirent->d_ino = 0; /* Not applicable */ + dirent->d_off = 0; /* Not applicable */ + dirent->d_reclen = offsetof(struct dirent, d_name) + len+1; + memcpy(dirent->d_name, name, len+1); + return 0; + } + } + + /* Otherwise try the next line... */ + } +} diff --git a/core/fs/pxe/gpxeurl.c b/core/fs/pxe/gpxeurl.c new file mode 100644 index 00000000..6bbae3c2 --- /dev/null +++ b/core/fs/pxe/gpxeurl.c @@ -0,0 +1,88 @@ +#include "pxe.h" +#if GPXE + +static void gpxe_close_file(struct inode *inode) +{ + struct pxe_pvt_inode *socket = PVT(inode); + static __lowmem struct s_PXENV_FILE_CLOSE file_close; + + file_close.FileHandle = socket->tftp_remoteport; + pxe_call(PXENV_FILE_CLOSE, &file_close); +} + +/** + * Get a fresh packet from a gPXE socket + * @param: inode -> Inode pointer + * + */ +static void gpxe_get_packet(struct inode *inode) +{ + struct pxe_pvt_inode *socket = PVT(inode); + static __lowmem struct s_PXENV_FILE_READ file_read; + int err; + + while (1) { + file_read.FileHandle = socket->tftp_remoteport; + file_read.Buffer = FAR_PTR(packet_buf); + file_read.BufferSize = PKTBUF_SIZE; + err = pxe_call(PXENV_FILE_READ, &file_read); + if (!err) /* successed */ + break; + + if (file_read.Status != PXENV_STATUS_TFTP_OPEN) + kaboom(); + } + + memcpy(socket->tftp_pktbuf, packet_buf, file_read.BufferSize); + + socket->tftp_dataptr = socket->tftp_pktbuf; + socket->tftp_bytesleft = file_read.BufferSize; + socket->tftp_filepos += file_read.BufferSize; + + if (socket->tftp_bytesleft == 0) + inode->size = socket->tftp_filepos; + + /* if we're done here, close the file */ + if (inode->size > socket->tftp_filepos) + return; + + /* Got EOF, close it */ + socket->tftp_goteof = 1; + gpxe_close_file(inode); +} + +/** + * Open a url using gpxe + * + * @param:inode, the inode to store our state in + * @param:url, the url we want to open + * + * @out: open_file_t structure, stores in file->open_file + * @out: the lenght of this file, stores in file->file_len + * + */ +void gpxe_open(struct inode *inode, const char *url) +{ + static __lowmem struct s_PXENV_FILE_OPEN file_open; + static char lowurl[2*FILENAME_MAX]; + struct pxe_pvt_inode *socket = PVT(inode); + int err; + + socket->tftp_pktbuf = malloc(PKTBUF_SIZE); + if (!socket->tftp_pktbuf) + return; + + snprintf(lowurl, sizeof lowurl, "%s", url); + file_open.Status = PXENV_STATUS_BAD_FUNC; + file_open.FileName = FAR_PTR(lowurl); + err = pxe_call(PXENV_FILE_OPEN, &file_open); + if (err) + return; + + socket->fill_buffer = gpxe_get_packet; + socket->close = gpxe_close_file; + socket->tftp_remoteport = file_open.FileHandle; + inode->size = -1; /* This is not an error */ +} + +#endif /* GPXE */ diff --git a/core/fs/pxe/http.c b/core/fs/pxe/http.c new file mode 100644 index 00000000..9bf0c92e --- /dev/null +++ b/core/fs/pxe/http.c @@ -0,0 +1,400 @@ +#include <syslinux/sysappend.h> +#include <ctype.h> +#include <lwip/api.h> +#include "pxe.h" +#include "../../../version.h" +#include "url.h" + +#define HTTP_PORT 80 + +static bool is_tspecial(int ch) +{ + bool tspecial = false; + switch(ch) { + case '(': case ')': case '<': case '>': case '@': + case ',': case ';': case ':': case '\\': case '"': + case '/': case '[': case ']': case '?': case '=': + case '{': case '}': case ' ': case '\t': + tspecial = true; + break; + } + return tspecial; +} + +static bool is_ctl(int ch) +{ + return ch < 0x20; +} + +static bool is_token(int ch) +{ + /* Can by antying except a ctl character or a tspecial */ + return !is_ctl(ch) && !is_tspecial(ch); +} + +static bool append_ch(char *str, size_t size, size_t *pos, int ch) +{ + bool success = true; + if ((*pos + 1) >= size) { + *pos = 0; + success = false; + } else { + str[*pos] = ch; + str[*pos + 1] = '\0'; + *pos += 1; + } + return success; +} + +static size_t cookie_len, header_len; +static char *cookie_buf, *header_buf; + +__export uint32_t SendCookies = -1UL; /* Send all cookies */ + +static size_t http_do_bake_cookies(char *q) +{ + static const char uchexchar[16] = "0123456789ABCDEF"; + int i; + size_t n = 0; + const char *p; + char c; + bool first = true; + uint32_t mask = SendCookies; + + for (i = 0; i < SYSAPPEND_MAX; i++) { + if ((mask & 1) && (p = sysappend_strings[i])) { + if (first) { + if (q) { + strcpy(q, "Cookie: "); + q += 8; + } + n += 8; + first = false; + } + if (q) { + strcpy(q, "_Syslinux_"); + q += 10; + } + n += 10; + /* Copy string up to and including '=' */ + do { + c = *p++; + if (q) + *q++ = c; + n++; + } while (c != '='); + while ((c = *p++)) { + if (c == ' ') { + if (q) + *q++ = '+'; + n++; + } else if (is_token(c)) { + if (q) + *q++ = c; + n++; + } else { + if (q) { + *q++ = '%'; + *q++ = uchexchar[c >> 4]; + *q++ = uchexchar[c & 15]; + } + n += 3; + } + } + if (q) + *q++ = ';'; + n++; + } + mask >>= 1; + } + if (!first) { + if (q) { + *q++ = '\r'; + *q++ = '\n'; + } + n += 2; + } + if (q) + *q = '\0'; + + return n; +} + +void http_bake_cookies(void) +{ + if (cookie_buf) + free(cookie_buf); + + cookie_len = http_do_bake_cookies(NULL); + cookie_buf = malloc(cookie_len+1); + if (!cookie_buf) { + cookie_len = 0; + return; + } + + if (header_buf) + free(header_buf); + + header_len = cookie_len + 6*FILENAME_MAX + 256; + header_buf = malloc(header_len); + if (!header_buf) { + header_len = 0; + return; /* Uh-oh... */ + } + + http_do_bake_cookies(cookie_buf); +} + +static const struct pxe_conn_ops http_conn_ops = { + .fill_buffer = tcp_fill_buffer, + .close = tcp_close_file, + .readdir = http_readdir, +}; + +void http_open(struct url_info *url, int flags, struct inode *inode, + const char **redir) +{ + struct pxe_pvt_inode *socket = PVT(inode); + int header_bytes; + const char *next; + char field_name[20]; + char field_value[1024]; + size_t field_name_len, field_value_len; + err_t err; + enum state { + st_httpver, + st_stcode, + st_skipline, + st_fieldfirst, + st_fieldname, + st_fieldvalue, + st_skip_fieldname, + st_skip_fieldvalue, + st_eoh, + } state; + struct ip_addr addr; + static char location[FILENAME_MAX]; + uint32_t content_length; /* same as inode->size */ + size_t response_size; + int status; + int pos; + + (void)flags; + + if (!header_buf) + return; /* http is broken... */ + + /* This is a straightforward TCP connection after headers */ + socket->ops = &http_conn_ops; + + /* Reset all of the variables */ + inode->size = content_length = -1; + + /* Start the http connection */ + socket->private.conn = netconn_new(NETCONN_TCP); + if (!socket->private.conn) { + printf("netconn_new failed\n"); + return; + } + + addr.addr = url->ip; + if (!url->port) + url->port = HTTP_PORT; + + err = netconn_connect(socket->private.conn, &addr, url->port); + if (err) { + printf("netconn_connect error %d\n", err); + goto fail; + } + + strcpy(header_buf, "GET /"); + header_bytes = 5; + header_bytes += url_escape_unsafe(header_buf+5, url->path, + header_len - 5); + if (header_bytes >= header_len) + goto fail; /* Buffer overflow */ + header_bytes += snprintf(header_buf + header_bytes, + header_len - header_bytes, + " HTTP/1.0\r\n" + "Host: %s\r\n" + "User-Agent: Syslinux/" VERSION_STR "\r\n" + "Connection: close\r\n" + "%s" + "\r\n", + url->host, cookie_buf ? cookie_buf : ""); + if (header_bytes >= header_len) + goto fail; /* Buffer overflow */ + + err = netconn_write(socket->private.conn, header_buf, + header_bytes, NETCONN_NOCOPY); + if (err) { + printf("netconn_write error %d\n", err); + goto fail; + } + + /* Parse the HTTP header */ + state = st_httpver; + pos = 0; + status = 0; + response_size = 0; + field_value_len = 0; + field_name_len = 0; + + while (state != st_eoh) { + int ch = pxe_getc(inode); + /* Eof before I finish paring the header */ + if (ch == -1) + goto fail; +#if 0 + printf("%c", ch); +#endif + response_size++; + if (ch == '\r' || ch == '\0') + continue; + switch (state) { + case st_httpver: + if (ch == ' ') { + state = st_stcode; + pos = 0; + } + break; + + case st_stcode: + if (ch < '0' || ch > '9') + goto fail; + status = (status*10) + (ch - '0'); + if (++pos == 3) + state = st_skipline; + break; + + case st_skipline: + if (ch == '\n') + state = st_fieldfirst; + break; + + case st_fieldfirst: + if (ch == '\n') + state = st_eoh; + else if (isspace(ch)) { + /* A continuation line */ + state = st_fieldvalue; + goto fieldvalue; + } + else if (is_token(ch)) { + /* Process the previous field before starting on the next one */ + if (strcasecmp(field_name, "Content-Length") == 0) { + next = field_value; + /* Skip leading whitespace */ + while (isspace(*next)) + next++; + content_length = 0; + for (;(*next >= '0' && *next <= '9'); next++) { + if ((content_length * 10) < content_length) + break; + content_length = (content_length * 10) + (*next - '0'); + } + /* In the case of overflow or other error ignore + * Content-Length. + */ + if (*next) + content_length = -1; + } + else if (strcasecmp(field_name, "Location") == 0) { + next = field_value; + /* Skip leading whitespace */ + while (isspace(*next)) + next++; + strlcpy(location, next, sizeof location); + } + /* Start the field name and field value afress */ + field_name_len = 1; + field_name[0] = ch; + field_name[1] = '\0'; + field_value_len = 0; + field_value[0] = '\0'; + state = st_fieldname; + } + else /* Bogus try to recover */ + state = st_skipline; + break; + + case st_fieldname: + if (ch == ':' ) { + state = st_fieldvalue; + } + else if (is_token(ch)) { + if (!append_ch(field_name, sizeof field_name, &field_name_len, ch)) + state = st_skip_fieldname; + } + /* Bogus cases try to recover */ + else if (ch == '\n') + state = st_fieldfirst; + else + state = st_skipline; + break; + + case st_fieldvalue: + if (ch == '\n') + state = st_fieldfirst; + else { + fieldvalue: + if (!append_ch(field_value, sizeof field_value, &field_value_len, ch)) + state = st_skip_fieldvalue; + } + break; + + /* For valid fields whose names are longer than I choose to support. */ + case st_skip_fieldname: + if (ch == ':') + state = st_skip_fieldvalue; + else if (is_token(ch)) + state = st_skip_fieldname; + /* Bogus cases try to recover */ + else if (ch == '\n') + state = st_fieldfirst; + else + state = st_skipline; + break; + + /* For valid fields whose bodies are longer than I choose to support. */ + case st_skip_fieldvalue: + if (ch == '\n') + state = st_fieldfirst; + break; + + case st_eoh: + break; /* Should never happen */ + } + } + + if (state != st_eoh) + status = 0; + + switch (status) { + case 200: + /* + * All OK, need to mark header data consumed and set up a file + * structure... + */ + /* Treat the remainder of the bytes as data */ + socket->tftp_filepos -= response_size; + break; + case 301: + case 302: + case 303: + case 307: + /* A redirect */ + if (!location[0]) + goto fail; + *redir = location; + goto fail; + default: + goto fail; + break; + } + return; +fail: + inode->size = 0; + tcp_close_file(inode); + return; +} diff --git a/core/fs/pxe/http_readdir.c b/core/fs/pxe/http_readdir.c new file mode 100644 index 00000000..b6e480e7 --- /dev/null +++ b/core/fs/pxe/http_readdir.c @@ -0,0 +1,471 @@ +/* ----------------------------------------------------------------------- * + * + * Copyright 2011 Intel Corporation; author: H. Peter Anvin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston MA 02110-1301, USA; either version 2 of the License, or + * (at your option) any later version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#include <inttypes.h> +#include <string.h> +#include <stdlib.h> +#include <ctype.h> +#include <dprintf.h> +#include "pxe.h" + +enum http_readdir_state { + st_start, /* 0 Initial state */ + st_open, /* 1 "<" */ + st_a, /* 2 "<a" */ + st_attribute, /* 3 "<a " */ + st_h, /* 4 "<a h" */ + st_hr, /* 5 */ + st_hre, /* 6 */ + st_href, /* 7 */ + st_hrefeq, /* 8 */ + st_hrefqu, /* 9 */ + st_badtag, /* 10 */ + st_badtagqu, /* 11 */ + st_badattr, /* 12 */ + st_badattrqu, /* 13 */ +}; + +struct machine { + char xchar; + uint8_t st_xchar; + uint8_t st_left; /* < */ + uint8_t st_right; /* > */ + uint8_t st_space; /* white */ + uint8_t st_other; /* anything else */ +}; + +static const struct machine statemachine[] = { + /* xchar st_xchar st_left st_right st_space st_other */ + { 0, 0, st_open, st_start, st_start, st_start }, + { 'a', st_a, st_badtag, st_start, st_open, st_badtag }, + { 0, 0, st_open, st_open, st_attribute, st_badtag }, + { 'h', st_h, st_open, st_start, st_attribute, st_badattr }, + { 'r', st_hr, st_open, st_start, st_attribute, st_badattr }, + { 'e', st_hre, st_open, st_start, st_attribute, st_badattr }, + { 'f', st_href, st_open, st_start, st_attribute, st_badattr }, + { '=', st_hrefeq, st_open, st_start, st_attribute, st_badattr }, + { '\"', st_hrefqu, st_open, st_start, st_attribute, st_hrefeq }, + { '\"', st_attribute, st_hrefqu, st_hrefqu, st_hrefqu, st_hrefqu }, + { '\"', st_badtagqu, st_open, st_start, st_badtag, st_badtag }, + { '\"', st_badtag, st_badtagqu, st_badtagqu, st_badtagqu, st_badtagqu }, + { '\"', st_badattrqu, st_open, st_start, st_attribute, st_badattr }, + { '\"', st_attribute, st_badattrqu, st_badattrqu, st_badattrqu, st_badattrqu }, +}; + +struct html_entity { + uint16_t ucs; + const char entity[9]; +}; + +static const struct html_entity entities[] = { + { 34, "quot" }, + { 38, "amp" }, + { 60, "lt" }, + { 62, "gt" }, +#ifdef HTTP_ALL_ENTITIES + { 160, "nbsp" }, + { 161, "iexcl" }, + { 162, "cent" }, + { 163, "pound" }, + { 164, "curren" }, + { 165, "yen" }, + { 166, "brvbar" }, + { 167, "sect" }, + { 168, "uml" }, + { 169, "copy" }, + { 170, "ordf" }, + { 171, "laquo" }, + { 172, "not" }, + { 173, "shy" }, + { 174, "reg" }, + { 175, "macr" }, + { 176, "deg" }, + { 177, "plusmn" }, + { 178, "sup2" }, + { 179, "sup3" }, + { 180, "acute" }, + { 181, "micro" }, + { 182, "para" }, + { 183, "middot" }, + { 184, "cedil" }, + { 185, "sup1" }, + { 186, "ordm" }, + { 187, "raquo" }, + { 188, "frac14" }, + { 189, "frac12" }, + { 190, "frac34" }, + { 191, "iquest" }, + { 192, "Agrave" }, + { 193, "Aacute" }, + { 194, "Acirc" }, + { 195, "Atilde" }, + { 196, "Auml" }, + { 197, "Aring" }, + { 198, "AElig" }, + { 199, "Ccedil" }, + { 200, "Egrave" }, + { 201, "Eacute" }, + { 202, "Ecirc" }, + { 203, "Euml" }, + { 204, "Igrave" }, + { 205, "Iacute" }, + { 206, "Icirc" }, + { 207, "Iuml" }, + { 208, "ETH" }, + { 209, "Ntilde" }, + { 210, "Ograve" }, + { 211, "Oacute" }, + { 212, "Ocirc" }, + { 213, "Otilde" }, + { 214, "Ouml" }, + { 215, "times" }, + { 216, "Oslash" }, + { 217, "Ugrave" }, + { 218, "Uacute" }, + { 219, "Ucirc" }, + { 220, "Uuml" }, + { 221, "Yacute" }, + { 222, "THORN" }, + { 223, "szlig" }, + { 224, "agrave" }, + { 225, "aacute" }, + { 226, "acirc" }, + { 227, "atilde" }, + { 228, "auml" }, + { 229, "aring" }, + { 230, "aelig" }, + { 231, "ccedil" }, + { 232, "egrave" }, + { 233, "eacute" }, + { 234, "ecirc" }, + { 235, "euml" }, + { 236, "igrave" }, + { 237, "iacute" }, + { 238, "icirc" }, + { 239, "iuml" }, + { 240, "eth" }, + { 241, "ntilde" }, + { 242, "ograve" }, + { 243, "oacute" }, + { 244, "ocirc" }, + { 245, "otilde" }, + { 246, "ouml" }, + { 247, "divide" }, + { 248, "oslash" }, + { 249, "ugrave" }, + { 250, "uacute" }, + { 251, "ucirc" }, + { 252, "uuml" }, + { 253, "yacute" }, + { 254, "thorn" }, + { 255, "yuml" }, + { 338, "OElig" }, + { 339, "oelig" }, + { 352, "Scaron" }, + { 353, "scaron" }, + { 376, "Yuml" }, + { 402, "fnof" }, + { 710, "circ" }, + { 732, "tilde" }, + { 913, "Alpha" }, + { 914, "Beta" }, + { 915, "Gamma" }, + { 916, "Delta" }, + { 917, "Epsilon" }, + { 918, "Zeta" }, + { 919, "Eta" }, + { 920, "Theta" }, + { 921, "Iota" }, + { 922, "Kappa" }, + { 923, "Lambda" }, + { 924, "Mu" }, + { 925, "Nu" }, + { 926, "Xi" }, + { 927, "Omicron" }, + { 928, "Pi" }, + { 929, "Rho" }, + { 931, "Sigma" }, + { 932, "Tau" }, + { 933, "Upsilon" }, + { 934, "Phi" }, + { 935, "Chi" }, + { 936, "Psi" }, + { 937, "Omega" }, + { 945, "alpha" }, + { 946, "beta" }, + { 947, "gamma" }, + { 948, "delta" }, + { 949, "epsilon" }, + { 950, "zeta" }, + { 951, "eta" }, + { 952, "theta" }, + { 953, "iota" }, + { 954, "kappa" }, + { 955, "lambda" }, + { 956, "mu" }, + { 957, "nu" }, + { 958, "xi" }, + { 959, "omicron" }, + { 960, "pi" }, + { 961, "rho" }, + { 962, "sigmaf" }, + { 963, "sigma" }, + { 964, "tau" }, + { 965, "upsilon" }, + { 966, "phi" }, + { 967, "chi" }, + { 968, "psi" }, + { 969, "omega" }, + { 977, "thetasym" }, + { 978, "upsih" }, + { 982, "piv" }, + { 8194, "ensp" }, + { 8195, "emsp" }, + { 8201, "thinsp" }, + { 8204, "zwnj" }, + { 8205, "zwj" }, + { 8206, "lrm" }, + { 8207, "rlm" }, + { 8211, "ndash" }, + { 8212, "mdash" }, + { 8216, "lsquo" }, + { 8217, "rsquo" }, + { 8218, "sbquo" }, + { 8220, "ldquo" }, + { 8221, "rdquo" }, + { 8222, "bdquo" }, + { 8224, "dagger" }, + { 8225, "Dagger" }, + { 8226, "bull" }, + { 8230, "hellip" }, + { 8240, "permil" }, + { 8242, "prime" }, + { 8243, "Prime" }, + { 8249, "lsaquo" }, + { 8250, "rsaquo" }, + { 8254, "oline" }, + { 8260, "frasl" }, + { 8364, "euro" }, + { 8465, "image" }, + { 8472, "weierp" }, + { 8476, "real" }, + { 8482, "trade" }, + { 8501, "alefsym" }, + { 8592, "larr" }, + { 8593, "uarr" }, + { 8594, "rarr" }, + { 8595, "darr" }, + { 8596, "harr" }, + { 8629, "crarr" }, + { 8656, "lArr" }, + { 8657, "uArr" }, + { 8658, "rArr" }, + { 8659, "dArr" }, + { 8660, "hArr" }, + { 8704, "forall" }, + { 8706, "part" }, + { 8707, "exist" }, + { 8709, "empty" }, + { 8711, "nabla" }, + { 8712, "isin" }, + { 8713, "notin" }, + { 8715, "ni" }, + { 8719, "prod" }, + { 8721, "sum" }, + { 8722, "minus" }, + { 8727, "lowast" }, + { 8730, "radic" }, + { 8733, "prop" }, + { 8734, "infin" }, + { 8736, "ang" }, + { 8743, "and" }, + { 8744, "or" }, + { 8745, "cap" }, + { 8746, "cup" }, + { 8747, "int" }, + { 8756, "there4" }, + { 8764, "sim" }, + { 8773, "cong" }, + { 8776, "asymp" }, + { 8800, "ne" }, + { 8801, "equiv" }, + { 8804, "le" }, + { 8805, "ge" }, + { 8834, "sub" }, + { 8835, "sup" }, + { 8836, "nsub" }, + { 8838, "sube" }, + { 8839, "supe" }, + { 8853, "oplus" }, + { 8855, "otimes" }, + { 8869, "perp" }, + { 8901, "sdot" }, + { 8968, "lceil" }, + { 8969, "rceil" }, + { 8970, "lfloor" }, + { 8971, "rfloor" }, + { 9001, "lang" }, + { 9002, "rang" }, + { 9674, "loz" }, + { 9824, "spades" }, + { 9827, "clubs" }, + { 9829, "hearts" }, + { 9830, "diams" }, +#endif /* HTTP_ALL_ENTITIES */ + { 0, "" } +}; + +struct entity_state { + char entity_buf[16]; + char *ep; +}; + +static char *emit(char *p, int c, struct entity_state *st) +{ + const struct html_entity *ent; + unsigned int ucs; + + if (!st->ep) { + if (c == '&') { + /* Entity open */ + st->ep = st->entity_buf; + } else { + *p++ = c; + } + } else { + if (c == ';') { + st->ep = NULL; + *p = '\0'; + if (st->entity_buf[0] == '#') { + if ((st->entity_buf[1] | 0x20)== 'x') { + ucs = strtoul(st->entity_buf + 2, NULL, 16); + } else { + ucs = strtoul(st->entity_buf + 1, NULL, 10); + } + } else { + for (ent = entities; ent->ucs; ent++) { + if (!strcmp(st->entity_buf, ent->entity)) + break; + } + ucs = ent->ucs; + } + if (ucs < 32 || ucs >= 0x10ffff) + return p; /* Bogus */ + if (ucs >= 0x10000) { + *p++ = 0xf0 + (ucs >> 18); + *p++ = 0x80 + ((ucs >> 12) & 0x3f); + *p++ = 0x80 + ((ucs >> 6) & 0x3f); + *p++ = 0x80 + (ucs & 0x3f); + } else if (ucs >= 0x800) { + *p++ = 0xe0 + (ucs >> 12); + *p++ = 0x80 + ((ucs >> 6) & 0x3f); + *p++ = 0x80 + (ucs & 0x3f); + } else if (ucs >= 0x80) { + *p++ = 0xc0 + (ucs >> 6); + *p++ = 0x80 + (ucs & 0x3f); + } else { + *p++ = ucs; + } + } else if (st->ep < st->entity_buf + sizeof st->entity_buf - 1) { + *st->ep++ = c; + } + } + return p; +} + +static const char *http_get_filename(struct inode *inode, char *buf) +{ + int c, lc; + char *p; + const struct machine *sm; + struct entity_state es; + enum http_readdir_state state = st_start; + enum http_readdir_state pstate = st_start; + + memset(&es, 0, sizeof es); + + p = buf; + for (;;) { + c = pxe_getc(inode); + if (c == -1) + return NULL; + + lc = tolower(c); + + sm = &statemachine[state]; + + if (lc == sm->xchar) + state = sm->st_xchar; + else if (c == '<') + state = sm->st_left; + else if (c == '>') + state = sm->st_right; + else if (isspace(c)) + state = sm->st_space; + else + state = sm->st_other; + + if (state == st_hrefeq || state == st_hrefqu) { + if (state != pstate) + p = buf; + else if (p < buf + FILENAME_MAX) + p = emit(p, c, &es); + pstate = state; + } else { + if (pstate != st_start) + pstate = st_start; + if (p != buf && state == st_start) { + *p = '\0'; + return buf; + } + } + } +} + +int http_readdir(struct inode *inode, struct dirent *dirent) +{ + char buf[FILENAME_MAX + 6]; + const char *fn, *sp; + + for (;;) { + fn = http_get_filename(inode, buf); + + if (!fn) + return -1; /* End of directory */ + + /* Ignore entries with http special characters */ + if (strchr(fn, '#')) + continue; + if (strchr(fn, '?')) + continue; + + /* A slash if present has to be the last character, and not the first */ + sp = strchr(fn, '/'); + if (sp) { + if (sp == fn || sp[1]) + continue; + } else { + sp = strchr(fn, '\0'); + } + + if (sp > fn + NAME_MAX) + continue; + + dirent->d_ino = 0; /* Not applicable */ + dirent->d_off = 0; /* Not applicable */ + dirent->d_reclen = offsetof(struct dirent, d_name) + (sp-fn) + 1; + dirent->d_type = *sp == '/' ? DT_DIR : DT_REG; + memcpy(dirent->d_name, fn, sp-fn); + dirent->d_name[sp-fn] = '\0'; + return 0; + } +} diff --git a/core/fs/pxe/idle.c b/core/fs/pxe/idle.c index 52a87c34..1d1bb8bc 100644 --- a/core/fs/pxe/idle.c +++ b/core/fs/pxe/idle.c @@ -19,91 +19,8 @@ #include <sys/cpu.h> #include "pxe.h" -static int pxe_idle_poll(void) -{ - static __lowmem char junk_pkt[PKTBUF_SIZE]; - static __lowmem t_PXENV_UDP_READ read_buf; - - memset(&read_buf, 0, sizeof read_buf); - - read_buf.src_ip = 0; /* Any destination */ - read_buf.dest_ip = IPInfo.myip; - read_buf.s_port = 0; /* Any source port */ - read_buf.d_port = htons(9); /* Discard port (not used...) */ - read_buf.buffer_size = sizeof junk_pkt; - read_buf.buffer = FAR_PTR(junk_pkt); - - pxe_call(PXENV_UDP_READ, &read_buf); - - return 0; -} - -static uint32_t pxe_detect_nic_type(void) -{ - static __lowmem t_PXENV_UNDI_GET_NIC_TYPE nic_type; - - if (pxe_call(PXENV_UNDI_GET_NIC_TYPE, &nic_type)) - return -1; /* Unknown NIC */ - - if (nic_type.NicType != PCI_NIC && nic_type.NicType != CardBus_NIC) - return -1; /* Not a PCI NIC */ - - /* - * Return VID:DID as a single number, with the VID in the high word - * -- this is opposite from the usual order, but it makes it easier to - * enforce that the table is sorted. - */ - return (nic_type.info.pci.Vendor_ID << 16) + nic_type.info.pci.Dev_ID; -} - -#define PCI_DEV(vid, did) (((vid) << 16) + (did)) - -/* This array should be sorted!! */ -static const uint32_t pxe_need_idle_drain[] = -{ - /* - * Older Broadcom NICs: they need receive calls on idle to avoid - * FIFO stalls. - */ - PCI_DEV(0x14e4, 0x1659), /* BCM5721 */ - PCI_DEV(0x14e4, 0x165a), /* BCM5722 */ - PCI_DEV(0x14e4, 0x165b), /* BCM5723 */ - PCI_DEV(0x14e4, 0x1668), /* BCM5714 */ - PCI_DEV(0x14e4, 0x1669), /* BCM5714S */ - PCI_DEV(0x14e4, 0x166a), /* BCM5780 */ - PCI_DEV(0x14e4, 0x1673), /* BCM5755M */ - PCI_DEV(0x14e4, 0x1674), /* BCM5756ME */ - PCI_DEV(0x14e4, 0x1678), /* BCM5715 */ - PCI_DEV(0x14e4, 0x1679), /* BCM5715S */ - PCI_DEV(0x14e4, 0x167b), /* BCM5755 */ -}; - void pxe_idle_init(void) { - uint32_t dev_id = pxe_detect_nic_type(); - int l, h; - bool found; - - l = 0; - h = sizeof pxe_need_idle_drain / sizeof pxe_need_idle_drain[0] - 1; - - found = false; - while (h >= l) { - int x = (l+h) >> 1; - uint32_t id = pxe_need_idle_drain[x]; - - if (id == dev_id) { - found = true; - break; - } else if (id < dev_id) { - l = x+1; - } else { - h = x-1; - } - } - - if (found) - idle_hook_func = pxe_idle_poll; } void pxe_idle_cleanup(void) diff --git a/core/fs/pxe/isr.c b/core/fs/pxe/isr.c new file mode 100644 index 00000000..069fefd5 --- /dev/null +++ b/core/fs/pxe/isr.c @@ -0,0 +1,278 @@ +/* + * core/fs/pxe/isr.c + * + * Stub invoked on return from real mode including from an interrupt. + * Interrupts are locked out on entry. + */ + +#include "core.h" +#include "thread.h" +#include "pxe.h" +#include <string.h> +#include <sys/cpu.h> +#include <sys/io.h> + +extern uint8_t pxe_irq_pending; +extern volatile uint8_t pxe_need_poll; +static DECLARE_INIT_SEMAPHORE(pxe_receive_thread_sem, 0); +static DECLARE_INIT_SEMAPHORE(pxe_poll_thread_sem, 0); +static struct thread *pxe_thread, *poll_thread; + +/* + * Note: this *must* be called with interrupts enabled. + */ +static bool install_irq_vector(uint8_t irq, void (*isr)(void), far_ptr_t *old) +{ + far_ptr_t *entry; + unsigned int vec; + uint8_t mask, mymask; + uint32_t now; + bool ok; + + if (irq < 8) + vec = irq + 0x08; + else if (irq < 16) + vec = (irq - 8) + 0x70; + else + return false; + + cli(); + + if (pxe_need_poll) { + sti(); + return false; + } + + entry = (far_ptr_t *)(vec << 2); + *old = *entry; + entry->ptr = (uint32_t)isr; + + /* Enable this interrupt at the PIC level, just in case... */ + mymask = ~(1 << (irq & 7)); + if (irq >= 8) { + mask = inb(0x21); + mask &= ~(1 << 2); /* Enable cascade */ + outb(mask, 0x21); + mask = inb(0xa1); + mask &= mymask; + outb(mask, 0xa1); + } else { + mask = inb(0x21); + mask &= mymask; + outb(mask, 0x21); + } + + sti(); + + now = jiffies(); + + /* Some time to watch for stuck interrupts */ + while (jiffies() - now < 4 && (ok = !pxe_need_poll)) + hlt(); + + if (!ok) + *entry = *old; /* Restore the old vector */ + + printf("UNDI: IRQ %d(0x%02x): %04x:%04x -> %04x:%04x\n", irq, vec, + old->seg, old->offs, entry->seg, entry->offs); + + return ok; +} + +static bool uninstall_irq_vector(uint8_t irq, void (*isr), far_ptr_t *old) +{ + far_ptr_t *entry; + unsigned int vec; + bool rv; + + if (!irq) + return true; /* Nothing to uninstall */ + + if (irq < 8) + vec = irq + 0x08; + else if (irq < 16) + vec = (irq - 8) + 0x70; + else + return false; + + cli(); + + entry = (far_ptr_t *)(vec << 2); + + if (entry->ptr != (uint32_t)isr) { + rv = false; + } else { + *entry = *old; + rv = true; + } + + sti(); + return rv; +} + +static void pxe_poll_wakeups(void) +{ + static jiffies_t last_jiffies = 0; + jiffies_t now = jiffies(); + + if (pxe_need_poll == 1) { + /* If we need polling now, activate polling */ + pxe_need_poll = 3; + sem_up(&pxe_poll_thread_sem); + } + + if (now != last_jiffies) { + last_jiffies = now; + __thread_process_timeouts(); + } + + if (pxe_irq_pending) { + pxe_irq_pending = 0; + sem_up(&pxe_receive_thread_sem); + } +} + +static void pxe_process_irq(void) +{ + static __lowmem t_PXENV_UNDI_ISR isr; + + uint16_t func = PXENV_UNDI_ISR_IN_PROCESS; /* First time */ + bool done = false; + + while (!done) { + memset(&isr, 0, sizeof isr); + isr.FuncFlag = func; + func = PXENV_UNDI_ISR_IN_GET_NEXT; /* Next time */ + + pxe_call(PXENV_UNDI_ISR, &isr); + + switch (isr.FuncFlag) { + case PXENV_UNDI_ISR_OUT_DONE: + done = true; + break; + + case PXENV_UNDI_ISR_OUT_TRANSMIT: + /* Transmit complete - nothing for us to do */ + break; + + case PXENV_UNDI_ISR_OUT_RECEIVE: + undiif_input(&isr); + break; + + case PXENV_UNDI_ISR_OUT_BUSY: + /* ISR busy, this should not happen */ + done = true; + break; + + default: + /* Invalid return code, this should not happen */ + done = true; + break; + } + } +} + +static void pxe_receive_thread(void *dummy) +{ + (void)dummy; + + for (;;) { + sem_down(&pxe_receive_thread_sem, 0); + pxe_process_irq(); + } +} + +static bool pxe_isr_poll(void) +{ + static __lowmem t_PXENV_UNDI_ISR isr; + + isr.FuncFlag = PXENV_UNDI_ISR_IN_START; + pxe_call(PXENV_UNDI_ISR, &isr); + + return isr.FuncFlag == PXENV_UNDI_ISR_OUT_OURS; +} + +static void pxe_poll_thread(void *dummy) +{ + (void)dummy; + + /* Block indefinitely unless activated */ + sem_down(&pxe_poll_thread_sem, 0); + + for (;;) { + cli(); + if (pxe_receive_thread_sem.count < 0 && pxe_isr_poll()) + sem_up(&pxe_receive_thread_sem); + else + __schedule(); + sti(); + cpu_relax(); + } +} + +/* + * This does preparations and enables the PXE thread + */ +void pxe_init_isr(void) +{ + start_idle_thread(); + sched_hook_func = pxe_poll_wakeups; + /* + * Run the pxe receive thread at elevated priority, since the UNDI + * stack is likely to have very limited memory available; therefore to + * avoid packet loss we need to move it into memory that we ourselves + * manage, as soon as possible. + */ + core_pm_hook = __schedule; + + pxe_thread = start_thread("pxe receive", 16384, -20, + pxe_receive_thread, NULL); +} + +/* + * Actually start the interrupt routine inside the UNDI stack + */ +void pxe_start_isr(void) +{ + int irq = pxe_undi_info.IntNumber; + + if (irq == 2) + irq = 9; /* IRQ 2 is really IRQ 9 */ + else if (irq > 15) + irq = 0; /* Invalid IRQ */ + + pxe_irq_vector = irq; + + if (irq) { + if (!install_irq_vector(irq, pxe_isr, &pxe_irq_chain)) + irq = 0; /* Install failed or stuck interrupt */ + } + + poll_thread = start_thread("pxe poll", 4096, POLL_THREAD_PRIORITY, + pxe_poll_thread, NULL); + + if (!irq || !(pxe_undi_iface.ServiceFlags & PXE_UNDI_IFACE_FLAG_IRQ)) + asm volatile("orb $1,%0" : "+m" (pxe_need_poll)); +} + +int reset_pxe(void) +{ + static __lowmem struct s_PXENV_UNDI_CLOSE undi_close; + + sched_hook_func = NULL; + core_pm_hook = core_pm_null_hook; + kill_thread(pxe_thread); + + memset(&undi_close, 0, sizeof(undi_close)); + pxe_call(PXENV_UNDI_CLOSE, &undi_close); + + if (undi_close.Status) + printf("PXENV_UNDI_CLOSE failed: 0x%x\n", undi_close.Status); + + if (pxe_irq_vector) + uninstall_irq_vector(pxe_irq_vector, pxe_isr, &pxe_irq_chain); + if (poll_thread) + kill_thread(poll_thread); + + return undi_close.Status; +} diff --git a/core/fs/pxe/portnum.c b/core/fs/pxe/portnum.c deleted file mode 100644 index 19af0cd0..00000000 --- a/core/fs/pxe/portnum.c +++ /dev/null @@ -1,68 +0,0 @@ -/* ----------------------------------------------------------------------- * - * - * Copyright 2010 Intel Corporation; author: H. Peter Anvin - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, - * Boston MA 02110-1301, USA; either version 2 of the License, or - * (at your option) any later version; incorporated herein by reference. - * - * ----------------------------------------------------------------------- */ - -#include <stdint.h> -#include <stdbool.h> -#include <netinet/in.h> -#include "pxe.h" - -/* Port number bitmap - port numbers 49152 (0xc000) to 57343 (0xefff) */ -#define PORT_NUMBER_BASE 49152 -#define PORT_NUMBER_COUNT 8192 /* Power of 2, please */ -static uint32_t port_number_bitmap[PORT_NUMBER_COUNT/32]; -static uint16_t first_port_number /* = 0 */; - -/* - * Bitmap functions - */ -static bool test_bit(const uint32_t *bitmap, int32_t index) -{ - uint8_t st; - asm("btl %2,%1 ; setc %0" : "=qm" (st) : "m" (*bitmap), "r" (index)); - return st; -} - -static void set_bit(uint32_t *bitmap, int32_t index) -{ - asm volatile("btsl %1,%0" : "+m" (*bitmap) : "r" (index) : "memory"); -} - -static void clr_bit(uint32_t *bitmap, int32_t index) -{ - asm volatile("btcl %1,%0" : "+m" (*bitmap) : "r" (index) : "memory"); -} - -/* - * Get and free a port number (host byte order) - */ -uint16_t get_port(void) -{ - uint16_t port; - - do { - port = first_port_number++; - first_port_number &= PORT_NUMBER_COUNT - 1; - } while (test_bit(port_number_bitmap, port)); - - set_bit(port_number_bitmap, port); - return htons(port + PORT_NUMBER_BASE); -} - -void free_port(uint16_t port) -{ - port = ntohs(port) - PORT_NUMBER_BASE; - - if (port >= PORT_NUMBER_COUNT) - return; - - clr_bit(port_number_bitmap, port); -} diff --git a/core/fs/pxe/pxe.c b/core/fs/pxe/pxe.c index c07f00ee..9f18f282 100644 --- a/core/fs/pxe/pxe.c +++ b/core/fs/pxe/pxe.c @@ -5,10 +5,16 @@ #include <bios.h> #include <fs.h> #include <minmax.h> +#include <fcntl.h> #include <sys/cpu.h> #include "pxe.h" +#include "thread.h" +#include "url.h" +#include "tftp.h" +#include <net.h> -#define GPXE 1 +__lowmem t_PXENV_UNDI_GET_INFORMATION pxe_undi_info; +__lowmem t_PXENV_UNDI_GET_IFACE_INFO pxe_undi_iface; static uint16_t real_base_mem; /* Amount of DOS memory after freeing */ @@ -16,45 +22,26 @@ uint8_t MAC[MAC_MAX]; /* Actual MAC address */ uint8_t MAC_len; /* MAC address len */ uint8_t MAC_type; /* MAC address type */ -char __bss16 BOOTIFStr[7+3*(MAC_MAX+1)]; -#define MAC_str (BOOTIFStr+7) /* The actual hardware address */ -char __bss16 SYSUUIDStr[8+32+5]; -#define UUID_str (SYSUUIDStr+8) /* The actual UUID */ - char boot_file[256]; /* From DHCP */ char path_prefix[256]; /* From DHCP */ -char dot_quad_buf[16]; static bool has_gpxe; static uint32_t gpxe_funcs; bool have_uuid = false; -/* Common receive buffer */ -static __lowmem char packet_buf[PKTBUF_SIZE] __aligned(16); - -const uint8_t TimeoutTable[] = { - 2, 2, 3, 3, 4, 5, 6, 7, 9, 10, 12, 15, 18, 21, 26, 31, 37, 44, - 53, 64, 77, 92, 110, 132, 159, 191, 229, 255, 255, 255, 255, 0 -}; - -struct tftp_options { - const char *str_ptr; /* string pointer */ - size_t offset; /* offset into socket structre */ -}; - -#define IFIELD(x) offsetof(struct inode, x) -#define PFIELD(x) (offsetof(struct inode, pvt) + \ - offsetof(struct pxe_pvt_inode, x)) - -static const struct tftp_options tftp_options[] = -{ - { "tsize", IFIELD(size) }, - { "blksize", PFIELD(tftp_blksize) }, +static struct url_scheme { + const char *name; + void (*open)(struct url_info *, int, struct inode *, const char **); + int ok_flags; +} url_schemes[] = { + { "tftp", tftp_open, 0 }, +#ifdef IS_LPXELINUX + { "http", http_open, O_DIRECTORY }, + { "ftp", ftp_open, O_DIRECTORY }, +#endif + { NULL, NULL, 0 }, }; -static const int tftp_nopts = sizeof tftp_options / sizeof tftp_options[0]; - -static void tftp_error(struct inode *file, uint16_t errnum, - const char *errstr); +#define OK_FLAGS_MASK (O_DIRECTORY|O_WRONLY) /* * Allocate a local UDP port structure and assign it a local port number. @@ -67,108 +54,35 @@ static struct inode *allocate_socket(struct fs_info *fs) if (!inode) { malloc_error("socket structure"); } else { - struct pxe_pvt_inode *socket = PVT(inode); - socket->tftp_localport = get_port(); inode->mode = DT_REG; /* No other types relevant for PXE */ } return inode; } -static void free_socket(struct inode *inode) +void free_socket(struct inode *inode) { struct pxe_pvt_inode *socket = PVT(inode); - free_port(socket->tftp_localport); + free(socket->tftp_pktbuf); /* If we allocated a buffer, free it now */ free_inode(inode); } -#if GPXE -static void gpxe_close_file(struct inode *inode) -{ - struct pxe_pvt_inode *socket = PVT(inode); - static __lowmem struct s_PXENV_FILE_CLOSE file_close; - - file_close.FileHandle = socket->tftp_remoteport; - pxe_call(PXENV_FILE_CLOSE, &file_close); -} -#endif - static void pxe_close_file(struct file *file) { struct inode *inode = file->inode; struct pxe_pvt_inode *socket = PVT(inode); + if (!inode) + return; + if (!socket->tftp_goteof) { -#if GPXE - if (socket->tftp_localport == 0xffff) { - gpxe_close_file(inode); - } else -#endif - if (socket->tftp_localport != 0) { - tftp_error(inode, 0, "No error, file close"); - } + socket->ops->close(inode); } free_socket(inode); } -/** - * Take a nubmer of bytes in memory and convert to lower-case hxeadecimal - * - * @param: dst, output buffer - * @param: src, input buffer - * @param: count, number of bytes - * - */ -static void lchexbytes(char *dst, const void *src, int count) -{ - uint8_t half; - uint8_t c; - const uint8_t *s = src; - - for(; count > 0; count--) { - c = *s++; - half = ((c >> 4) & 0x0f) + '0'; - *dst++ = half > '9' ? (half + 'a' - '9' - 1) : half; - - half = (c & 0x0f) + '0'; - *dst++ = half > '9' ? (half + 'a' - '9' - 1) : half; - } -} - -/* - * just like the lchexbytes, except to upper-case - * - */ -static void uchexbytes(char *dst, const void *src, int count) -{ - uint8_t half; - uint8_t c; - const uint8_t *s = src; - - for(; count > 0; count--) { - c = *s++; - half = ((c >> 4) & 0x0f) + '0'; - *dst++ = half > '9' ? (half + 'A' - '9' - 1) : half; - - half = (c & 0x0f) + '0'; - *dst++ = half > '9' ? (half + 'A' - '9' - 1) : half; - } -} - -/* - * Parse a single hexadecimal byte, which must be complete (two - * digits). This is used in URL parsing. - */ -static int hexbyte(const char *p) -{ - if (!is_hex(p[0]) || !is_hex(p[1])) - return -1; - else - return (hexval(p[0]) << 4) + hexval(p[1]); -} - /* * Tests an IP address in _ip_ for validity; return with 0 for bad, 1 for good. * We used to refuse class E, but class E addresses are likely to become @@ -197,57 +111,11 @@ bool ip_ok(uint32_t ip) */ static int gendotquad(char *dst, uint32_t ip) { - int part; - int i = 0, j; - char temp[4]; - char *p = dst; - - for (; i < 4; i++) { - j = 0; - part = ip & 0xff; - do { - temp[j++] = (part % 10) + '0'; - }while(part /= 10); - for (; j > 0; j--) - *p++ = temp[j-1]; - *p++ = '.'; - - ip >>= 8; - } - /* drop the last dot '.' and zero-terminate string*/ - *(--p) = 0; - - return p - dst; -} - -/* - * parse the ip_str and return the ip address with *res. - * return the the string address after the ip string - * - */ -static const char *parse_dotquad(const char *ip_str, uint32_t *res) -{ - const char *p = ip_str; - uint8_t part = 0; - uint32_t ip = 0; - int i; - - for (i = 0; i < 4; i++) { - while (is_digit(*p)) { - part = part * 10 + *p - '0'; - p++; - } - if (i != 3 && *p != '.') - return NULL; - - ip = (ip << 8) | part; - part = 0; - p++; - } - p--; - - *res = htonl(ip); - return p; + return sprintf(dst, "%u.%u.%u.%u", + ((const uint8_t *)&ip)[0], + ((const uint8_t *)&ip)[1], + ((const uint8_t *)&ip)[2], + ((const uint8_t *)&ip)[3]); } /* @@ -256,11 +124,14 @@ static const char *parse_dotquad(const char *ip_str, uint32_t *res) */ __export int pxe_call(int opcode, void *data) { + static DECLARE_INIT_SEMAPHORE(pxe_sem, 1); extern void pxenv(void); com32sys_t regs; + sem_down(&pxe_sem, 0); + #if 0 - printf("pxe_call op %04x data %p\n", opcode, data); + dprintf("pxe_call op %04x data %p\n", opcode, data); #endif memset(®s, 0, sizeof regs); @@ -269,77 +140,11 @@ __export int pxe_call(int opcode, void *data) regs.edi.w[0] = OFFS(data); call16(pxenv, ®s, ®s); - return regs.eflags.l & EFLAGS_CF; /* CF SET if fail */ -} - -/** - * Send an ERROR packet. This is used to terminate a connection. - * - * @inode: Inode structure - * @errnum: Error number (network byte order) - * @errstr: Error string (included in packet) - */ -static void tftp_error(struct inode *inode, uint16_t errnum, - const char *errstr) -{ - static __lowmem struct { - uint16_t err_op; - uint16_t err_num; - char err_msg[64]; - } __packed err_buf; - static __lowmem struct s_PXENV_UDP_WRITE udp_write; - int len = min(strlen(errstr), sizeof(err_buf.err_msg)-1); - struct pxe_pvt_inode *socket = PVT(inode); - - err_buf.err_op = TFTP_ERROR; - err_buf.err_num = errnum; - memcpy(err_buf.err_msg, errstr, len); - err_buf.err_msg[len] = '\0'; - - udp_write.src_port = socket->tftp_localport; - udp_write.dst_port = socket->tftp_remoteport; - udp_write.ip = socket->tftp_remoteip; - udp_write.gw = gateway(udp_write.ip); - udp_write.buffer = FAR_PTR(&err_buf); - udp_write.buffer_size = 4 + len + 1; + sem_up(&pxe_sem); - /* If something goes wrong, there is nothing we can do, anyway... */ - pxe_call(PXENV_UDP_WRITE, &udp_write); -} - - -/** - * Send ACK packet. This is a common operation and so is worth canning. - * - * @param: inode, Inode pointer - * @param: ack_num, Packet # to ack (network byte order) - * - */ -static void ack_packet(struct inode *inode, uint16_t ack_num) -{ - int err; - static __lowmem uint16_t ack_packet_buf[2]; - static __lowmem struct s_PXENV_UDP_WRITE udp_write; - struct pxe_pvt_inode *socket = PVT(inode); - - /* Packet number to ack */ - ack_packet_buf[0] = TFTP_ACK; - ack_packet_buf[1] = ack_num; - udp_write.src_port = socket->tftp_localport; - udp_write.dst_port = socket->tftp_remoteport; - udp_write.ip = socket->tftp_remoteip; - udp_write.gw = gateway(udp_write.ip); - udp_write.buffer = FAR_PTR(ack_packet_buf); - udp_write.buffer_size = 4; - - err = pxe_call(PXENV_UDP_WRITE, &udp_write); - (void)err; -#if 0 - printf("sent %s\n", err ? "FAILED" : "OK"); -#endif + return regs.eflags.l & EFLAGS_CF; /* CF SET if fail */ } - /** * Get a DHCP packet from the PXE stack into a lowmem buffer * @@ -353,7 +158,7 @@ static int pxe_get_cached_info(int type, void *buf, size_t bufsiz) static __lowmem struct s_PXENV_GET_CACHED_INFO get_cached_info; printf(" %02x", type); - get_cached_info.Status = 0; + memset(&get_cached_info, 0, sizeof get_cached_info); get_cached_info.PacketType = type; get_cached_info.BufferSize = bufsiz; get_cached_info.Buffer = FAR_PTR(buf); @@ -366,104 +171,13 @@ static int pxe_get_cached_info(int type, void *buf, size_t bufsiz) return get_cached_info.BufferSize; } - -/* - * Return the type of pathname passed. - */ -enum pxe_path_type { - PXE_RELATIVE, /* No :: or URL */ - PXE_HOMESERVER, /* Starting with :: */ - PXE_TFTP, /* host:: */ - PXE_URL_TFTP, /* tftp:// */ - PXE_URL, /* Absolute URL syntax */ -}; - -static enum pxe_path_type pxe_path_type(const char *str) -{ - const char *p; - - p = str; - - while (1) { - switch (*p) { - case ':': - if (p[1] == ':') { - if (p == str) - return PXE_HOMESERVER; - else - return PXE_TFTP; - } else if (p > str && p[1] == '/' && p[2] == '/') { - if (!strncasecmp(str, "tftp://", 7)) - return PXE_URL_TFTP; - else - return PXE_URL; - } - - /* else fall through */ - case '/': case '!': case '@': case '#': case '%': - case '^': case '&': case '*': case '(': case ')': - case '[': case ']': case '{': case '}': case '\\': - case '|': case '=': case '`': case '~': case '\'': - case '\"': case ';': case '>': case '<': case '?': - case '\0': - /* Any of these characters terminate the colon search */ - return PXE_RELATIVE; - default: - break; - } - p++; - } -} - -#if GPXE - -/** - * Get a fresh packet from a gPXE socket - * @param: inode -> Inode pointer - * - */ -static void get_packet_gpxe(struct inode *inode) -{ - struct pxe_pvt_inode *socket = PVT(inode); - static __lowmem struct s_PXENV_FILE_READ file_read; - int err; - - while (1) { - file_read.FileHandle = socket->tftp_remoteport; - file_read.Buffer = FAR_PTR(packet_buf); - file_read.BufferSize = PKTBUF_SIZE; - err = pxe_call(PXENV_FILE_READ, &file_read); - if (!err) /* successed */ - break; - - if (file_read.Status != PXENV_STATUS_TFTP_OPEN) - kaboom(); - } - - memcpy(socket->tftp_pktbuf, packet_buf, file_read.BufferSize); - - socket->tftp_dataptr = socket->tftp_pktbuf; - socket->tftp_bytesleft = file_read.BufferSize; - socket->tftp_filepos += file_read.BufferSize; - - if (socket->tftp_bytesleft == 0) - inode->size = socket->tftp_filepos; - - /* if we're done here, close the file */ - if (inode->size > socket->tftp_filepos) - return; - - /* Got EOF, close it */ - socket->tftp_goteof = 1; - gpxe_close_file(inode); -} -#endif /* GPXE */ - - /* * mangle a filename pointed to by _src_ into a buffer pointed * to by _dst_; ends on encountering any whitespace. * + * This deliberately does not attempt to do any conversion of + * pathname separators. + * */ static void pxe_mangle_name(char *dst, const char *src) { @@ -476,109 +190,40 @@ static void pxe_mangle_name(char *dst, const char *src) } /* - * Get a fresh packet if the buffer is drained, and we haven't hit - * EOF yet. The buffer should be filled immediately after draining! + * Read a single character from the specified pxe inode. + * Very useful for stepping through http streams and + * parsing their headers. */ -static void fill_buffer(struct inode *inode) +int pxe_getc(struct inode *inode) { - int err; - int last_pkt; - const uint8_t *timeout_ptr; - uint8_t timeout; - uint16_t buffersize; - uint32_t oldtime; - void *data = NULL; - static __lowmem struct s_PXENV_UDP_READ udp_read; struct pxe_pvt_inode *socket = PVT(inode); + unsigned char byte; - if (socket->tftp_bytesleft || socket->tftp_goteof) - return; + while (!socket->tftp_bytesleft) { + if (socket->tftp_goteof) + return -1; -#if GPXE - if (socket->tftp_localport == 0xffff) { - get_packet_gpxe(inode); - return; + socket->ops->fill_buffer(inode); } -#endif - /* - * Start by ACKing the previous packet; this should cause - * the next packet to be sent. - */ - timeout_ptr = TimeoutTable; - timeout = *timeout_ptr++; - oldtime = jiffies(); - - ack_again: - ack_packet(inode, socket->tftp_lastpkt); - - while (timeout) { - udp_read.buffer = FAR_PTR(packet_buf); - udp_read.buffer_size = PKTBUF_SIZE; - udp_read.src_ip = socket->tftp_remoteip; - udp_read.dest_ip = IPInfo.myip; - udp_read.s_port = socket->tftp_remoteport; - udp_read.d_port = socket->tftp_localport; - err = pxe_call(PXENV_UDP_READ, &udp_read); - if (err) { - uint32_t now = jiffies(); - - if (now-oldtime >= timeout) { - oldtime = now; - timeout = *timeout_ptr++; - if (!timeout) - break; - } - continue; - } + byte = *socket->tftp_dataptr; + socket->tftp_bytesleft -= 1; + socket->tftp_dataptr += 1; - if (udp_read.buffer_size < 4) /* Bad size for a DATA packet */ - continue; - - data = packet_buf; - if (*(uint16_t *)data != TFTP_DATA) /* Not a data packet */ - continue; - - /* If goes here, recevie OK, break */ - break; - } - - /* time runs out */ - if (timeout == 0) - kaboom(); + return byte; +} - last_pkt = socket->tftp_lastpkt; - last_pkt = ntohs(last_pkt); /* Host byte order */ - last_pkt++; - last_pkt = htons(last_pkt); /* Network byte order */ - if (*(uint16_t *)(data + 2) != last_pkt) { - /* - * Wrong packet, ACK the packet and try again. - * This is presumably because the ACK got lost, - * so the server just resent the previous packet. - */ -#if 0 - printf("Wrong packet, wanted %04x, got %04x\n", \ - htons(last_pkt), htons(*(uint16_t *)(data+2))); -#endif - goto ack_again; - } +/* + * Get a fresh packet if the buffer is drained, and we haven't hit + * EOF yet. The buffer should be filled immediately after draining! + */ +static void fill_buffer(struct inode *inode) +{ + struct pxe_pvt_inode *socket = PVT(inode); + if (socket->tftp_bytesleft || socket->tftp_goteof) + return; - /* It's the packet we want. We're also EOF if the size < blocksize */ - socket->tftp_lastpkt = last_pkt; /* Update last packet number */ - buffersize = udp_read.buffer_size - 4; /* Skip TFTP header */ - memcpy(socket->tftp_pktbuf, packet_buf + 4, buffersize); - socket->tftp_dataptr = socket->tftp_pktbuf; - socket->tftp_filepos += buffersize; - socket->tftp_bytesleft = buffersize; - if (buffersize < socket->tftp_blksize) { - /* it's the last block, ACK packet immediately */ - ack_packet(inode, *(uint16_t *)(data + 2)); - - /* Make sure we know we are at end of file */ - inode->size = socket->tftp_filepos; - socket->tftp_goteof = 1; - } + return socket->ops->fill_buffer(inode); } @@ -635,8 +280,20 @@ static uint32_t pxe_getfssec(struct file *file, char *buf, return bytes_read; } +/* + * Assign an IP address to a URL + */ +static void url_set_ip(struct url_info *url) +{ + url->ip = 0; + if (url->host) + url->ip = dns_resolv(url->host); + if (!url->ip) + url->ip = IPInfo.serverip; +} + /** - * Open a TFTP connection to the server + * Open the specified connection * * @param:filename, the file we wanna open * @@ -644,352 +301,95 @@ static uint32_t pxe_getfssec(struct file *file, char *buf, * @out: the lenght of this file, stores in file->file_len * */ -static void __pxe_searchdir(const char *filename, struct file *file); +static void __pxe_searchdir(const char *filename, int flags, struct file *file); extern uint16_t PXERetry; -static void pxe_searchdir(const char *filename, struct file *file) +static void pxe_searchdir(const char *filename, int flags, struct file *file) { int i = PXERetry; do { dprintf("PXE: file = %p, retries left = %d: ", file, i); - __pxe_searchdir(filename, file); + __pxe_searchdir(filename, flags, file); dprintf("%s\n", file->inode ? "ok" : "failed"); } while (!file->inode && i--); } - -static void __pxe_searchdir(const char *filename, struct file *file) +static void __pxe_searchdir(const char *filename, int flags, struct file *file) { struct fs_info *fs = file->fs; struct inode *inode; - struct pxe_pvt_inode *socket; - char *buf; - const char *np; - char *p; - char *options; - char *data; - static __lowmem struct s_PXENV_UDP_WRITE udp_write; - static __lowmem struct s_PXENV_UDP_READ udp_read; - static __lowmem struct s_PXENV_FILE_OPEN file_open; - static const char rrq_tail[] = "octet\0""tsize\0""0\0""blksize\0""1408"; - static __lowmem char rrq_packet_buf[2+2*FILENAME_MAX+sizeof rrq_tail]; - const struct tftp_options *tftp_opt; - int i = 0; - int err; - int buffersize; - int rrq_len; - const uint8_t *timeout_ptr; - uint32_t timeout; - uint32_t oldtime; - uint16_t tid; - uint16_t opcode; - uint16_t blk_num; - uint32_t ip = 0; - uint32_t opdata, *opdata_ptr; - enum pxe_path_type path_type; char fullpath[2*FILENAME_MAX]; - uint16_t server_port = TFTP_PORT; /* TFTP server port */ +#if GPXE + char urlsave[2*FILENAME_MAX]; +#endif + struct url_info url; + const struct url_scheme *us = NULL; + int redirect_count = 0; + bool found_scheme = false; inode = file->inode = NULL; - - buf = rrq_packet_buf; - *(uint16_t *)buf = TFTP_RRQ; /* TFTP opcode */ - buf += 2; - - path_type = pxe_path_type(filename); - if (path_type == PXE_RELATIVE) { - snprintf(fullpath, sizeof fullpath, "%s%s", fs->cwd_name, filename); - path_type = pxe_path_type(filename = fullpath); - } - switch (path_type) { - case PXE_RELATIVE: /* Really shouldn't happen... */ - case PXE_URL: - buf = stpcpy(buf, filename); - ip = IPInfo.serverip; /* Default server */ - break; - - case PXE_HOMESERVER: - buf = stpcpy(buf, filename+2); - ip = IPInfo.serverip; - break; - - case PXE_TFTP: - np = strchr(filename, ':'); - buf = stpcpy(buf, np+2); - if (parse_dotquad(filename, &ip) != np) - ip = dns_resolv(filename); - break; - - case PXE_URL_TFTP: - np = filename + 7; - while (*np && *np != '/' && *np != ':') - np++; - if (np > filename + 7) { - if (parse_dotquad(filename + 7, &ip) != np) - ip = dns_resolv(filename + 7); - } - if (*np == ':') { - np++; - server_port = 0; - while (*np >= '0' && *np <= '9') - server_port = server_port * 10 + *np++ - '0'; - server_port = server_port ? htons(server_port) : TFTP_PORT; - } - if (*np == '/') - np++; /* Do *NOT* eat more than one slash here... */ - /* - * The ; is because of a quirk in the TFTP URI spec (RFC - * 3617); it is to be followed by TFTP modes, which we just ignore. - */ - while (*np && *np != ';') { - int v; - if (*np == '%' && (v = hexbyte(np+1)) > 0) { - *buf++ = v; - np += 3; - } else { - *buf++ = *np++; - } - } - *buf = '\0'; - break; - } - - buf++; /* Point *past* the final NULL */ - memcpy(buf, rrq_tail, sizeof rrq_tail); - buf += sizeof rrq_tail; - - rrq_len = buf - rrq_packet_buf; - - inode = allocate_socket(fs); - if (!inode) - return; /* Allocation failure */ - socket = PVT(inode); + while (filename) { + if (redirect_count++ > 5) + break; + strlcpy(fullpath, filename, sizeof fullpath); #if GPXE - if (path_type == PXE_URL) { - if (has_gpxe) { - file_open.Status = PXENV_STATUS_BAD_FUNC; - file_open.FileName = FAR_PTR(rrq_packet_buf + 2); - err = pxe_call(PXENV_FILE_OPEN, &file_open); - if (err) - goto done; - - socket->tftp_localport = -1; - socket->tftp_remoteport = file_open.FileHandle; - inode->size = -1; - goto done; - } else { - static bool already = false; - if (!already) { - printf("URL syntax, but gPXE extensions not detected, " - "trying plain TFTP...\n"); - already = true; - } + strcpy(urlsave, fullpath); +#endif + parse_url(&url, fullpath); + if (url.type == URL_SUFFIX) { + snprintf(fullpath, sizeof fullpath, "%s%s", fs->cwd_name, filename); +#if GPXE + strcpy(urlsave, fullpath); +#endif + parse_url(&url, fullpath); } - } -#endif /* GPXE */ - - if (!ip) - goto done; /* No server */ - - timeout_ptr = TimeoutTable; /* Reset timeout */ - -sendreq: - timeout = *timeout_ptr++; - if (!timeout) { - free_socket(inode); - return; /* No file available... */ - } - oldtime = jiffies(); - - socket->tftp_remoteip = ip; - tid = socket->tftp_localport; /* TID(local port No) */ - udp_write.buffer = FAR_PTR(rrq_packet_buf); - udp_write.ip = ip; - udp_write.gw = gateway(udp_write.ip); - udp_write.src_port = tid; - udp_write.dst_port = server_port; - udp_write.buffer_size = rrq_len; - pxe_call(PXENV_UDP_WRITE, &udp_write); - - /* If the WRITE call fails, we let the timeout take care of it... */ - -wait_pkt: - for (;;) { - buf = packet_buf; - udp_read.status = 0; - udp_read.buffer = FAR_PTR(buf); - udp_read.buffer_size = PKTBUF_SIZE; - udp_read.dest_ip = IPInfo.myip; - udp_read.d_port = tid; - err = pxe_call(PXENV_UDP_READ, &udp_read); - if (err || udp_read.status) { - uint32_t now = jiffies(); - if (now - oldtime >= timeout) - goto sendreq; - } else { - /* Make sure the packet actually came from the server */ - if (udp_read.src_ip == socket->tftp_remoteip) + + inode = allocate_socket(fs); + if (!inode) + return; /* Allocation failure */ + + url_set_ip(&url); + + filename = NULL; + found_scheme = false; + for (us = url_schemes; us->name; us++) { + if (!strcmp(us->name, url.scheme)) { + if ((flags & ~us->ok_flags & OK_FLAGS_MASK) == 0) + us->open(&url, flags, inode, &filename); + found_scheme = true; break; + } } - } - - socket->tftp_remoteport = udp_read.s_port; - - /* filesize <- -1 == unknown */ - inode->size = -1; - /* Default blksize unless blksize option negotiated */ - socket->tftp_blksize = TFTP_BLOCKSIZE; - buffersize = udp_read.buffer_size - 2; /* bytes after opcode */ - if (buffersize < 0) - goto wait_pkt; /* Garbled reply */ - - /* - * Get the opcode type, and parse it - */ - opcode = *(uint16_t *)packet_buf; - switch (opcode) { - case TFTP_ERROR: - inode->size = 0; - break; /* ERROR reply; don't try again */ - - case TFTP_DATA: - /* - * If the server doesn't support any options, we'll get a - * DATA reply instead of OACK. Stash the data in the file - * buffer and go with the default value for all options... - * - * We got a DATA packet, meaning no options are - * suported. Save the data away and consider the - * length undefined, *unless* this is the only - * data packet... - */ - buffersize -= 2; - if (buffersize < 0) - goto wait_pkt; - data = packet_buf + 2; - blk_num = *(uint16_t *)data; - data += 2; - if (blk_num != htons(1)) - goto wait_pkt; - socket->tftp_lastpkt = blk_num; - if (buffersize > TFTP_BLOCKSIZE) - goto err_reply; /* Corrupt */ - else if (buffersize < TFTP_BLOCKSIZE) { - /* - * This is the final EOF packet, already... - * We know the filesize, but we also want to - * ack the packet and set the EOF flag. - */ - inode->size = buffersize; - socket->tftp_goteof = 1; - ack_packet(inode, blk_num); - } - - socket->tftp_bytesleft = buffersize; - socket->tftp_dataptr = socket->tftp_pktbuf; - memcpy(socket->tftp_pktbuf, data, buffersize); - break; - - case TFTP_OACK: - /* - * Now we need to parse the OACK packet to get the transfer - * and packet sizes. - */ - - options = packet_buf + 2; - p = options; - - while (buffersize) { - const char *opt = p; - /* - * If we find an option which starts with a NUL byte, - * (a null option), we're either seeing garbage that some - * TFTP servers add to the end of the packet, or we have - * no clue how to parse the rest of the packet (what is - * an option name and what is a value?) In either case, - * discard the rest. - */ - if (!*opt) - goto done; - - while (buffersize) { - if (!*p) - break; /* Found a final null */ - *p++ |= 0x20; - buffersize--; - } - if (!buffersize) - break; /* Unterminated option */ - - /* Consume the terminal null */ - p++; - buffersize--; - - if (!buffersize) - break; /* No option data */ - - /* - * Parse option pointed to by options; guaranteed to be - * null-terminated - */ - tftp_opt = tftp_options; - for (i = 0; i < tftp_nopts; i++) { - if (!strcmp(opt, tftp_opt->str_ptr)) - break; - tftp_opt++; - } - if (i == tftp_nopts) - goto err_reply; /* Non-negotitated option returned, - no idea what it means ...*/ - - /* get the address of the filed that we want to write on */ - opdata_ptr = (uint32_t *)((char *)inode + tftp_opt->offset); - opdata = 0; - - /* do convert a number-string to decimal number, just like atoi */ - while (buffersize--) { - uint8_t d = *p++; - if (d == '\0') - break; /* found a final null */ - d -= '0'; - if (d > 9) - goto err_reply; /* Not a decimal digit */ - opdata = opdata*10 + d; - } - *opdata_ptr = opdata; - } - break; + /* filename here is set on a redirect */ + } - default: - printf("TFTP unknown opcode %d\n", ntohs(opcode)); - goto err_reply; + if (!found_scheme) { +#if GPXE + /* No URL scheme found, hand it to GPXE */ + gpxe_open(inode, urlsave); +#endif } -done: - if (!inode->size) { + if (inode->size) { + file->inode = inode; + file->inode->mode = (flags & O_DIRECTORY) ? DT_DIR : DT_REG; + } else { free_socket(inode); - return; } - file->inode = inode; - return; -err_reply: - /* Build the TFTP error packet */ - tftp_error(inode, TFTP_EOPTNEG, "TFTP protocol error"); - printf("TFTP server sent an incomprehesible reply\n"); - kaboom(); + return; } static int __pxe_chdir(struct fs_info *fs, const char *src, - enum pxe_path_type path_type) + enum url_type url_type) { - if (path_type == PXE_RELATIVE) + if (url_type == URL_SUFFIX) strlcat(fs->cwd_name, src, sizeof fs->cwd_name); - else if (path_type == PXE_HOMESERVER) + else if (url_type == URL_OLD_TFTP) snprintf(fs->cwd_name, sizeof fs->cwd_name, "::%s", src); else strlcpy(fs->cwd_name, src, sizeof fs->cwd_name); @@ -1031,7 +431,7 @@ static void get_prefix(void) } printf("TFTP prefix: %s\n", path_prefix); - __pxe_chdir(this_fs, path_prefix, PXE_HOMESERVER); + __pxe_chdir(this_fs, path_prefix, URL_OLD_TFTP); } /* @@ -1040,10 +440,8 @@ static void get_prefix(void) static size_t pxe_realpath(struct fs_info *fs, char *dst, const char *src, size_t bufsize) { - enum pxe_path_type path_type = pxe_path_type(src); - return snprintf(dst, bufsize, "%s%s", - path_type == PXE_RELATIVE ? fs->cwd_name : "", src); + url_type(src) == URL_SUFFIX ? fs->cwd_name : "", src); } /* @@ -1052,9 +450,7 @@ static size_t pxe_realpath(struct fs_info *fs, char *dst, const char *src, static int pxe_chdir(struct fs_info *fs, const char *src) { /* The cwd for PXE is just a text prefix */ - enum pxe_path_type path_type = pxe_path_type(src); - - __pxe_chdir(fs, src, path_type); + __pxe_chdir(fs, src, url_type(src)); dprintf("cwd = \"%s\"\n", fs->cwd_name); return 0; @@ -1078,7 +474,7 @@ static int pxe_open_config(struct com32_filedata *filedata) get_prefix(); if (DHCPMagic & 0x02) { /* We got a DHCP option, try it first */ - if (open_file(ConfigName, filedata) >= 0) + if (open_file(ConfigName, O_RDONLY, filedata) >= 0) return 0; } @@ -1088,23 +484,23 @@ static int pxe_open_config(struct com32_filedata *filedata) config_file = stpcpy(ConfigName, cfgprefix); /* Try loading by UUID */ - if (have_uuid) { - strcpy(config_file, UUID_str); - if (open_file(ConfigName, filedata) >= 0) + if (sysappend_strings[SYSAPPEND_SYSUUID]) { + strcpy(config_file, sysappend_strings[SYSAPPEND_SYSUUID]+8); + if (open_file(ConfigName, O_RDONLY, filedata) >= 0) return 0; } /* Try loading by MAC address */ - strcpy(config_file, MAC_str); - if (open_file(ConfigName, filedata) >= 0) + strcpy(config_file, sysappend_strings[SYSAPPEND_BOOTIF]+7); + if (open_file(ConfigName, O_RDONLY, filedata) >= 0) return 0; /* Nope, try hexadecimal IP prefixes... */ - uchexbytes(config_file, (uint8_t *)&IPInfo.myip, 4); + sprintf(config_file, "%08X", ntohl(IPInfo.myip)); last = &config_file[8]; while (tries) { *last = '\0'; /* Zero-terminate string */ - if (open_file(ConfigName, filedata) >= 0) + if (open_file(ConfigName, O_RDONLY, filedata) >= 0) return 0; last--; /* Drop one character */ tries--; @@ -1112,7 +508,7 @@ static int pxe_open_config(struct com32_filedata *filedata) /* Final attempt: "default" string */ strcpy(config_file, default_str); - if (open_file(ConfigName, filedata) >= 0) + if (open_file(ConfigName, O_RDONLY, filedata) >= 0) return 0; printf("%-68s\n", "Unable to locate configuration file"); @@ -1124,43 +520,17 @@ static int pxe_open_config(struct com32_filedata *filedata) */ static void make_bootif_string(void) { + static char bootif_str[7+3*(MAC_MAX+1)]; const uint8_t *src; - char *dst = BOOTIFStr; + char *dst = bootif_str; int i; dst += sprintf(dst, "BOOTIF=%02x", MAC_type); src = MAC; for (i = MAC_len; i; i--) dst += sprintf(dst, "-%02x", *src++); -} -/* - * Generate the SYSUUID string, if we have one... - */ -static void make_sysuuid_string(void) -{ - static const uint8_t uuid_dashes[] = {4, 2, 2, 2, 6, 0}; - const uint8_t *src = uuid; - const uint8_t *uuid_ptr = uuid_dashes; - char *dst; - SYSUUIDStr[0] = '\0'; /* If nothing there... */ - - /* Try loading by UUID */ - if (have_uuid) { - dst = stpcpy(SYSUUIDStr, "SYSUUID="); - - while (*uuid_ptr) { - int len = *uuid_ptr; - - lchexbytes(dst, src, len); - dst += len * 2; - src += len; - uuid_ptr++; - *dst++ = '-'; - } - /* Remove last dash and zero-terminate */ - *--dst = '\0'; - } + sysappend_strings[SYSAPPEND_BOOTIF] = bootif_str; } /* @@ -1168,21 +538,22 @@ static void make_sysuuid_string(void) * option into IPOption based on DHCP information in IPInfo. * */ -char __bss16 IPOption[3+4*16]; - static void genipopt(void) { - char *p = IPOption; + static char ip_option[3+4*16]; const uint32_t *v = &IPInfo.myip; + char *p; int i; - p = stpcpy(p, "ip="); + p = stpcpy(ip_option, "ip="); for (i = 0; i < 4; i++) { p += gendotquad(p, *v++); *p++ = ':'; } *--p = '\0'; + + sysappend_strings[SYSAPPEND_IP] = ip_option; } @@ -1190,6 +561,7 @@ static void genipopt(void) static void ip_init(void) { uint32_t ip = IPInfo.myip; + char dot_quad_buf[16]; genipopt(); gendotquad(dot_quad_buf, ip); @@ -1199,20 +571,6 @@ static void ip_init(void) } /* - * Print the IPAPPEND strings, in order - */ -static void print_ipappend(void) -{ - size_t i; - - for (i = 0; i < (size_t)numIPAppends; i++) { - const char *p = (const char *)(size_t)IPAppends[i]; - if (*p) - printf("%s\n", p); - } -} - -/* * Validity check on possible !PXE structure in buf * return 1 for success, 0 for failure. * @@ -1413,6 +771,8 @@ static int pxe_init(bool quiet) real_base_mem = max(code_seg, data_seg) >> 6; /* Convert to kilobytes */ + probe_undi(); + return 0; } @@ -1437,24 +797,6 @@ static void gpxe_init(void) } /* - * Initialize UDP stack - * - */ -static void udp_init(void) -{ - int err; - static __lowmem struct s_PXENV_UDP_OPEN udp_open; - udp_open.src_ip = IPInfo.myip; - err = pxe_call(PXENV_UDP_OPEN, &udp_open); - if (err || udp_open.status) { - printf("Failed to initialize UDP stack "); - printf("%d\n", udp_open.status); - kaboom(); - } -} - - -/* * Network-specific initialization */ static void network_init(void) @@ -1514,10 +856,12 @@ static void network_init(void) lfree(bp); make_bootif_string(); - make_sysuuid_string(); + /* If DMI and DHCP disagree, which one should we set? */ + if (have_uuid) + sysappend_set_uuid(uuid); ip_init(); - print_ipappend(); + /* print_sysappend(); */ /* * Check to see if we got any PXELINUX-specific DHCP options; in particular, * if we didn't get the magic enable, do not recognize any other options. @@ -1525,7 +869,7 @@ static void network_init(void) if ((DHCPMagic & 1) == 0) DHCPMagic = 0; - udp_init(); + net_core_init(); } /* @@ -1536,9 +880,8 @@ static int pxe_fs_init(struct fs_info *fs) { (void)fs; /* drop the compile warning message */ - /* This block size is actually arbitrary... */ - fs->sector_shift = fs->block_shift = TFTP_BLOCKSIZE_LG2; - fs->sector_size = fs->block_size = 1 << TFTP_BLOCKSIZE_LG2; + /* Prepare for handling pxe interrupts */ + pxe_init_isr(); /* This block size is actually arbitrary... */ fs->sector_shift = fs->block_shift = TFTP_BLOCKSIZE_LG2; @@ -1600,9 +943,9 @@ static inline bool is_efi(const struct efi_struct *efi) return (efi->magic == EFI_MAGIC) && (efi->len >= 83); } +#if 0 static void install_int18_hack(void) { -#if 0 static const uint8_t int18_hack[] = { 0xcd, 0x18, /* int $0x18 */ @@ -1638,30 +981,8 @@ static void install_int18_hack(void) *(uint16_t *)(dst+46) = InitStack.seg; } } -#endif -} - -int reset_pxe(void) -{ - static __lowmem struct s_PXENV_UDP_CLOSE udp_close; - extern void gpxe_unload(void); - int err = 0; - - pxe_idle_cleanup(); - - pxe_call(PXENV_UDP_CLOSE, &udp_close); - - if (gpxe_funcs & 0x80) { - /* gPXE special unload implemented */ - call16(gpxe_unload, &zero_regs, NULL); - - /* Locate the actual vendor stack... */ - err = pxe_init(true); - } - - install_int18_hack(); - return err; } +#endif /* * This function unloads the PXE and UNDI stacks and @@ -1670,6 +991,12 @@ int reset_pxe(void) __export void unload_pxe(uint16_t flags) { /* PXE unload sequences */ + /* + * iPXE does: + * UNDI_SHUTDOWN, UNDI_CLEANUP, STOP_UNDI + * Older Syslinux did: + * UDP_CLOSE, UNDI_SHUTDOWN, UNLOAD_STACK, STOP_UNDI/UNDI_CLEANUP + */ static const uint8_t new_api_unload[] = { PXENV_UNDI_SHUTDOWN, PXENV_UNLOAD_STACK, PXENV_STOP_UNDI, 0 }; @@ -1689,6 +1016,7 @@ __export void unload_pxe(uint16_t flags) uint16_t Status; /* All calls have this as the first member */ } unload_call; + dprintf("Called unload_pxe()...\n"); dprintf("FBM before unload = %d\n", bios_fbm()); err = reset_pxe(); @@ -1707,7 +1035,8 @@ __export void unload_pxe(uint16_t flags) memset(&unload_call, 0, sizeof unload_call); err = pxe_call(api, &unload_call); if (err || unload_call.Status != PXENV_STATUS_SUCCESS) { - dprintf("PXE unload API call %04x failed\n", api); + printf("PXE unload API call %04x failed: 0x%x\n", + api, unload_call.Status); goto cant_free; } } @@ -1739,6 +1068,17 @@ cant_free: return; } +static int pxe_readdir(struct file *file, struct dirent *dirent) +{ + struct inode *inode = file->inode; + struct pxe_pvt_inode *socket = PVT(inode); + + if (socket->ops->readdir) + return socket->ops->readdir(inode, dirent); + else + return -1; /* No such operation */ +} + const struct fs_ops pxe_fs_ops = { .fs_name = "pxe", .fs_flags = FS_NODEV, @@ -1751,4 +1091,5 @@ const struct fs_ops pxe_fs_ops = { .mangle_name = pxe_mangle_name, .chdir_start = pxe_chdir_start, .open_config = pxe_open_config, + .readdir = pxe_readdir, }; diff --git a/core/fs/pxe/pxe.h b/core/fs/pxe/pxe.h index d4377581..2d4be3b1 100644 --- a/core/fs/pxe/pxe.h +++ b/core/fs/pxe/pxe.h @@ -1,7 +1,7 @@ /* ----------------------------------------------------------------------- * * Copyright 1999-2008 H. Peter Anvin - All Rights Reserved - * Copyright 2009-2010 Intel Corporation; author: H. Peter Anvin + * Copyright 2009-2011 Intel Corporation; author: H. Peter Anvin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,67 +21,21 @@ #define PXE_H #include <syslinux/pxe_api.h> -#include "fs.h" /* For MAX_OPEN, should go away */ +#include "fs.h" /* Mostly for FILENAME_MAX */ /* * Some basic defines... */ -#define TFTP_PORT htons(69) /* Default TFTP port */ -#define TFTP_BLOCKSIZE_LG2 9 -#define TFTP_BLOCKSIZE (1 << TFTP_BLOCKSIZE_LG2) -#define PKTBUF_SIZE 2048 /* */ +#define PKTBUF_SIZE 2048 /* Used mostly by the gPXE backend */ #define is_digit(c) (((c) >= '0') && ((c) <= '9')) -static inline bool is_hex(char c) -{ - return (c >= '0' && c <= '9') || - (c >= 'A' && c <= 'F') || - (c >= 'a' && c <= 'f'); -} - -static inline int hexval(char c) -{ - return (c >= 'A') ? (c & ~0x20) - 'A' + 10 : (c - '0'); -} - -/* - * TFTP operation codes - */ -#define TFTP_RRQ htons(1) // Read rest -#define TFTP_WRQ htons(2) // Write rest -#define TFTP_DATA htons(3) // Data packet -#define TFTP_ACK htons(4) // ACK packet -#define TFTP_ERROR htons(5) // ERROR packet -#define TFTP_OACK htons(6) // OACK packet - -/* - * TFTP error codes - */ -#define TFTP_EUNDEF htons(0) // Unspecified error -#define TFTP_ENOTFOUND htons(1) // File not found -#define TFTP_EACCESS htons(2) // Access violation -#define TFTP_ENOSPACE htons(3) // Disk full -#define TFTP_EBADOP htons(4) // Invalid TFTP operation -#define TFTP_EBADID htons(5) // Unknown transfer -#define TFTP_EEXISTS htons(6) // File exists -#define TFTP_ENOUSER htons(7) // No such user -#define TFTP_EOPTNEG htons(8) // Option negotiation failure - - #define BOOTP_OPTION_MAGIC htonl(0x63825363) #define MAC_MAX 32 -/* Defines for DNS */ -#define DNS_PORT htons(53) /* Default DNS port */ -#define DNS_MAX_PACKET 512 /* Defined by protocol */ -#define DNS_MAX_SERVERS 4 /* Max no of DNS servers */ - - /* - * structures + * structures */ - struct pxenv_t { uint8_t signature[6]; /* PXENV+ */ uint16_t version; @@ -149,22 +103,42 @@ struct bootp_t { uint8_t options[1260]; /* Vendor options */ } __attribute__ ((packed)); +struct netconn; +struct netbuf; /* * Our inode private information -- this includes the packet buffer! */ +struct pxe_conn_ops { + void (*fill_buffer)(struct inode *inode); + void (*close)(struct inode *inode); + int (*readdir)(struct inode *inode, struct dirent *dirent); +}; + +struct net_private { +#ifdef IS_LPXELINUX + struct netconn *conn; /* lwip network connection */ + struct netbuf *buf; /* lwip cached buffer */ +#else + uint16_t tftp_localport; /* Local port number (0=not in use) */ + uint32_t tftp_remoteip; /* Remote IP address (0 = disconnected) */ +#endif + +}; + struct pxe_pvt_inode { - uint16_t tftp_localport; /* Local port number (0=not in us)*/ - uint16_t tftp_remoteport; /* Remote port number */ - uint32_t tftp_remoteip; /* Remote IP address */ - uint32_t tftp_filepos; /* bytes downloaded (includeing buffer) */ - uint32_t tftp_blksize; /* Block size for this connection(*) */ - uint16_t tftp_bytesleft; /* Unclaimed data bytes */ - uint16_t tftp_lastpkt; /* Sequence number of last packet (NBO) */ - char *tftp_dataptr; /* Pointer to available data */ - uint8_t tftp_goteof; /* 1 if the EOF packet received */ - uint8_t tftp_unused[3]; /* Currently unused */ - char tftp_pktbuf[PKTBUF_SIZE]; -} __attribute__ ((packed)); + struct net_private private; /* Network stack private data */ + uint16_t tftp_remoteport; /* Remote port number */ + uint32_t tftp_filepos; /* bytes downloaded (including buffer) */ + uint32_t tftp_blksize; /* Block size for this connection(*) */ + uint16_t tftp_bytesleft; /* Unclaimed data bytes */ + uint16_t tftp_lastpkt; /* Sequence number of last packet (HBO) */ + char *tftp_dataptr; /* Pointer to available data */ + uint8_t tftp_goteof; /* 1 if the EOF packet received */ + uint8_t tftp_unused[3]; /* Currently unused */ + char *tftp_pktbuf; /* Packet buffer */ + struct inode *ctl; /* Control connection (for FTP) */ + const struct pxe_conn_ops *ops; +}; #define PVT(i) ((struct pxe_pvt_inode *)((i)->pvt)) @@ -184,6 +158,9 @@ struct ip_info { */ extern struct ip_info IPInfo; +extern t_PXENV_UNDI_GET_INFORMATION pxe_undi_info; +extern t_PXENV_UNDI_GET_IFACE_INFO pxe_undi_iface; + extern uint8_t MAC[]; extern char BOOTIFStr[]; extern uint8_t MAC_len; @@ -196,9 +173,6 @@ extern char boot_file[]; extern char path_prefix[]; extern char LocalDomain[]; -extern char IPOption[]; -extern char dot_quad_buf[]; - extern uint32_t dns_server[]; extern uint16_t APIVer; @@ -211,8 +185,6 @@ extern bool have_uuid; extern uint8_t uuid_type; extern uint8_t uuid[]; -extern const uint8_t TimeoutTable[]; - /* * Compute the suitable gateway for a specific route -- too many * vendor PXE stacks don't do this correctly... @@ -226,24 +198,62 @@ static inline uint32_t gateway(uint32_t ip) } /* - * functions + * functions */ +/* pxeisr.inc */ +extern uint8_t pxe_irq_vector; +extern void pxe_isr(void); +extern far_ptr_t pxe_irq_chain; +extern void pxe_poll(void); + +/* isr.c */ +void pxe_init_isr(void); +void pxe_start_isr(void); +int reset_pxe(void); + /* pxe.c */ +struct url_info; bool ip_ok(uint32_t); +int pxe_getc(struct inode *inode); +void free_socket(struct inode *inode); + +/* undiif.c */ +int undiif_start(uint32_t ip, uint32_t netmask, uint32_t gw); +void undiif_input(t_PXENV_UNDI_ISR *isr); /* dhcp_options.c */ void parse_dhcp(const void *, size_t); -/* dnsresolv.c */ -int dns_mangle(char **, const char *); - /* idle.c */ void pxe_idle_init(void); void pxe_idle_cleanup(void); -/* socknum.c */ -uint16_t get_port(void); -void free_port(uint16_t port); +/* tftp.c */ +void tftp_open(struct url_info *url, int flags, struct inode *inode, + const char **redir); + +/* gpxeurl.c */ +void gpxe_open(struct inode *inode, const char *url); +#define GPXE 0 + +/* http.c */ +void http_open(struct url_info *url, int flags, struct inode *inode, + const char **redir); + +/* http_readdir.c */ +int http_readdir(struct inode *inode, struct dirent *dirent); + +/* ftp.c */ +void ftp_open(struct url_info *url, int flags, struct inode *inode, + const char **redir); + +/* ftp_readdir.c */ +int ftp_readdir(struct inode *inode, struct dirent *dirent); + +/* tcp.c */ +void tcp_close_file(struct inode *inode); +void tcp_fill_buffer(struct inode *inode); +const struct pxe_conn_ops tcp_conn_ops; #endif /* pxe.h */ diff --git a/core/fs/pxe/tcp.c b/core/fs/pxe/tcp.c new file mode 100644 index 00000000..528fbce8 --- /dev/null +++ b/core/fs/pxe/tcp.c @@ -0,0 +1,78 @@ +/* ----------------------------------------------------------------------- * + * + * Copyright 2011 Intel Corporation; author: H. Peter Anvin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston MA 02110-1301, USA; either version 2 of the License, or + * (at your option) any later version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +/* + * tcp.c + * + * Common operations for TCP-based network protocols + */ + +#include <lwip/api.h> +#include "pxe.h" +#include "../../../version.h" +#include "url.h" + +void tcp_close_file(struct inode *inode) +{ + struct pxe_pvt_inode *socket = PVT(inode); + + if (socket->private.conn) { + netconn_delete(socket->private.conn); + socket->private.conn = NULL; + } + if (socket->private.buf) { + netbuf_delete(socket->private.buf); + socket->private.buf = NULL; + } +} + +void tcp_fill_buffer(struct inode *inode) +{ + struct pxe_pvt_inode *socket = PVT(inode); + void *data; + u16_t len; + err_t err; + + /* Clean up or advance an inuse netbuf */ + if (socket->private.buf) { + if (netbuf_next(socket->private.buf) < 0) { + netbuf_delete(socket->private.buf); + socket->private.buf = NULL; + } + } + /* If needed get a new netbuf */ + if (!socket->private.buf) { + err = netconn_recv(socket->private.conn, &(socket->private.buf)); + if (!socket->private.buf || err) { + socket->tftp_goteof = 1; + if (inode->size == -1) + inode->size = socket->tftp_filepos; + socket->ops->close(inode); + return; + } + } + /* Report the current fragment of the netbuf */ + err = netbuf_data(socket->private.buf, &data, &len); + if (err) { + printf("netbuf_data err: %d\n", err); + kaboom(); + } + socket->tftp_dataptr = data; + socket->tftp_filepos += len; + socket->tftp_bytesleft = len; + return; +} + +const struct pxe_conn_ops tcp_conn_ops = { + .fill_buffer = tcp_fill_buffer, + .close = tcp_close_file, +}; diff --git a/core/fs/pxe/tftp.c b/core/fs/pxe/tftp.c new file mode 100644 index 00000000..9b755c93 --- /dev/null +++ b/core/fs/pxe/tftp.c @@ -0,0 +1,443 @@ +#include <minmax.h> +#include <net.h> +#include "pxe.h" +#include "url.h" +#include "tftp.h" + +const uint8_t TimeoutTable[] = { + 2, 2, 3, 3, 4, 5, 6, 7, 9, 10, 12, 15, 18, 21, 26, 31, 37, 44, + 53, 64, 77, 92, 110, 132, 159, 191, 229, 255, 255, 255, 255, 0 +}; +struct tftp_options { + const char *str_ptr; /* string pointer */ + size_t offset; /* offset into socket structre */ +}; +struct tftp_packet { + uint16_t opcode; + uint16_t serial; + char data[]; +}; + +#define IFIELD(x) offsetof(struct inode, x) +#define PFIELD(x) (offsetof(struct inode, pvt) + \ + offsetof(struct pxe_pvt_inode, x)) + +static const struct tftp_options tftp_options[] = +{ + { "tsize", IFIELD(size) }, + { "blksize", PFIELD(tftp_blksize) }, +}; +static const int tftp_nopts = sizeof tftp_options / sizeof tftp_options[0]; + +static void tftp_error(struct inode *file, uint16_t errnum, + const char *errstr); + +static void tftp_close_file(struct inode *inode) +{ + struct pxe_pvt_inode *socket = PVT(inode); + if (!socket->tftp_goteof) { + tftp_error(inode, 0, "No error, file close"); + } + net_core_close(socket); +} + +/** + * Send an ERROR packet. This is used to terminate a connection. + * + * @inode: Inode structure + * @errnum: Error number (network byte order) + * @errstr: Error string (included in packet) + */ +static void tftp_error(struct inode *inode, uint16_t errnum, + const char *errstr) +{ + static struct { + uint16_t err_op; + uint16_t err_num; + char err_msg[64]; + } __packed err_buf; + int len = min(strlen(errstr), sizeof(err_buf.err_msg)-1); + struct pxe_pvt_inode *socket = PVT(inode); + + err_buf.err_op = TFTP_ERROR; + err_buf.err_num = errnum; + memcpy(err_buf.err_msg, errstr, len); + err_buf.err_msg[len] = '\0'; + + net_core_send(socket, &err_buf, 4 + len + 1); +} + +/** + * Send ACK packet. This is a common operation and so is worth canning. + * + * @param: inode, Inode pointer + * @param: ack_num, Packet # to ack (host byte order) + * + */ +static void ack_packet(struct inode *inode, uint16_t ack_num) +{ + static uint16_t ack_packet_buf[2]; + struct pxe_pvt_inode *socket = PVT(inode); + + /* Packet number to ack */ + ack_packet_buf[0] = TFTP_ACK; + ack_packet_buf[1] = htons(ack_num); + + net_core_send(socket, ack_packet_buf, 4); +} + +/* + * Get a fresh packet if the buffer is drained, and we haven't hit + * EOF yet. The buffer should be filled immediately after draining! + */ +static void tftp_get_packet(struct inode *inode) +{ + uint16_t last_pkt; + const uint8_t *timeout_ptr; + uint8_t timeout; + uint16_t buffersize; + uint16_t serial; + jiffies_t oldtime; + struct tftp_packet *pkt = NULL; + uint16_t buf_len; + struct pxe_pvt_inode *socket = PVT(inode); + uint16_t src_port; + uint32_t src_ip; + int err; + + /* + * Start by ACKing the previous packet; this should cause + * the next packet to be sent. + */ + timeout_ptr = TimeoutTable; + timeout = *timeout_ptr++; + oldtime = jiffies(); + + ack_again: + ack_packet(inode, socket->tftp_lastpkt); + + while (timeout) { + buf_len = socket->tftp_blksize + 4; + err = net_core_recv(socket, socket->tftp_pktbuf, &buf_len, + &src_ip, &src_port); + if (err) { + jiffies_t now = jiffies(); + + if (now-oldtime >= timeout) { + oldtime = now; + timeout = *timeout_ptr++; + if (!timeout) + break; + goto ack_again; + } + continue; + } + + if (buf_len < 4) /* Bad size for a DATA packet */ + continue; + + pkt = (struct tftp_packet *)(socket->tftp_pktbuf); + if (pkt->opcode != TFTP_DATA) /* Not a data packet */ + continue; + + /* If goes here, recevie OK, break */ + break; + } + + /* time runs out */ + if (timeout == 0) + kaboom(); + + last_pkt = socket->tftp_lastpkt; + last_pkt++; + serial = ntohs(pkt->serial); + if (serial != last_pkt) { + /* + * Wrong packet, ACK the packet and try again. + * This is presumably because the ACK got lost, + * so the server just resent the previous packet. + */ +#if 0 + printf("Wrong packet, wanted %04x, got %04x\n", \ + htons(last_pkt), htons(*(uint16_t *)(data+2))); +#endif + goto ack_again; + } + + /* It's the packet we want. We're also EOF if the size < blocksize */ + socket->tftp_lastpkt = last_pkt; /* Update last packet number */ + buffersize = buf_len - 4; /* Skip TFTP header */ + socket->tftp_dataptr = socket->tftp_pktbuf + 4; + socket->tftp_filepos += buffersize; + socket->tftp_bytesleft = buffersize; + if (buffersize < socket->tftp_blksize) { + /* it's the last block, ACK packet immediately */ + ack_packet(inode, serial); + + /* Make sure we know we are at end of file */ + inode->size = socket->tftp_filepos; + socket->tftp_goteof = 1; + tftp_close_file(inode); + } +} + +const struct pxe_conn_ops tftp_conn_ops = { + .fill_buffer = tftp_get_packet, + .close = tftp_close_file, +}; + +/** + * Open a TFTP connection to the server + * + * @param:inode, the inode to store our state in + * @param:ip, the ip to contact to get the file + * @param:filename, the file we wanna open + * + * @out: open_file_t structure, stores in file->open_file + * @out: the lenght of this file, stores in file->file_len + * + */ +void tftp_open(struct url_info *url, int flags, struct inode *inode, + const char **redir) +{ + struct pxe_pvt_inode *socket = PVT(inode); + char *buf; + uint16_t buf_len; + char *p; + char *options; + char *data; + static const char rrq_tail[] = "octet\0""tsize\0""0\0""blksize\0""1408"; + char rrq_packet_buf[2+2*FILENAME_MAX+sizeof rrq_tail]; + char reply_packet_buf[PKTBUF_SIZE]; + const struct tftp_options *tftp_opt; + int i = 0; + int err; + int buffersize; + int rrq_len; + const uint8_t *timeout_ptr; + jiffies_t timeout; + jiffies_t oldtime; + uint16_t opcode; + uint16_t blk_num; + uint32_t opdata, *opdata_ptr; + uint16_t src_port; + uint32_t src_ip; + + (void)redir; /* TFTP does not redirect */ + (void)flags; + + if (url->type != URL_OLD_TFTP) { + /* + * The TFTP URL specification allows the TFTP to end with a + * ;mode= which we just ignore. + */ + url_unescape(url->path, ';'); + } + + if (!url->port) + url->port = TFTP_PORT; + + socket->ops = &tftp_conn_ops; + if (net_core_open(socket, NET_CORE_UDP)) + return; + + buf = rrq_packet_buf; + *(uint16_t *)buf = TFTP_RRQ; /* TFTP opcode */ + buf += 2; + + buf = stpcpy(buf, url->path); + + buf++; /* Point *past* the final NULL */ + memcpy(buf, rrq_tail, sizeof rrq_tail); + buf += sizeof rrq_tail; + + rrq_len = buf - rrq_packet_buf; + + timeout_ptr = TimeoutTable; /* Reset timeout */ +sendreq: + net_core_disconnect(socket); + timeout = *timeout_ptr++; + if (!timeout) + return; /* No file available... */ + oldtime = jiffies(); + + net_core_connect(socket, url->ip, url->port); + net_core_send(socket, rrq_packet_buf, rrq_len); + + /* If the WRITE call fails, we let the timeout take care of it... */ +wait_pkt: + net_core_disconnect(socket); + for (;;) { + buf_len = sizeof(reply_packet_buf); + + err = net_core_recv(socket, reply_packet_buf, &buf_len, + &src_ip, &src_port); + if (err) { + jiffies_t now = jiffies(); + if (now - oldtime >= timeout) + goto sendreq; + } else { + /* Make sure the packet actually came from the server */ + if (src_ip == url->ip) + break; + } + } + + net_core_disconnect(socket); + net_core_connect(socket, src_ip, src_port); + + /* filesize <- -1 == unknown */ + inode->size = -1; + socket->tftp_blksize = TFTP_BLOCKSIZE; + buffersize = buf_len - 2; /* bytes after opcode */ + if (buffersize < 0) + goto wait_pkt; /* Garbled reply */ + + /* + * Get the opcode type, and parse it + */ + opcode = *(uint16_t *)reply_packet_buf; + switch (opcode) { + case TFTP_ERROR: + inode->size = 0; + goto done; /* ERROR reply; don't try again */ + + case TFTP_DATA: + /* + * If the server doesn't support any options, we'll get a + * DATA reply instead of OACK. Stash the data in the file + * buffer and go with the default value for all options... + * + * We got a DATA packet, meaning no options are + * suported. Save the data away and consider the + * length undefined, *unless* this is the only + * data packet... + */ + buffersize -= 2; + if (buffersize < 0) + goto wait_pkt; + data = reply_packet_buf + 2; + blk_num = ntohs(*(uint16_t *)data); + data += 2; + if (blk_num != 1) + goto wait_pkt; + socket->tftp_lastpkt = blk_num; + if (buffersize > TFTP_BLOCKSIZE) + goto err_reply; /* Corrupt */ + + socket->tftp_pktbuf = malloc(TFTP_BLOCKSIZE + 4); + if (!socket->tftp_pktbuf) + goto err_reply; /* Internal error */ + + if (buffersize < TFTP_BLOCKSIZE) { + /* + * This is the final EOF packet, already... + * We know the filesize, but we also want to + * ack the packet and set the EOF flag. + */ + inode->size = buffersize; + socket->tftp_goteof = 1; + ack_packet(inode, blk_num); + } + + socket->tftp_bytesleft = buffersize; + socket->tftp_dataptr = socket->tftp_pktbuf; + memcpy(socket->tftp_pktbuf, data, buffersize); + goto done; + + case TFTP_OACK: + /* + * Now we need to parse the OACK packet to get the transfer + * and packet sizes. + */ + + options = reply_packet_buf + 2; + p = options; + + while (buffersize) { + const char *opt = p; + + /* + * If we find an option which starts with a NUL byte, + * (a null option), we're either seeing garbage that some + * TFTP servers add to the end of the packet, or we have + * no clue how to parse the rest of the packet (what is + * an option name and what is a value?) In either case, + * discard the rest. + */ + if (!*opt) + goto done; + + while (buffersize) { + if (!*p) + break; /* Found a final null */ + *p++ |= 0x20; + buffersize--; + } + if (!buffersize) + break; /* Unterminated option */ + + /* Consume the terminal null */ + p++; + buffersize--; + + if (!buffersize) + break; /* No option data */ + + /* + * Parse option pointed to by options; guaranteed to be + * null-terminated + */ + tftp_opt = tftp_options; + for (i = 0; i < tftp_nopts; i++) { + if (!strcmp(opt, tftp_opt->str_ptr)) + break; + tftp_opt++; + } + if (i == tftp_nopts) + goto err_reply; /* Non-negotitated option returned, + no idea what it means ...*/ + + /* get the address of the filed that we want to write on */ + opdata_ptr = (uint32_t *)((char *)inode + tftp_opt->offset); + opdata = 0; + + /* do convert a number-string to decimal number, just like atoi */ + while (buffersize--) { + uint8_t d = *p++; + if (d == '\0') + break; /* found a final null */ + d -= '0'; + if (d > 9) + goto err_reply; /* Not a decimal digit */ + opdata = opdata*10 + d; + } + *opdata_ptr = opdata; + } + + if (socket->tftp_blksize < 64 || socket->tftp_blksize > PKTBUF_SIZE) + goto err_reply; + + /* Parsing successful, allocate buffer */ + socket->tftp_pktbuf = malloc(socket->tftp_blksize + 4); + if (!socket->tftp_pktbuf) + goto err_reply; + else + goto done; + + default: + printf("TFTP unknown opcode %d\n", ntohs(opcode)); + goto err_reply; + } + +err_reply: + /* Build the TFTP error packet */ + tftp_error(inode, TFTP_EOPTNEG, "TFTP protocol error"); + inode->size = 0; + +done: + if (!inode->size) + net_core_close(socket); + + return; +} diff --git a/core/fs/pxe/tftp.h b/core/fs/pxe/tftp.h new file mode 100644 index 00000000..114c221f --- /dev/null +++ b/core/fs/pxe/tftp.h @@ -0,0 +1,54 @@ +/* ----------------------------------------------------------------------- + * + * Copyright 1999-2008 H. Peter Anvin - All Rights Reserved + * Copyright 2009-2011 Intel Corporation; author: H. Peter Anvin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, Inc., 53 Temple Place Ste 330, + * Boston MA 02111-1307, USA; either version 2 of the License, or + * (at your option) any later version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +/* + * tftp.h + */ +#ifndef PXE_TFTP_H +#define PXE_TFTP_H + +/* + * TFTP default port number + */ +#define TFTP_PORT 69 + +/* + * TFTP default block size + */ +#define TFTP_BLOCKSIZE_LG2 9 +#define TFTP_BLOCKSIZE (1 << TFTP_BLOCKSIZE_LG2) + +/* + * TFTP operation codes + */ +#define TFTP_RRQ htons(1) // Read rest +#define TFTP_WRQ htons(2) // Write rest +#define TFTP_DATA htons(3) // Data packet +#define TFTP_ACK htons(4) // ACK packet +#define TFTP_ERROR htons(5) // ERROR packet +#define TFTP_OACK htons(6) // OACK packet + +/* + * TFTP error codes + */ +#define TFTP_EUNDEF htons(0) // Unspecified error +#define TFTP_ENOTFOUND htons(1) // File not found +#define TFTP_EACCESS htons(2) // Access violation +#define TFTP_ENOSPACE htons(3) // Disk full +#define TFTP_EBADOP htons(4) // Invalid TFTP operation +#define TFTP_EBADID htons(5) // Unknown transfer +#define TFTP_EEXISTS htons(6) // File exists +#define TFTP_ENOUSER htons(7) // No such user +#define TFTP_EOPTNEG htons(8) // Option negotiation failure + +#endif /* PXE_TFTP_H */ diff --git a/core/fs/pxe/url.h b/core/fs/pxe/url.h new file mode 100644 index 00000000..53984f3a --- /dev/null +++ b/core/fs/pxe/url.h @@ -0,0 +1,33 @@ +/* + * url.h + */ + +#ifndef CORE_PXE_URL_H +#define CORE_PXE_URL_H + +#include <stddef.h> +#include <stdint.h> + +enum url_type { + URL_NORMAL, /* It is a full URL */ + URL_OLD_TFTP, /* It's a ::-style TFTP path */ + URL_SUFFIX /* Prepend the pathname prefix */ +}; + +struct url_info { + char *scheme; + char *user; + char *passwd; + char *host; + uint32_t ip; /* Placeholder field not set by parse_url() */ + unsigned int port; + char *path; /* Includes query */ + enum url_type type; +}; + +enum url_type url_type(const char *url); +void parse_url(struct url_info *ui, char *url); +size_t url_escape_unsafe(char *output, const char *input, size_t bufsize); +char *url_unescape(char *buffer, char terminator); + +#endif /* CORE_PXE_URL_H */ diff --git a/core/fs/pxe/urlparse.c b/core/fs/pxe/urlparse.c new file mode 100644 index 00000000..6b73ddb6 --- /dev/null +++ b/core/fs/pxe/urlparse.c @@ -0,0 +1,223 @@ +/* ----------------------------------------------------------------------- * + * + * Copyright 2009-2011 Intel Corporation; author: H. Peter Anvin + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom + * the Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall + * be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * ----------------------------------------------------------------------- */ + +/* + * urlparse.c + */ + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include "url.h" + +/* + * Return the type of a URL without modifying the string + */ +enum url_type url_type(const char *url) +{ + const char *q; + + q = strchr(url, ':'); + if (!q) + return URL_SUFFIX; + + if (q[1] == '/' && q[2] == '/') + return URL_NORMAL; + + if (q[1] == ':') + return URL_OLD_TFTP; + + return URL_SUFFIX; +} + +/* + * Decompose a URL into its components. This is done in-place; + * this routine does not allocate any additional storage. Freeing the + * original buffer frees all storage used. + */ +void parse_url(struct url_info *ui, char *url) +{ + char *p = url; + char *q, *r, *s; + int c; + + memset(ui, 0, sizeof *ui); + + q = strchr(p, ':'); + if (q && (q[1] == '/' && q[2] == '/')) { + ui->type = URL_NORMAL; + + ui->scheme = p; + *q = '\0'; + p = q+3; + + q = strchr(p, '/'); + if (q) { + *q = '\0'; + ui->path = q+1; + q = strchr(q+1, '#'); + if (q) + *q = '\0'; + } else { + ui->path = ""; + } + + r = strchr(p, '@'); + if (r) { + ui->user = p; + *r = '\0'; + s = strchr(p, ':'); + if (s) { + *s = '\0'; + ui->passwd = s+1; + } + p = r+1; + } + + ui->host = p; + r = strchr(p, ':'); + if (r) { + *r++ = '\0'; + ui->port = 0; + while ((c = *r++)) { + c -= '0'; + if (c > 9) + break; + ui->port = ui->port * 10 + c; + } + } + } else if (q && q[1] == ':') { + *q = '\0'; + ui->scheme = "tftp"; + ui->host = p; + ui->path = q+2; + ui->type = URL_OLD_TFTP; + } else { + ui->path = p; + ui->type = URL_SUFFIX; + } +} + +/* + * Escapes unsafe characters in a URL. + * This does *not* escape things like query characters! + * Returns the number of characters in the total output. + */ +size_t url_escape_unsafe(char *output, const char *input, size_t bufsize) +{ + static const char uchexchar[] = "0123456789ABCDEF"; + const char *p; + unsigned char c; + char *q; + size_t n = 0; + + q = output; + for (p = input; (c = *p); p++) { + if (c <= ' ' || c > '~') { + if (++n < bufsize) *q++ = '%'; + if (++n < bufsize) *q++ = uchexchar[c >> 4]; + if (++n < bufsize) *q++ = uchexchar[c & 15]; + } else { + if (++n < bufsize) *q++ = c; + } + } + + *q = '\0'; + return n; +} + +static int hexdigit(char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + c |= 0x20; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + return -1; +} + +/* + * Unescapes a buffer, optionally ending at an *unescaped* terminator + * (like ; for TFTP). The unescaping is done in-place. + * + * If a terminator is reached, return a pointer to the first character + * after the terminator. + */ +char *url_unescape(char *buffer, char terminator) +{ + char *p = buffer; + char *q = buffer; + unsigned char c; + int x, y; + + while ((c = *p)) { + if (c == terminator) { + *q = '\0'; + return p; + } + p++; + if (c == '%') { + x = hexdigit(p[0]); + if (x >= 0) { + y = hexdigit(p[1]); + if (y >= 0) { + *q++ = (x << 4) + y; + p += 2; + continue; + } + } + } + *q++ = c; + } + *q = '\0'; + return NULL; +} + +#ifdef URL_TEST + +int main(int argc, char *argv[]) +{ + int i; + struct url_info url; + + for (i = 1; i < argc; i++) { + parse_url(&url, argv[i]); + printf("scheme: %s\n" + "user: %s\n" + "passwd: %s\n" + "host: %s\n" + "port: %d\n" + "path: %s\n" + "type: %d\n", + url.scheme, url.user, url.passwd, url.host, url.port, + url.path, url.type); + } + + return 0; +} + +#endif diff --git a/core/fs/readdir.c b/core/fs/readdir.c index e2d593fc..546a704a 100644 --- a/core/fs/readdir.c +++ b/core/fs/readdir.c @@ -1,3 +1,4 @@ +#include <fcntl.h> #include <stdio.h> #include <string.h> #include <sys/dirent.h> @@ -12,7 +13,7 @@ __export DIR *opendir(const char *path) int rv; struct file *file; - rv = searchdir(path); + rv = searchdir(path, O_RDONLY|O_DIRECTORY); if (rv < 0) return NULL; |