grub/grub-core/osdep/linux/ofpath.c
Eric Snowberg f8679cedff sparc64: Don't use devspec to determine the OBP path
Don't use devspec to determine the OBP path on SPARC hardware.  Within all
versions of Linux on SPARC, the devspec returns one of three values:
"none", "vnet-port", or "vdisk".  Unlike on PPC, none of these values
are useful in determining the OBP path.

Before this patch grub-ofpathname always returned the wrong value
for a virtual disk. For example:

% grub-ofpathname /dev/vdiskc2
vdisk/disk@2:b

After this patch it now returns the correct value:

% grub-ofpathname /dev/vdiskc2
/virtual-devices@100/channel-devices@200/disk@2:b

Orabug: 24459765

Signed-off-by: Eric Snowberg <eric.snowberg@oracle.com>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
2017-05-29 16:59:44 +02:00

574 lines
13 KiB
C

/* ofpath.c - calculate OpenFirmware path names given an OS device */
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2009, 2011,2012, 2013 Free Software Foundation, Inc.
*
* GRUB 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 3 of the License, or
* (at your option) any later version.
*
* GRUB 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 GRUB. If not, see <http://www.gnu.org/licenses/>.
*/
#undef OFPATH_STANDALONE
#ifndef OFPATH_STANDALONE
#include <grub/types.h>
#include <grub/util/misc.h>
#include <grub/util/ofpath.h>
#include <grub/i18n.h>
#endif
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <ctype.h>
#ifdef OFPATH_STANDALONE
#define xmalloc malloc
void
grub_util_error (const char *fmt, ...)
{
va_list ap;
fprintf (stderr, "ofpath: error: ");
va_start (ap, fmt);
vfprintf (stderr, fmt, ap);
va_end (ap);
fputc ('\n', stderr);
exit (1);
}
void
grub_util_info (const char *fmt, ...)
{
va_list ap;
fprintf (stderr, "ofpath: info: ");
va_start (ap, fmt);
vfprintf (stderr, fmt, ap);
va_end (ap);
fputc ('\n', stderr);
}
#define grub_util_warn grub_util_info
#define _(x) x
#define xstrdup strdup
#endif
static void
kill_trailing_dir(char *path)
{
char *end = path + strlen(path) - 1;
while (end >= path)
{
if (*end != '/')
{
end--;
continue;
}
*end = '\0';
break;
}
}
static void
trim_newline (char *path)
{
char *end = path + strlen(path) - 1;
while (*end == '\n')
*end-- = '\0';
}
#define MAX_DISK_CAT 64
static char *
find_obppath (const char *sysfs_path_orig)
{
char *sysfs_path, *path;
size_t path_size = strlen (sysfs_path_orig) + sizeof ("/obppath");
sysfs_path = xstrdup (sysfs_path_orig);
path = xmalloc (path_size);
while (1)
{
int fd;
char *of_path;
struct stat st;
size_t size;
snprintf(path, path_size, "%s/obppath", sysfs_path);
#if 0
printf("Trying %s\n", path);
#endif
fd = open(path, O_RDONLY);
#ifndef __sparc__
if (fd < 0 || fstat (fd, &st) < 0)
{
if (fd >= 0)
close (fd);
snprintf(path, path_size, "%s/devspec", sysfs_path);
fd = open(path, O_RDONLY);
}
#endif
if (fd < 0 || fstat (fd, &st) < 0)
{
if (fd >= 0)
close (fd);
kill_trailing_dir(sysfs_path);
if (!strcmp(sysfs_path, "/sys"))
{
grub_util_info (_("`obppath' not found in parent dirs of `%s',"
" no IEEE1275 name discovery"),
sysfs_path_orig);
free (path);
free (sysfs_path);
return NULL;
}
continue;
}
size = st.st_size;
of_path = xmalloc (size + MAX_DISK_CAT + 1);
memset(of_path, 0, size + MAX_DISK_CAT + 1);
if (read(fd, of_path, size) < 0)
{
grub_util_info (_("cannot read `%s': %s"), path, strerror (errno));
close(fd);
free (path);
free (of_path);
free (sysfs_path);
return NULL;
}
close(fd);
trim_newline(of_path);
free (path);
free (sysfs_path);
return of_path;
}
}
static char *
xrealpath (const char *in)
{
char *out;
#ifdef PATH_MAX
out = xmalloc (PATH_MAX);
out = realpath (in, out);
#else
out = realpath (in, NULL);
#endif
if (!out)
grub_util_error (_("failed to get canonical path of `%s'"), in);
return out;
}
static char *
block_device_get_sysfs_path_and_link(const char *devicenode)
{
char *rpath;
char *rpath2;
char *ret;
size_t tmp_size = strlen (devicenode) + sizeof ("/sys/block/");
char *tmp = xmalloc (tmp_size);
memcpy (tmp, "/sys/block/", sizeof ("/sys/block/"));
strcat (tmp, devicenode);
rpath = xrealpath (tmp);
rpath2 = xmalloc (strlen (rpath) + sizeof ("/device"));
strcpy (rpath2, rpath);
strcat (rpath2, "/device");
ret = xrealpath (rpath2);
free (tmp);
free (rpath);
free (rpath2);
return ret;
}
static inline int
my_isdigit (int c)
{
return (c >= '0' && c <= '9');
}
static const char *
trailing_digits (const char *p)
{
const char *end;
end = p + strlen(p) - 1;
while (end >= p)
{
if (! my_isdigit(*end))
break;
end--;
}
return end + 1;
}
static char *
__of_path_common(char *sysfs_path,
const char *device, int devno)
{
const char *digit_string;
char disk[MAX_DISK_CAT];
char *of_path = find_obppath(sysfs_path);
if (!of_path)
return NULL;
digit_string = trailing_digits (device);
if (*digit_string == '\0')
{
snprintf(disk, sizeof (disk), "/disk@%d", devno);
}
else
{
int part;
sscanf(digit_string, "%d", &part);
snprintf(disk, sizeof (disk), "/disk@%d:%c", devno, 'a' + (part - 1));
}
strcat(of_path, disk);
return of_path;
}
static char *
get_basename(char *p)
{
char *ret = p;
while (*p)
{
if (*p == '/')
ret = p + 1;
p++;
}
return ret;
}
static char *
of_path_of_vdisk(const char *sys_devname __attribute__((unused)),
const char *device,
const char *devnode __attribute__((unused)),
const char *devicenode)
{
char *sysfs_path, *p;
int devno, junk;
char *ret;
sysfs_path = block_device_get_sysfs_path_and_link(devicenode);
p = get_basename (sysfs_path);
sscanf(p, "vdc-port-%d-%d", &devno, &junk);
ret = __of_path_common (sysfs_path, device, devno);
free (sysfs_path);
return ret;
}
static char *
of_path_of_ide(const char *sys_devname __attribute__((unused)), const char *device,
const char *devnode __attribute__((unused)),
const char *devicenode)
{
char *sysfs_path, *p;
int chan, devno;
char *ret;
sysfs_path = block_device_get_sysfs_path_and_link(devicenode);
p = get_basename (sysfs_path);
sscanf(p, "%d.%d", &chan, &devno);
ret = __of_path_common(sysfs_path, device, 2 * chan + devno);
free (sysfs_path);
return ret;
}
static int
vendor_is_ATA(const char *path)
{
int fd, err;
char *bufname;
char bufcont[3];
size_t path_size;
path_size = strlen (path) + sizeof ("/vendor");
bufname = xmalloc (path_size);
snprintf (bufname, path_size, "%s/vendor", path);
fd = open (bufname, O_RDONLY);
if (fd < 0)
grub_util_error (_("cannot open `%s': %s"), bufname, strerror (errno));
memset(bufcont, 0, sizeof (bufcont));
err = read(fd, bufcont, sizeof (bufcont));
if (err < 0)
grub_util_error (_("cannot open `%s': %s"), bufname, strerror (errno));
close(fd);
free (bufname);
return (memcmp(bufcont, "ATA", 3) == 0);
}
static void
check_sas (const char *sysfs_path, int *tgt, unsigned long int *sas_address)
{
char *ed = strstr (sysfs_path, "end_device");
char *p, *q, *path;
char phy[21];
int fd;
size_t path_size;
if (!ed)
return;
/* SAS devices are identified using disk@$PHY_ID */
p = xstrdup (sysfs_path);
ed = strstr(p, "end_device");
if (!ed)
return;
q = ed;
while (*q && *q != '/')
q++;
*q = '\0';
path_size = (strlen (p) + strlen (ed)
+ sizeof ("%s/sas_device/%s/phy_identifier"));
path = xmalloc (path_size);
snprintf (path, path_size, "%s/sas_device/%s/phy_identifier", p, ed);
fd = open (path, O_RDONLY);
if (fd < 0)
grub_util_error (_("cannot open `%s': %s"), path, strerror (errno));
memset (phy, 0, sizeof (phy));
if (read (fd, phy, sizeof (phy) - 1) < 0)
grub_util_error (_("cannot read `%s': %s"), path, strerror (errno));
close (fd);
sscanf (phy, "%d", tgt);
snprintf (path, path_size, "%s/sas_device/%s/sas_address", p, ed);
fd = open (path, O_RDONLY);
if (fd < 0)
grub_util_error (_("cannot open `%s': %s"), path, strerror (errno));
memset (phy, 0, sizeof (phy));
if (read (fd, phy, sizeof (phy) - 1) < 0)
grub_util_error (_("cannot read `%s': %s"), path, strerror (errno));
sscanf (phy, "%lx", sas_address);
free (path);
free (p);
close (fd);
}
static char *
of_path_of_scsi(const char *sys_devname __attribute__((unused)), const char *device,
const char *devnode __attribute__((unused)),
const char *devicenode)
{
const char *p, *digit_string, *disk_name;
int host, bus, tgt, lun;
unsigned long int sas_address;
char *sysfs_path, disk[MAX_DISK_CAT - sizeof ("/fp@0,0")];
char *of_path;
sysfs_path = block_device_get_sysfs_path_and_link(devicenode);
p = get_basename (sysfs_path);
sscanf(p, "%d:%d:%d:%d", &host, &bus, &tgt, &lun);
check_sas (sysfs_path, &tgt, &sas_address);
if (vendor_is_ATA(sysfs_path))
{
of_path = __of_path_common(sysfs_path, device, tgt);
free (sysfs_path);
return of_path;
}
of_path = find_obppath(sysfs_path);
free (sysfs_path);
if (!of_path)
return NULL;
if (strstr (of_path, "qlc"))
strcat (of_path, "/fp@0,0");
if (strstr (of_path, "sbus"))
disk_name = "sd";
else
disk_name = "disk";
digit_string = trailing_digits (device);
if (strncmp (of_path, "/vdevice/", sizeof ("/vdevice/") - 1) == 0)
{
unsigned long id = 0x8000 | (tgt << 8) | (bus << 5) | lun;
if (*digit_string == '\0')
{
snprintf(disk, sizeof (disk), "/%s@%04lx000000000000", disk_name, id);
}
else
{
int part;
sscanf(digit_string, "%d", &part);
snprintf(disk, sizeof (disk),
"/%s@%04lx000000000000:%c", disk_name, id, 'a' + (part - 1));
}
}
else
{
if (lun == 0)
{
int sas_id = 0;
sas_id = bus << 16 | tgt << 8 | lun;
if (*digit_string == '\0')
{
snprintf(disk, sizeof (disk), "/sas/%s@%x", disk_name, sas_id);
}
else
{
int part;
sscanf(digit_string, "%d", &part);
snprintf(disk, sizeof (disk),
"/sas/%s@%x:%c", disk_name, sas_id, 'a' + (part - 1));
}
}
else
{
char *lunstr;
int lunpart[4];
lunstr = xmalloc (20);
lunpart[0] = (lun >> 8) & 0xff;
lunpart[1] = lun & 0xff;
lunpart[2] = (lun >> 24) & 0xff;
lunpart[3] = (lun >> 16) & 0xff;
sprintf(lunstr, "%02x%02x%02x%02x00000000", lunpart[0], lunpart[1], lunpart[2], lunpart[3]);
long int longlun = atol(lunstr);
if (*digit_string == '\0')
{
snprintf(disk, sizeof (disk), "/sas/%s@%lx,%lu", disk_name, sas_address, longlun);
}
else
{
int part;
sscanf(digit_string, "%d", &part);
snprintf(disk, sizeof (disk),
"/sas/%s@%lx,%lu:%c", disk_name, sas_address, longlun, 'a' + (part - 1));
}
free (lunstr);
}
}
strcat(of_path, disk);
return of_path;
}
static char *
strip_trailing_digits (const char *p)
{
char *new, *end;
new = strdup (p);
end = new + strlen(new) - 1;
while (end >= new)
{
if (! my_isdigit(*end))
break;
*end-- = '\0';
}
return new;
}
char *
grub_util_devname_to_ofpath (const char *sys_devname)
{
char *name_buf, *device, *devnode, *devicenode, *ofpath;
name_buf = xrealpath (sys_devname);
device = get_basename (name_buf);
devnode = strip_trailing_digits (name_buf);
devicenode = strip_trailing_digits (device);
if (device[0] == 'h' && device[1] == 'd')
ofpath = of_path_of_ide(name_buf, device, devnode, devicenode);
else if (device[0] == 's'
&& (device[1] == 'd' || device[1] == 'r'))
ofpath = of_path_of_scsi(name_buf, device, devnode, devicenode);
else if (device[0] == 'v' && device[1] == 'd' && device[2] == 'i'
&& device[3] == 's' && device[4] == 'k')
ofpath = of_path_of_vdisk(name_buf, device, devnode, devicenode);
else if (device[0] == 'f' && device[1] == 'd'
&& device[2] == '0' && device[3] == '\0')
/* All the models I've seen have a devalias "floppy".
New models have no floppy at all. */
ofpath = xstrdup ("floppy");
else
{
grub_util_warn (_("unknown device type %s"), device);
ofpath = NULL;
}
free (devnode);
free (devicenode);
free (name_buf);
return ofpath;
}
#ifdef OFPATH_STANDALONE
int main(int argc, char **argv)
{
char *of_path;
if (argc != 2)
{
printf(_("Usage: %s DEVICE\n"), argv[0]);
return 1;
}
of_path = grub_util_devname_to_ofpath (argv[1]);
if (of_path)
printf("%s\n", of_path);
free (of_path);
return 0;
}
#endif