| #include <dprintf.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <core.h> |
| #include <fs.h> |
| #include <fcntl.h> |
| #include <sys/cpu.h> |
| #include "pxe.h" |
| #include "thread.h" |
| #include "url.h" |
| #include "tftp.h" |
| #include <net.h> |
| |
| __lowmem t_PXENV_UNDI_GET_INFORMATION pxe_undi_info; |
| __lowmem t_PXENV_UNDI_GET_IFACE_INFO pxe_undi_iface; |
| |
| uint8_t MAC[MAC_MAX]; /* Actual MAC address */ |
| uint8_t MAC_len; /* MAC address len */ |
| uint8_t MAC_type; /* MAC address type */ |
| |
| char boot_file[256]; /* From DHCP */ |
| char path_prefix[256]; /* From DHCP */ |
| |
| bool have_uuid = false; |
| |
| /* |
| * Allocate a local UDP port structure and assign it a local port number. |
| * Return the inode pointer if success, or null if failure |
| */ |
| static struct inode *allocate_socket(struct fs_info *fs) |
| { |
| struct inode *inode = alloc_inode(fs, 0, sizeof(struct pxe_pvt_inode)); |
| |
| if (!inode) { |
| malloc_error("socket structure"); |
| } else { |
| inode->mode = DT_REG; /* No other types relevant for PXE */ |
| } |
| |
| return inode; |
| } |
| |
| void free_socket(struct inode *inode) |
| { |
| struct pxe_pvt_inode *socket = PVT(inode); |
| |
| free(socket->tftp_pktbuf); /* If we allocated a buffer, free it now */ |
| free_inode(inode); |
| } |
| |
| 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) { |
| socket->ops->close(inode); |
| } |
| |
| free_socket(inode); |
| } |
| |
| /* |
| * 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 |
| * assignable unicast addresses in the near future. |
| * |
| */ |
| bool ip_ok(uint32_t ip) |
| { |
| uint8_t ip_hi = (uint8_t)ip; /* First octet of the ip address */ |
| |
| if (ip == 0xffffffff || /* Refuse the all-ones address */ |
| ip_hi == 0 || /* Refuse network zero */ |
| ip_hi == 127 || /* Refuse the loopback network */ |
| (ip_hi & 240) == 224) /* Refuse class D */ |
| return false; |
| |
| return true; |
| } |
| |
| |
| /* |
| * Take an IP address (in network byte order) in _ip_ and |
| * output a dotted quad string to _dst_, returns the length |
| * of the dotted quad ip string. |
| * |
| */ |
| static int gendotquad(char *dst, uint32_t ip) |
| { |
| 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]); |
| } |
| |
| /* |
| * the ASM pxenv function wrapper, return 1 if error, or 0 |
| * |
| */ |
| __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 |
| dprintf("pxe_call op %04x data %p\n", opcode, data); |
| #endif |
| |
| memset(®s, 0, sizeof regs); |
| regs.ebx.w[0] = opcode; |
| regs.es = SEG(data); |
| regs.edi.w[0] = OFFS(data); |
| call16(pxenv, ®s, ®s); |
| |
| sem_up(&pxe_sem); |
| |
| return regs.eflags.l & EFLAGS_CF; /* CF SET if fail */ |
| } |
| |
| /* |
| * 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) |
| { |
| size_t len = FILENAME_MAX-1; |
| |
| while (len-- && not_whitespace(*src)) |
| *dst++ = *src++; |
| |
| *dst = '\0'; |
| } |
| |
| /* |
| * Read a single character from the specified pxe inode. |
| * Very useful for stepping through http streams and |
| * parsing their headers. |
| */ |
| int pxe_getc(struct inode *inode) |
| { |
| struct pxe_pvt_inode *socket = PVT(inode); |
| unsigned char byte; |
| |
| while (!socket->tftp_bytesleft) { |
| if (socket->tftp_goteof) |
| return -1; |
| |
| socket->ops->fill_buffer(inode); |
| } |
| |
| byte = *socket->tftp_dataptr; |
| socket->tftp_bytesleft -= 1; |
| socket->tftp_dataptr += 1; |
| |
| return byte; |
| } |
| |
| /* |
| * 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; |
| |
| return socket->ops->fill_buffer(inode); |
| } |
| |
| |
| /** |
| * getfssec: Get multiple clusters from a file, given the starting cluster. |
| * In this case, get multiple blocks from a specific TCP connection. |
| * |
| * @param: fs, the fs_info structure address, in pxe, we don't use this. |
| * @param: buf, buffer to store the read data |
| * @param: openfile, TFTP socket pointer |
| * @param: blocks, 512-byte block count; 0FFFFh = until end of file |
| * |
| * @return: the bytes read |
| * |
| */ |
| static uint32_t pxe_getfssec(struct file *file, char *buf, |
| int blocks, bool *have_more) |
| { |
| struct inode *inode = file->inode; |
| struct pxe_pvt_inode *socket = PVT(inode); |
| int count = blocks; |
| int chunk; |
| int bytes_read = 0; |
| |
| count <<= TFTP_BLOCKSIZE_LG2; |
| while (count) { |
| fill_buffer(inode); /* If we have no 'fresh' buffer, get it */ |
| if (!socket->tftp_bytesleft) |
| break; |
| |
| chunk = count; |
| if (chunk > socket->tftp_bytesleft) |
| chunk = socket->tftp_bytesleft; |
| socket->tftp_bytesleft -= chunk; |
| memcpy(buf, socket->tftp_dataptr, chunk); |
| socket->tftp_dataptr += chunk; |
| buf += chunk; |
| bytes_read += chunk; |
| count -= chunk; |
| } |
| |
| |
| if (socket->tftp_bytesleft || (socket->tftp_filepos < inode->size)) { |
| fill_buffer(inode); |
| *have_more = 1; |
| } else if (socket->tftp_goteof) { |
| /* |
| * The socket is closed and the buffer drained; the caller will |
| * call close_file and therefore free the socket. |
| */ |
| *have_more = 0; |
| } |
| |
| 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 the specified connection |
| * |
| * @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 |
| * |
| */ |
| static void __pxe_searchdir(const char *filename, int flags, struct file *file); |
| extern uint16_t PXERetry; |
| |
| 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, flags, file); |
| dprintf("%s\n", file->inode ? "ok" : "failed"); |
| } while (!file->inode && i--); |
| } |
| static void __pxe_searchdir(const char *filename, int flags, struct file *file) |
| { |
| struct fs_info *fs = file->fs; |
| struct inode *inode; |
| char fullpath[2*FILENAME_MAX]; |
| #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; |
| |
| while (filename) { |
| if (redirect_count++ > 5) |
| break; |
| |
| strlcpy(fullpath, filename, sizeof fullpath); |
| #if GPXE |
| 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); |
| } |
| |
| 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; |
| } |
| } |
| |
| /* filename here is set on a redirect */ |
| } |
| |
| if (!found_scheme) { |
| #if GPXE |
| /* No URL scheme found, hand it to GPXE */ |
| gpxe_open(inode, urlsave); |
| #endif |
| } |
| |
| if (inode->size) { |
| file->inode = inode; |
| file->inode->mode = (flags & O_DIRECTORY) ? DT_DIR : DT_REG; |
| } else { |
| free_socket(inode); |
| } |
| |
| return; |
| } |
| |
| |
| /* |
| * Store standard filename prefix |
| */ |
| static void get_prefix(void) |
| { |
| int len; |
| char *p; |
| char c; |
| |
| if (!(DHCPMagic & 0x04)) { |
| /* No path prefix option, derive from boot file */ |
| |
| strlcpy(path_prefix, boot_file, sizeof path_prefix); |
| len = strlen(path_prefix); |
| p = &path_prefix[len - 1]; |
| |
| while (len--) { |
| c = *p--; |
| c |= 0x20; |
| |
| c = (c >= '0' && c <= '9') || |
| (c >= 'a' && c <= 'z') || |
| (c == '.' || c == '-'); |
| if (!c) |
| break; |
| }; |
| |
| if (len < 0) |
| p --; |
| |
| *(p + 2) = 0; /* Zero-terminate after delimiter */ |
| } |
| |
| ddprintf("TFTP prefix: %s\n", path_prefix); |
| |
| if (url_type(path_prefix) == URL_SUFFIX) { |
| /* |
| * Construct a ::-style TFTP path. |
| * |
| * We may have moved out of the root directory at the time |
| * this function is invoked, but to maintain compatibility |
| * with versions of Syslinux < 5.00, path_prefix must be |
| * relative to "::". |
| */ |
| p = strdup(path_prefix); |
| if (!p) |
| return; |
| |
| snprintf(path_prefix, sizeof path_prefix, "::%s", p); |
| free(p); |
| } |
| |
| chdir(path_prefix); |
| } |
| |
| /* |
| * realpath for PXE |
| */ |
| static size_t pxe_realpath(struct fs_info *fs, char *dst, const char *src, |
| size_t bufsize) |
| { |
| return snprintf(dst, bufsize, "%s%s", |
| url_type(src) == URL_SUFFIX ? fs->cwd_name : "", src); |
| } |
| |
| /* |
| * chdir for PXE |
| */ |
| static int pxe_chdir(struct fs_info *fs, const char *src) |
| { |
| /* The cwd for PXE is just a text prefix */ |
| enum url_type path_type = url_type(src); |
| |
| if (path_type == URL_SUFFIX) |
| strlcat(fs->cwd_name, src, sizeof fs->cwd_name); |
| else |
| strlcpy(fs->cwd_name, src, sizeof fs->cwd_name); |
| return 0; |
| |
| dprintf("cwd = \"%s\"\n", fs->cwd_name); |
| return 0; |
| } |
| |
| static int pxe_chdir_start(void) |
| { |
| get_prefix(); |
| return 0; |
| } |
| |
| /* Load the config file, return -1 if failed, or 0 */ |
| static int pxe_open_config(struct com32_filedata *filedata) |
| { |
| const char *cfgprefix = "pxelinux.cfg/"; |
| const char *default_str = "default"; |
| char *config_file; |
| char *last; |
| int tries = 8; |
| |
| chdir(path_prefix); |
| if (DHCPMagic & 0x02) { |
| /* We got a DHCP option, try it first */ |
| if (open_file(ConfigName, O_RDONLY, filedata) >= 0) |
| return 0; |
| } |
| |
| /* |
| * Have to guess config file name ... |
| */ |
| config_file = stpcpy(ConfigName, cfgprefix); |
| |
| /* Try loading by UUID */ |
| 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, sysappend_strings[SYSAPPEND_BOOTIF]+7); |
| if (open_file(ConfigName, O_RDONLY, filedata) >= 0) |
| return 0; |
| |
| /* Nope, try hexadecimal IP prefixes... */ |
| sprintf(config_file, "%08X", ntohl(IPInfo.myip)); |
| last = &config_file[8]; |
| while (tries) { |
| *last = '\0'; /* Zero-terminate string */ |
| if (open_file(ConfigName, O_RDONLY, filedata) >= 0) |
| return 0; |
| last--; /* Drop one character */ |
| tries--; |
| }; |
| |
| /* Final attempt: "default" string */ |
| strcpy(config_file, default_str); |
| if (open_file(ConfigName, O_RDONLY, filedata) >= 0) |
| return 0; |
| |
| ddprintf("%-68s\n", "Unable to locate configuration file"); |
| kaboom(); |
| } |
| |
| /* |
| * Generate the bootif string. |
| */ |
| static void make_bootif_string(void) |
| { |
| static char bootif_str[7+3*(MAC_MAX+1)]; |
| const uint8_t *src; |
| 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++); |
| |
| sysappend_strings[SYSAPPEND_BOOTIF] = bootif_str; |
| } |
| |
| /* |
| * Generate an ip=<client-ip>:<boot-server-ip>:<gw-ip>:<netmask> |
| * option into IPOption based on DHCP information in IPInfo. |
| * |
| */ |
| static void genipopt(void) |
| { |
| static char ip_option[3+4*16]; |
| const uint32_t *v = &IPInfo.myip; |
| char *p; |
| int i; |
| |
| p = stpcpy(ip_option, "ip="); |
| |
| for (i = 0; i < 4; i++) { |
| p += gendotquad(p, *v++); |
| *p++ = ':'; |
| } |
| *--p = '\0'; |
| |
| sysappend_strings[SYSAPPEND_IP] = ip_option; |
| } |
| |
| |
| /* Generate ip= option and print the ip adress */ |
| static void ip_init(void) |
| { |
| uint32_t ip = IPInfo.myip; |
| char dot_quad_buf[16]; |
| |
| genipopt(); |
| gendotquad(dot_quad_buf, ip); |
| |
| ip = ntohl(ip); |
| ddprintf("My IP address seems to be %08X %s\n", ip, dot_quad_buf); |
| } |
| |
| /* |
| * Network-specific initialization |
| */ |
| static void network_init(void) |
| { |
| net_parse_dhcp(); |
| |
| make_bootif_string(); |
| /* If DMI and DHCP disagree, which one should we set? */ |
| if (have_uuid) |
| sysappend_set_uuid(uuid); |
| ip_init(); |
| |
| /* 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. |
| */ |
| if ((DHCPMagic & 1) == 0) |
| DHCPMagic = 0; |
| |
| net_core_init(); |
| } |
| |
| /* |
| * Initialize pxe fs |
| * |
| */ |
| static int pxe_fs_init(struct fs_info *fs) |
| { |
| (void)fs; /* drop the compile warning message */ |
| |
| /* Prepare for handling pxe interrupts */ |
| pxe_init_isr(); |
| |
| /* 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; |
| |
| /* Find the PXE stack */ |
| if (pxe_init(false)) |
| kaboom(); |
| |
| /* See if we also have a gPXE stack */ |
| gpxe_init(); |
| |
| /* Network-specific initialization */ |
| network_init(); |
| |
| /* Initialize network-card-specific idle handling */ |
| pxe_idle_init(); |
| |
| /* Our name for the root */ |
| strcpy(fs->cwd_name, "::"); |
| |
| return 0; |
| } |
| |
| /* |
| * Look to see if we are on an EFI CSM system. Some EFI |
| * CSM systems put the BEV stack in low memory, which means |
| * a return to the PXE stack will crash the system. However, |
| * INT 18h works reliably, so in that case hack the stack and |
| * point the "return address" to an INT 18h instruction. |
| * |
| * Hack the stack instead of the much simpler "just invoke INT 18h |
| * if we want to reset", so that chainloading other NBPs will work. |
| * |
| * This manipulates the real-mode InitStack directly. It relies on this |
| * *not* being a currently active stack, i.e. the former |
| * USE_PXE_PROVIDED_STACK no longer works. |
| * |
| * XXX: Disable this until we can find a better way to discriminate |
| * between BIOSes that are broken on BEV return and BIOSes which are |
| * broken on INT 18h. Keying on the EFI CSM turns out to cause more |
| * problems than it solves. |
| */ |
| extern far_ptr_t InitStack; |
| |
| struct efi_struct { |
| uint32_t magic; |
| uint8_t csum; |
| uint8_t len; |
| } __attribute__((packed)); |
| #define EFI_MAGIC (('$' << 24)+('E' << 16)+('F' << 8)+'I') |
| |
| static inline bool is_efi(const struct efi_struct *efi) |
| { |
| /* |
| * We don't verify the checksum, because it seems some CSMs leave |
| * it at zero, sigh... |
| */ |
| return (efi->magic == EFI_MAGIC) && (efi->len >= 83); |
| } |
| |
| #if 0 |
| static void install_int18_hack(void) |
| { |
| static const uint8_t int18_hack[] = |
| { |
| 0xcd, 0x18, /* int $0x18 */ |
| 0xea, 0xf0, 0xff, 0x00, 0xf0, /* ljmpw $0xf000,$0xfff0 */ |
| 0xf4 /* hlt */ |
| }; |
| uint16_t *retcode; |
| |
| retcode = GET_PTR(*(far_ptr_t *)((char *)GET_PTR(InitStack) + 44)); |
| |
| /* Don't do this if the return already points to int $0x18 */ |
| if (*retcode != 0x18cd) { |
| uint32_t efi_ptr; |
| bool efi = false; |
| |
| for (efi_ptr = 0xe0000 ; efi_ptr < 0x100000 ; efi_ptr += 16) { |
| if (is_efi((const struct efi_struct *)efi_ptr)) { |
| efi = true; |
| break; |
| } |
| } |
| |
| if (efi) { |
| uint8_t *src = GET_PTR(InitStack); |
| uint8_t *dst = src - sizeof int18_hack; |
| |
| memmove(dst, src, 52); |
| memcpy(dst+52, int18_hack, sizeof int18_hack); |
| InitStack.offs -= sizeof int18_hack; |
| |
| /* Clobber the return address */ |
| *(uint16_t *)(dst+44) = OFFS_WRT(dst+52, InitStack.seg); |
| *(uint16_t *)(dst+46) = InitStack.seg; |
| } |
| } |
| } |
| #endif |
| |
| 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, |
| .fs_init = pxe_fs_init, |
| .searchdir = pxe_searchdir, |
| .chdir = pxe_chdir, |
| .realpath = pxe_realpath, |
| .getfssec = pxe_getfssec, |
| .close_file = pxe_close_file, |
| .mangle_name = pxe_mangle_name, |
| .chdir_start = pxe_chdir_start, |
| .open_config = pxe_open_config, |
| .readdir = pxe_readdir, |
| .fs_uuid = NULL, |
| }; |