blob: fd081a3010d3ba4710349144da54ac73fe23cb3f [file] [log] [blame] [raw]
/*
* 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:
*/