| /* |
| * file.c - Filesystem related interfaces |
| * |
| * Copyright (C) 2001, 2002 Ethan Benson |
| * |
| * parse_device_path() |
| * |
| * Copyright (C) 2001 Colin Walters |
| * |
| * Copyright (C) 1999 Benjamin Herrenschmidt |
| * |
| * 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; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
| */ |
| |
| #include "ctype.h" |
| #include "types.h" |
| #include "stddef.h" |
| #include "stdlib.h" |
| #include "file.h" |
| #include "prom.h" |
| #include "string.h" |
| #include "partition.h" |
| #include "fs.h" |
| #include "errors.h" |
| #include "debug.h" |
| |
| extern char bootdevice[]; |
| |
| /* Convert __u32 into std, dotted quad string, leaks like a sive :( */ |
| static char * |
| ipv4_to_str(__u32 ip) |
| { |
| char *buf = malloc(sizeof("000.000.000.000")); |
| |
| sprintf(buf,"%u.%u.%u.%u", |
| (ip & 0xff000000) >> 24, (ip & 0x00ff0000) >> 16, |
| (ip & 0x0000ff00) >> 8, (ip & 0x000000ff)); |
| |
| return buf; |
| } |
| |
| /* Ensure the string arg is a plausible IPv4 address */ |
| static char * is_valid_ipv4_str(char *str) |
| { |
| int i; |
| long tmp; |
| __u32 ip = 0; |
| char *ptr=str, *endptr; |
| |
| if (str == NULL) |
| return NULL; |
| |
| for (i=0; i<4; i++, ptr = ++endptr) { |
| tmp = strtol(ptr, &endptr, 10); |
| if ((tmp & 0xff) != tmp) |
| return NULL; |
| |
| /* If we reach the end of the string but we're not in the 4th octet |
| * we have an invalid IP */ |
| if (*endptr == '\x0' && i!=3) |
| return NULL; |
| |
| /* If we have anything other than a NULL or '.' we have an invlaid |
| * IP */ |
| if (*endptr != '\x0' && *endptr != '.') |
| return NULL; |
| |
| ip += (tmp << (24-(i*8))); |
| } |
| |
| if (ip == 0 || ip == ~0u) |
| return NULL; |
| |
| return str; |
| } |
| |
| |
| /* |
| * Copy the string from source to dest until the end of string or comma is seen |
| * in the source. |
| * Move source and dest pointers respectively. |
| * Returns pointer to the start of the string that has just been copied. |
| */ |
| static char * |
| scopy(char **dest, char **source) |
| { |
| char *ret = *dest; |
| |
| if (!**source) |
| return NULL; |
| |
| while (**source != ',' && **source != '\0') |
| *(*dest)++ = *(*source)++; |
| if (**source != '\0') |
| (void)*(*source)++; |
| **dest = '\0'; |
| (void)*(*dest)++; |
| return ret; |
| } |
| |
| /* |
| * Extract all the ipv4 arguments from the bootpath provided and fill result |
| * Returns 1 on success, 0 on failure. |
| */ |
| static int |
| extract_ipv4_args(char *imagepath, struct boot_fspec_t *result) |
| { |
| char *tmp, *args, *str, *start; |
| |
| args = strrchr(imagepath, ':'); |
| if (!args) |
| return 1; |
| |
| start = args; /* used to see if we read any optional parameters */ |
| |
| /* The obp-tftp device arguments should be at the end of |
| * the argument list. Skip over any extra arguments (promiscuous, |
| * speed, duplex, bootp, rarp). |
| */ |
| |
| tmp = strstr(args, "promiscuous"); |
| if (tmp && tmp > args) |
| args = tmp + strlen("promiscuous"); |
| |
| tmp = strstr(args, "speed="); |
| if (tmp && tmp > args) |
| args = tmp + strlen("speed="); |
| |
| tmp = strstr(args, "duplex="); |
| if (tmp && tmp > args) |
| args = tmp + strlen("duplex="); |
| |
| tmp = strstr(args, "bootp"); |
| if (tmp && tmp > args) |
| args = tmp + strlen("bootp"); |
| |
| tmp = strstr(args, "rarp"); |
| if (tmp && tmp > args) |
| args = tmp + strlen("rarp"); |
| |
| if (args != start) /* we read some parameters, so go past the next comma(,) */ |
| args = strchr(args, ','); |
| if (!args) |
| return 1; |
| |
| str = malloc(strlen(args) + 1); /*long enough to hold all strings */ |
| if (!str) |
| return 0; |
| |
| if (args[-1] != ':') |
| args++; /* If comma(,) is not immediately followed by ':' then go past the , */ |
| |
| /* |
| * read the arguments in order: siaddr,filename,ciaddr,giaddr, |
| * bootp-retries,tftp-retries,addl_prameters |
| */ |
| result->siaddr = is_valid_ipv4_str(scopy(&str, &args)); |
| result->file = scopy(&str, &args); |
| result->ciaddr = is_valid_ipv4_str(scopy(&str, &args)); |
| result->giaddr = is_valid_ipv4_str(scopy(&str, &args)); |
| result->bootp_retries = scopy(&str, &args); |
| result->tftp_retries = scopy(&str, &args); |
| result->subnetmask = is_valid_ipv4_str(scopy(&str, &args)); |
| if (*args) { |
| result->addl_params = strdup(args); |
| if (!result->addl_params) |
| return 0; |
| } |
| return 1; |
| } |
| |
| /* DHCP options */ |
| enum dhcp_options { |
| DHCP_PAD = 0, |
| DHCP_NETMASK = 1, |
| DHCP_ROUTERS = 3, |
| DHCP_END = 255, |
| }; |
| |
| #define DHCP_COOKIE 0x63825363 |
| #define DHCP_COOKIE_SIZE 4 |
| |
| /* |
| * Process the bootp reply packet's vendor extensions. |
| * Vendor extensions are detailed in: http://www.faqs.org/rfcs/rfc1084.html |
| */ |
| static void |
| extract_vendor_options(struct bootp_packet *packet, struct boot_fspec_t *result) |
| { |
| int i = 0; |
| __u32 cookie; |
| __u8 *options = &packet->options[0]; |
| |
| memcpy(&cookie, &options[i], DHCP_COOKIE_SIZE); |
| |
| if (cookie != DHCP_COOKIE) { |
| prom_printf("EEEK! cookie is fubar got %08x expected %08x\n", |
| cookie, DHCP_COOKIE); |
| return; |
| } |
| |
| i += DHCP_COOKIE_SIZE; |
| |
| /* FIXME: It may be possible to run off the end of a packet here /if/ |
| * it's malformed. :( */ |
| while (options[i] != DHCP_END) { |
| __u8 tag = options[i++], len; |
| __u32 value = 0; |
| |
| if (tag == DHCP_PAD) |
| continue; |
| |
| len = options[i++]; |
| /* Clamp the maxium length of the memcpy() to the right size for |
| * value. */ |
| if (len > sizeof(value)) |
| memcpy(&value, &options[i], sizeof(value)); |
| else |
| memcpy(&value, &options[i], len); |
| |
| #if DEBUG |
| { |
| DEBUG_F("tag=%2d, len=%2d, data=", tag, len); |
| int j; |
| for (j=0; j<len; j++) |
| prom_printf("%02x", options[i+j]); |
| prom_printf("\n"); |
| } |
| #endif |
| |
| switch (tag) { |
| case DHCP_NETMASK: |
| if ((result->subnetmask == NULL || |
| *(result->subnetmask) == '\x0') && value != 0) { |
| result->subnetmask = ipv4_to_str(value); |
| DEBUG_F("Storing %s as subnetmask from options\n", |
| result->subnetmask); |
| } |
| break; |
| case DHCP_ROUTERS: |
| if ((result->giaddr == NULL || *(result->giaddr) == '\x0') |
| && value != 0) { |
| result->giaddr = ipv4_to_str(value); |
| DEBUG_F("Storing %s as gateway from options\n", |
| result->giaddr); |
| } |
| break; |
| } |
| i += len; |
| } |
| } |
| |
| /* |
| * Check netinfo for ipv4 parameters and add them to the fspec iff the |
| * fspec has no existing value. |
| */ |
| static void |
| extract_netinfo_args(struct boot_fspec_t *result) |
| { |
| struct bootp_packet *packet; |
| |
| /* Check to see if we can get the [scyg]iaddr fields from netinfo */ |
| packet = prom_get_netinfo(); |
| if (!packet) |
| return; |
| |
| DEBUG_F("We have a boot packet\n"); |
| DEBUG_F(" siaddr = <%x>\n", packet->siaddr); |
| DEBUG_F(" ciaddr = <%x>\n", packet->ciaddr); |
| DEBUG_F(" yiaddr = <%x>\n", packet->yiaddr); |
| DEBUG_F(" giaddr = <%x>\n", packet->giaddr); |
| |
| /* Try to fallback to yiaddr if ciaddr is empty. Broken? */ |
| if (packet->ciaddr == 0 && packet->yiaddr != 0) |
| packet->ciaddr = packet->yiaddr; |
| |
| if ((result->siaddr == NULL || *(result->siaddr) == '\x0') |
| && packet->siaddr != 0) |
| result->siaddr = ipv4_to_str(packet->siaddr); |
| if ((result->ciaddr == NULL || *(result->ciaddr) == '\x0') |
| && packet->ciaddr != 0) |
| result->ciaddr = ipv4_to_str(packet->ciaddr); |
| if ((result->giaddr == NULL || *(result->giaddr) == '\x0') |
| && packet->giaddr != 0) |
| result->giaddr = ipv4_to_str(packet->giaddr); |
| |
| extract_vendor_options(packet, result); |
| |
| /* FIXME: Yck! if we /still/ do not have a gateway then "cheat" and use |
| * the server. This will be okay if the client and server are on |
| * the same IP network, if not then lets hope the server does ICMP |
| * redirections */ |
| if (result->giaddr == NULL) { |
| result->giaddr = ipv4_to_str(packet->siaddr); |
| DEBUG_F("Forcing giaddr to siaddr <%s>\n", result->giaddr); |
| } |
| } |
| |
| /* |
| * Extract all the ipv6 arguments from the bootpath provided and fill result |
| * Syntax: ipv6,[dhcpv6[=diaddr,]]ciaddr=c_iaddr,giaddr=g_iaddr,siaddr=s_iaddr, |
| * filename=file_name,tftp-retries=tftp_retries,blksize=block_size |
| * Returns 1 on success, 0 on failure. |
| */ |
| static int |
| extract_ipv6_args(char *imagepath, struct boot_fspec_t *result) |
| { |
| char *str, *tmp; |
| int total_len; |
| |
| result->is_ipv6 = 1; |
| |
| /* Just allocate the max required size */ |
| total_len = strlen(imagepath) + 1; |
| str = malloc(total_len); |
| if (!str) |
| return 0; |
| |
| if ((tmp = strstr(imagepath, "dhcpv6=")) != NULL) |
| result->dhcpv6 = scopy(&str, &tmp); |
| |
| if ((tmp = strstr(imagepath, "ciaddr=")) != NULL) |
| result->ciaddr = scopy(&str, &tmp); |
| |
| if ((tmp = strstr(imagepath, "giaddr=")) != NULL) |
| result->giaddr = scopy(&str, &tmp); |
| |
| if ((tmp = strstr(imagepath, "siaddr=")) != NULL) |
| result->siaddr = scopy(&str, &tmp); |
| |
| if ((tmp = strstr(imagepath, "filename=")) != NULL) |
| result->file = scopy(&str, &tmp); |
| |
| if ((tmp = strstr(imagepath, "tftp-retries=")) != NULL) |
| result->tftp_retries = scopy(&str, &tmp); |
| |
| if ((tmp = strstr(imagepath, "blksize=")) != NULL) |
| result->blksize = scopy(&str, &tmp); |
| |
| return 1; |
| } |
| |
| /* |
| * Extract all the arguments provided in the imagepath and fill it in result. |
| * Returns 1 on success, 0 on failure. |
| */ |
| static int |
| extract_netboot_args(char *imagepath, struct boot_fspec_t *result) |
| { |
| int ret; |
| |
| DEBUG_F("imagepath = %s\n", imagepath); |
| |
| if (!imagepath) |
| return 1; |
| |
| if (strstr(imagepath, TOK_IPV6)) |
| ret = extract_ipv6_args(imagepath, result); |
| else |
| ret = extract_ipv4_args(imagepath, result); |
| extract_netinfo_args(result); |
| |
| DEBUG_F("ipv6 = <%d>\n", result->is_ipv6); |
| DEBUG_F("siaddr = <%s>\n", result->siaddr); |
| DEBUG_F("file = <%s>\n", result->file); |
| DEBUG_F("ciaddr = <%s>\n", result->ciaddr); |
| DEBUG_F("giaddr = <%s>\n", result->giaddr); |
| DEBUG_F("bootp_retries = <%s>\n", result->bootp_retries); |
| DEBUG_F("tftp_retries = <%s>\n", result->tftp_retries); |
| DEBUG_F("addl_params = <%s>\n", result->addl_params); |
| DEBUG_F("dhcpv6 = <%s>\n", result->dhcpv6); |
| DEBUG_F("blksize = <%s>\n", result->blksize); |
| |
| return ret; |
| } |
| |
| static char *netdev_path_to_dev(const char *path) |
| { |
| char *dev, *tmp; |
| size_t len; |
| |
| DEBUG_F("path = %s\n", path); |
| |
| if (!path) |
| return NULL; |
| |
| tmp = strchr(path, ':'); |
| if (!tmp) |
| return strdup(path); |
| tmp++; |
| |
| len = tmp - path + 1; |
| |
| dev = malloc(len); |
| if (dev) { |
| strncpy(dev, path, len); |
| dev[len - 1] = '\0'; |
| } |
| return dev; |
| } |
| |
| /* This function follows the device path in the devtree and separates |
| the device name, partition number, and other datas (mostly file name) |
| the string passed in parameters is changed since 0 are put in place |
| of some separators to terminate the various strings. |
| |
| when a default device is supplied imagepath will be assumed to be a |
| plain filename unless it contains a : otherwise if defaultdev is |
| NULL imagepath will be assumed to be a device path. |
| |
| returns 1 on success 0 on failure. |
| |
| Supported examples: |
| - /pci@80000000/pci-bridge@d/ADPT,2930CU@2/@1:4 |
| - /pci@80000000/pci-bridge@d/ADPT,2930CU@2/@1:4,/boot/vmlinux |
| - hd:3,/boot/vmlinux |
| - enet:10.0.0.1,/tftpboot/vmlinux |
| - enet:,/tftpboot/vmlinux |
| - enet:bootp |
| - enet:0 |
| - arguments for obp-tftp open as specified in section 4.1 of |
| http://playground.sun.com/1275/practice/obp-tftp/tftp1_0.pdf |
| [bootp,]siaddr,filename,ciaddr,giaddr,bootp-retries,tftp-retries |
| ex: enet:bootp,10.0.0.11,bootme,10.0.0.12,10.0.0.1,5,5 |
| Supported only if defdevice == NULL |
| - disc |
| - any other device path lacking a : |
| Unsupported examples: |
| - hd:2,\\:tbxi <- no filename will be detected due to the extra : |
| - enet:192.168.2.1,bootme,c-iaddr,g-iaddr,subnet-mask,bootp-retries,tftp-retries */ |
| |
| int |
| parse_device_path(char *imagepath, char *defdevice, int defpart, |
| char *deffile, struct boot_fspec_t *result) |
| { |
| char *ptr; |
| char *ipath = NULL; |
| char *defdev = NULL; |
| int device_kind = -1; |
| |
| DEBUG_F("imagepath = %s; defdevice %s; defpart %d, deffile %s\n", |
| imagepath, defdevice, defpart, deffile); |
| |
| result->dev = NULL; |
| result->part = -1; |
| result->file = NULL; |
| |
| if (!imagepath) |
| return 0; |
| |
| /* |
| * Do preliminary checking for an iscsi device; it may appear as |
| * pure a network device (device_type == "network") if this is |
| * ISWI. This is the case on IBM systems doing an iscsi OFW |
| * boot. |
| */ |
| if (strstr(imagepath, TOK_ISCSI)) { |
| /* |
| * get the virtual device information from the |
| * "nas-bootdevice" property. |
| */ |
| if (prom_get_chosen("nas-bootdevice", bootdevice, BOOTDEVSZ)) { |
| DEBUG_F("reset boot-device to" |
| " /chosen/nas-bootdevice = %s\n", bootdevice); |
| device_kind = FILE_DEVICE_ISCSI; |
| ipath = strdup(bootdevice); |
| if (!ipath) |
| return 0; |
| } |
| else |
| return 0; |
| } |
| else if (!(ipath = strdup(imagepath))) |
| return 0; |
| |
| if (defdevice) { |
| defdev = strdup(defdevice); |
| device_kind = prom_get_devtype(defdev); |
| } else if (device_kind == -1) |
| device_kind = prom_get_devtype(ipath); |
| |
| /* |
| * When an iscsi iqn is present, it may have embedded colons, so |
| * don't parse off anything. |
| */ |
| if (device_kind != FILE_DEVICE_NET && |
| device_kind != FILE_DEVICE_ISCSI && |
| strchr(defdev, ':') != NULL) { |
| if ((ptr = strrchr(defdev, ':')) != NULL) |
| *ptr = 0; /* remove trailing : from defdevice if necessary */ |
| } |
| |
| /* This will not properly handle an obp-tftp argument list |
| * with elements after the filename; that is handled below. |
| */ |
| if (device_kind != FILE_DEVICE_NET && |
| device_kind != FILE_DEVICE_ISCSI && |
| strchr(ipath, ':') != NULL) { |
| if ((ptr = strrchr(ipath, ',')) != NULL) { |
| char *colon = strrchr(ipath, ':'); |
| /* If a ':' occurs *after* a ',', then we assume that there is |
| no filename */ |
| if (!colon || colon < ptr) { |
| result->file = strdup(ptr+1); |
| /* Trim the filename off */ |
| *ptr = 0; |
| } |
| } |
| } |
| |
| if (device_kind == FILE_DEVICE_NET) { |
| if (strchr(ipath, ':')) { |
| if (extract_netboot_args(ipath, result) == 0) |
| return 0; |
| } else { |
| /* If we didn't get a ':' then look only in netinfo */ |
| extract_netinfo_args(result); |
| result->file = strdup(ipath); |
| } |
| |
| if (!defdev) |
| result->dev = netdev_path_to_dev(ipath); |
| } else if (device_kind != FILE_DEVICE_ISCSI && |
| (ptr = strrchr(ipath, ':')) != NULL) { |
| *ptr = 0; |
| result->dev = strdup(ipath); |
| if (*(ptr+1)) |
| result->part = simple_strtol(ptr+1, NULL, 10); |
| } else if (!defdev) { |
| result->dev = strdup(ipath); |
| } else if (strlen(ipath)) { |
| result->file = strdup(ipath); |
| } else { |
| free(defdev); |
| return 0; |
| } |
| |
| if (!result->dev && defdev) |
| result->dev = strdup(defdev); |
| |
| if (result->part < 0) |
| result->part = defpart; |
| |
| if (!result->file) |
| result->file = strdup(deffile); |
| |
| free(ipath); |
| if (defdev) |
| free(defdev); |
| return 1; |
| } |
| |
| |
| static int |
| file_block_open( struct boot_file_t* file, |
| struct boot_fspec_t* fspec, |
| int partition) |
| { |
| struct partition_t* parts; |
| struct partition_t* p; |
| struct partition_t* found; |
| |
| parts = partitions_lookup(fspec->dev); |
| found = NULL; |
| |
| #if DEBUG |
| if (parts) |
| prom_printf("partitions:\n"); |
| else |
| prom_printf("no partitions found.\n"); |
| #endif |
| for (p = parts; p && !found; p=p->next) { |
| DEBUG_F("number: %02d, start: 0x%08lx, length: 0x%08lx\n", |
| p->part_number, p->part_start, p->part_size ); |
| if (partition == -1) { |
| file->fs = fs_open( file, p, fspec ); |
| if (file->fs == NULL || fserrorno != FILE_ERR_OK) |
| continue; |
| else { |
| partition = p->part_number; |
| goto done; |
| } |
| } |
| if ((partition >= 0) && (partition == p->part_number)) |
| found = p; |
| #if DEBUG |
| if (found) |
| prom_printf(" (match)\n"); |
| #endif |
| } |
| |
| /* Note: we don't skip when found is NULL since we can, in some |
| * cases, let OF figure out a default partition. |
| */ |
| DEBUG_F( "Using OF defaults.. (found = %p)\n", found ); |
| file->fs = fs_open( file, found, fspec ); |
| |
| done: |
| if (parts) |
| partitions_free(parts); |
| |
| return fserrorno; |
| } |
| |
| static int |
| file_net_open(struct boot_file_t* file, struct boot_fspec_t *fspec) |
| { |
| file->fs = fs_of_netboot; |
| return fs_of_netboot->open(file, NULL, fspec); |
| } |
| |
| static int |
| default_read( struct boot_file_t* file, |
| unsigned int size, |
| void* buffer) |
| { |
| prom_printf("WARNING ! default_read called !\n"); |
| return FILE_ERR_EOF; |
| } |
| |
| static int |
| default_seek( struct boot_file_t* file, |
| unsigned int newpos) |
| { |
| prom_printf("WARNING ! default_seek called !\n"); |
| return FILE_ERR_EOF; |
| } |
| |
| static int |
| default_close( struct boot_file_t* file) |
| { |
| prom_printf("WARNING ! default_close called !\n"); |
| return FILE_ERR_OK; |
| } |
| |
| static struct fs_t fs_default = |
| { |
| "defaults", |
| NULL, |
| default_read, |
| default_seek, |
| default_close |
| }; |
| |
| |
| int open_file(struct boot_fspec_t* spec, struct boot_file_t* file) |
| { |
| int result; |
| |
| memset(file, 0, sizeof(struct boot_file_t*)); |
| file->fs = &fs_default; |
| |
| DEBUG_F("dev_path = %s\nfile_name = %s\npartition = %d\n", |
| spec->dev, spec->file, spec->part); |
| |
| result = prom_get_devtype(spec->dev); |
| if (result > 0) |
| file->device_kind = result; |
| else |
| return result; |
| |
| switch(file->device_kind) { |
| case FILE_DEVICE_BLOCK: |
| DEBUG_F("device is a block device\n"); |
| return file_block_open(file, spec, spec->part); |
| case FILE_DEVICE_ISCSI: |
| DEBUG_F("device is a iSCSI device\n"); |
| return file_block_open(file, spec, spec->part); |
| case FILE_DEVICE_NET: |
| DEBUG_F("device is a network device\n"); |
| return file_net_open(file, spec); |
| } |
| return 0; |
| } |
| |
| /* |
| * Local variables: |
| * c-file-style: "k&r" |
| * c-basic-offset: 5 |
| * End: |
| */ |