blob: 875cad807063efaac91c729648786a470d4e938c [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[];
/*
* Copy the string from source to dest till newline 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')
*(*source)++;
**dest = '\0';
*(*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 = scopy(&str, &args);
result->file = scopy(&str, &args);
result->ciaddr = scopy(&str, &args);
result->giaddr = scopy(&str, &args);
result->bootp_retries = scopy(&str, &args);
result->tftp_retries = scopy(&str, &args);
if (*args) {
result->addl_params = strdup(args);
if (!result->addl_params)
return 0;
}
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_args_from_netdev_path(char *imagepath, struct boot_fspec_t *result)
{
int ret;
DEBUG_F("imagepath = %s\n", imagepath);
if (!imagepath)
return 1;
ret = extract_ipv4_args(imagepath, result);
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);
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_args_from_netdev_path(ipath, result) == 0)
return 0;
} else
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_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:
*/