blob: 4bd49cd947347734ca990a1525a85943accdb09b [file] [log] [blame] [edit]
/*
libparted - a library for manipulating disk partitions
Copyright (C) 1999 - 2005, 2007 Free Software Foundation, Inc.
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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#define PROC_DEVICES_BUFSIZ 16384
#include <config.h>
#include <parted/parted.h>
#include <parted/debug.h>
#include <parted/linux.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <stdio.h>
#include <syscall.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/utsname.h> /* for uname() */
#include <scsi/scsi.h>
#ifdef ENABLE_DEVICE_MAPPER
#include <libdevmapper.h>
#endif
#include "blkpg.h"
#if ENABLE_NLS
# include <libintl.h>
# define _(String) dgettext (PACKAGE, String)
#else
# define _(String) (String)
#endif /* ENABLE_NLS */
#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
#ifndef __NR__llseek
#define __NR__llseek 140
#endif
#ifndef SCSI_IOCTL_SEND_COMMAND
#define SCSI_IOCTL_SEND_COMMAND 1
#endif
/* from <linux/hdreg.h> */
#define HDIO_GETGEO 0x0301 /* get device geometry */
#define HDIO_GET_IDENTITY 0x030d /* get IDE identification info */
/* O_DIRECT mode not supported in Sapphire */
#undef O_DIRECT
#if defined(O_DIRECT) && (!defined(__s390__) || !defined(__s390x__))
#define RD_MODE (O_RDONLY | O_DIRECT)
#define WR_MODE (O_WRONLY | O_DIRECT)
#define RW_MODE (O_RDWR | O_DIRECT)
#else
#define RD_MODE (O_RDONLY)
#define WR_MODE (O_WRONLY)
#define RW_MODE (O_RDWR)
#endif
struct hd_geometry {
unsigned char heads;
unsigned char sectors;
unsigned short cylinders;
unsigned long start;
};
struct ata7_sectinfo {
int valid1:1;
int valid2:1;
int rsv:26;
int multiplier:4;
};
/* structure returned by HDIO_GET_IDENTITY, as per ANSI ATA2 rev.2f spec */
struct hd_driveid {
unsigned short config; /* lots of obsolete bit flags */
unsigned short cyls; /* "physical" cyls */
unsigned short reserved2; /* reserved (word 2) */
unsigned short heads; /* "physical" heads */
unsigned short track_bytes; /* unformatted bytes per track */
unsigned short sector_bytes; /* unformatted bytes per sector */
unsigned short sectors; /* "physical" sectors per track */
unsigned short vendor0; /* vendor unique */
unsigned short vendor1; /* vendor unique */
unsigned short vendor2; /* vendor unique */
unsigned char serial_no[20]; /* 0 = not_specified */
unsigned short buf_type;
unsigned short buf_size; /* 512 byte increments;
0 = not_specified */
unsigned short ecc_bytes; /* for r/w long cmds;
0 = not_specified */
unsigned char fw_rev[8]; /* 0 = not_specified */
char model[40]; /* 0 = not_specified */
unsigned char max_multsect; /* 0=not_implemented */
unsigned char vendor3; /* vendor unique */
unsigned short dword_io; /* 0=not_implemented; 1=implemented */
unsigned char vendor4; /* vendor unique */
unsigned char capability; /* bits 0:DMA 1:LBA 2:IORDYsw
3:IORDYsup*/
unsigned short reserved50; /* reserved (word 50) */
unsigned char vendor5; /* vendor unique */
unsigned char tPIO; /* 0=slow, 1=medium, 2=fast */
unsigned char vendor6; /* vendor unique */
unsigned char tDMA; /* 0=slow, 1=medium, 2=fast */
unsigned short field_valid; /* bits 0:cur_ok 1:eide_ok */
unsigned short cur_cyls; /* logical cylinders */
unsigned short cur_heads; /* logical heads */
unsigned short cur_sectors; /* logical sectors per track */
unsigned short cur_capacity0; /* logical total sectors on drive */
unsigned short cur_capacity1; /* (2 words, misaligned int) */
unsigned char multsect; /* current multiple sector count */
unsigned char multsect_valid; /* when (bit0==1) multsect is ok */
unsigned int lba_capacity; /* total number of sectors */
unsigned short dma_1word; /* single-word dma info */
unsigned short dma_mword; /* multiple-word dma info */
unsigned short eide_pio_modes; /* bits 0:mode3 1:mode4 */
unsigned short eide_dma_min; /* min mword dma cycle time (ns) */
unsigned short eide_dma_time; /* recommended mword dma cycle
time (ns) */
unsigned short eide_pio; /* min cycle time (ns), no IORDY */
unsigned short eide_pio_iordy; /* min cycle time (ns), with IORDY */
unsigned short words69_70[2]; /* reserved words 69-70 */
/* HDIO_GET_IDENTITY currently returns only words 0 through 70 */
unsigned short words71_74[4]; /* reserved words 71-74 */
unsigned short queue_depth; /* */
unsigned short words76_79[4]; /* reserved words 76-79 */
unsigned short major_rev_num; /* */
unsigned short minor_rev_num; /* */
unsigned short command_set_1; /* bits 0:Smart 1:Security 2:Removable
3:PM */
unsigned short command_set_2; /* bits 14:Smart Enabled 13:0 zero */
unsigned short cfsse; /* command set-feature supported
extensions */
unsigned short cfs_enable_1; /* command set-feature enabled */
unsigned short cfs_enable_2; /* command set-feature enabled */
unsigned short csf_default; /* command set-feature default */
unsigned short dma_ultra; /* */
unsigned short word89; /* reserved (word 89) */
unsigned short word90; /* reserved (word 90) */
unsigned short CurAPMvalues; /* current APM values */
unsigned short word92; /* reserved (word 92) */
unsigned short hw_config; /* hardware config */
unsigned short words94_105[12];/* reserved words 94-105 */
struct ata7_sectinfo ata7_sectinfo; /* ATAPI/ATA7 physical and logical
sector size */
unsigned short words107_116[10];/* reserved words 107-116 */
unsigned int logical_sectsize;/* ATAPI/ATA7 logical sector size */
unsigned short words119_125[7];/* reserved words 119-125 */
unsigned short last_lun; /* reserved (word 126) */
unsigned short word127; /* reserved (word 127) */
unsigned short dlf; /* device lock function
* 15:9 reserved
* 8 security level 1:max 0:high
* 7:6 reserved
* 5 enhanced erase
* 4 expire
* 3 frozen
* 2 locked
* 1 en/disabled
* 0 capability
*/
unsigned short csfo; /* current set features options
* 15:4 reserved
* 3 auto reassign
* 2 reverting
* 1 read-look-ahead
* 0 write cache
*/
unsigned short words130_155[26];/* reserved vendor words 130-155 */
unsigned short word156;
unsigned short words157_159[3]; /* reserved vendor words 157-159 */
unsigned short words160_255[95];/* reserved words 160-255 */
};
/* from <linux/fs.h> */
#define BLKRRPART _IO(0x12,95) /* re-read partition table */
#define BLKGETSIZE _IO(0x12,96) /* return device size */
#define BLKFLSBUF _IO(0x12,97) /* flush buffer cache */
#define BLKSSZGET _IO(0x12,104) /* get block device sector size */
#define BLKGETLASTSECT _IO(0x12,108) /* get last sector of block device */
#define BLKSETLASTSECT _IO(0x12,109) /* set last sector of block device */
/* return device size in bytes (u64 *arg) */
#define BLKGETSIZE64 _IOR(0x12,114,size_t)
struct blkdev_ioctl_param {
unsigned int block;
size_t content_length;
char * block_contents;
};
/* from <linux/major.h> */
#define IDE0_MAJOR 3
#define IDE1_MAJOR 22
#define IDE2_MAJOR 33
#define IDE3_MAJOR 34
#define IDE4_MAJOR 56
#define IDE5_MAJOR 57
#define SCSI_CDROM_MAJOR 11
#define SCSI_DISK0_MAJOR 8
#define SCSI_DISK1_MAJOR 65
#define SCSI_DISK2_MAJOR 66
#define SCSI_DISK3_MAJOR 67
#define SCSI_DISK4_MAJOR 68
#define SCSI_DISK5_MAJOR 69
#define SCSI_DISK6_MAJOR 70
#define SCSI_DISK7_MAJOR 71
#define COMPAQ_SMART2_MAJOR 72
#define COMPAQ_SMART2_MAJOR1 73
#define COMPAQ_SMART2_MAJOR2 74
#define COMPAQ_SMART2_MAJOR3 75
#define COMPAQ_SMART2_MAJOR4 76
#define COMPAQ_SMART2_MAJOR5 77
#define COMPAQ_SMART2_MAJOR6 78
#define COMPAQ_SMART2_MAJOR7 79
#define COMPAQ_SMART_MAJOR 104
#define COMPAQ_SMART_MAJOR1 105
#define COMPAQ_SMART_MAJOR2 106
#define COMPAQ_SMART_MAJOR3 107
#define COMPAQ_SMART_MAJOR4 108
#define COMPAQ_SMART_MAJOR5 109
#define COMPAQ_SMART_MAJOR6 110
#define COMPAQ_SMART_MAJOR7 111
#define DAC960_MAJOR 48
#define ATARAID_MAJOR 114
#define I2O_MAJOR1 80
#define I2O_MAJOR2 81
#define I2O_MAJOR3 82
#define I2O_MAJOR4 83
#define I2O_MAJOR5 84
#define I2O_MAJOR6 85
#define I2O_MAJOR7 86
#define I2O_MAJOR8 87
#define UBD_MAJOR 98
#define DASD_MAJOR 94
#define VIODASD_MAJOR 112
#define SX8_MAJOR1 160
#define SX8_MAJOR2 161
#define SCSI_BLK_MAJOR(M) ( \
(M) == SCSI_DISK0_MAJOR \
|| (M) == SCSI_CDROM_MAJOR \
|| ((M) >= SCSI_DISK1_MAJOR && (M) <= SCSI_DISK7_MAJOR))
static char* _device_get_part_path (PedDevice* dev, int num);
static int _partition_is_mounted_by_path (const char* path);
static int
_is_ide_major (int major)
{
switch (major) {
case IDE0_MAJOR:
case IDE1_MAJOR:
case IDE2_MAJOR:
case IDE3_MAJOR:
case IDE4_MAJOR:
case IDE5_MAJOR:
return 1;
default:
return 0;
}
}
static int
_is_cpqarray_major (int major)
{
return ((COMPAQ_SMART2_MAJOR <= major && major <= COMPAQ_SMART2_MAJOR7)
|| (COMPAQ_SMART_MAJOR <= major && major <= COMPAQ_SMART_MAJOR7));
}
static int
_is_i2o_major (int major)
{
return (I2O_MAJOR1 <= major && major <= I2O_MAJOR8);
}
static int
_is_sx8_major (int major)
{
return (SX8_MAJOR1 <= major && major <= SX8_MAJOR2);
}
#ifdef ENABLE_DEVICE_MAPPER
static int
readFD (int fd, char **buf)
{
char* p;
size_t size = PROC_DEVICES_BUFSIZ;
int s, filesize = 0;
*buf = malloc (size * sizeof (char));
if (*buf == 0) {
return -1;
}
do {
p = &(*buf) [filesize];
s = read (fd, p, PROC_DEVICES_BUFSIZ);
/* exit if there is an error or EOF is reached */
if (s <= 0)
break;
filesize += s;
size += s;
*buf = realloc (*buf, size);
} while (1);
if (filesize == 0 && s < 0) {
free (*buf);
*buf = NULL;
return -1;
} else {
/* there is always some excess memory left unused */
*buf = realloc (*buf, filesize+1);
(*buf)[filesize] = '\0';
}
return filesize;
}
static int
_is_dm_major (int major)
{
int fd;
char* buf = NULL;
char* line;
char* end;
int bd = 0;
char c;
fd = open ("/proc/devices", O_RDONLY);
if (fd < 0)
return 0;
if (readFD(fd, &buf) < 0) {
close(fd);
return 0;
}
line = buf;
end = strchr(line, '\n');
while (end) {
char *name;
int maj;
c = *end;
*end = '\0';
if (!bd) {
if (!strncmp(line, "Block devices:", 14))
bd = 1;
goto next;
}
name = strrchr(line, ' ');
if (!name || strcmp(name+1, "device-mapper"))
goto next;
maj = strtol(line, &name, 10);
if (maj == major) {
free(buf);
close(fd);
return 1;
}
next:
*end = c;
line = end+1;
end = strchr(line, '\n');
}
free(buf);
close(fd);
return 0;
}
#endif
static int
_device_stat (PedDevice* dev, struct stat * dev_stat)
{
PED_ASSERT (dev != NULL, return 0);
PED_ASSERT (!dev->external_mode, return 0);
while (1) {
if (!stat (dev->path, dev_stat)) {
return 1;
} else {
if (ped_exception_throw (
PED_EXCEPTION_ERROR,
PED_EXCEPTION_RETRY_CANCEL,
_("Could not stat device %s - %s."),
dev->path,
strerror (errno))
!= PED_EXCEPTION_RETRY)
return 0;
}
}
}
static int
_device_probe_type (PedDevice* dev)
{
struct stat dev_stat;
int dev_major;
int dev_minor;
if (!_device_stat (dev, &dev_stat))
return 0;
if (!S_ISBLK(dev_stat.st_mode)) {
dev->type = PED_DEVICE_FILE;
return 1;
}
dev_major = major (dev_stat.st_rdev);
dev_minor = minor (dev_stat.st_rdev);
if (SCSI_BLK_MAJOR (dev_major) && (dev_minor % 0x10 == 0)) {
dev->type = PED_DEVICE_SCSI;
} else if (_is_ide_major (dev_major) && (dev_minor % 0x40 == 0)) {
dev->type = PED_DEVICE_IDE;
} else if (dev_major == DAC960_MAJOR && (dev_minor % 0x8 == 0)) {
dev->type = PED_DEVICE_DAC960;
} else if (dev_major == ATARAID_MAJOR && (dev_minor % 0x10 == 0)) {
dev->type = PED_DEVICE_ATARAID;
} else if (dev_major == DASD_MAJOR && (dev_minor % 0x4 == 0)) {
dev->type = PED_DEVICE_DASD;
} else if (dev_major == VIODASD_MAJOR && (dev_minor % 0x8 == 0)) {
dev->type = PED_DEVICE_VIODASD;
} else if (_is_sx8_major(dev_major) && (dev_minor % 0x20 == 0)) {
dev->type = PED_DEVICE_SX8;
} else if (_is_i2o_major (dev_major) && (dev_minor % 0x10 == 0)) {
dev->type = PED_DEVICE_I2O;
} else if (_is_cpqarray_major (dev_major) && (dev_minor % 0x10 == 0)) {
dev->type = PED_DEVICE_CPQARRAY;
} else if (dev_major == UBD_MAJOR && (dev_minor % 0x10 == 0)) {
dev->type = PED_DEVICE_UBD;
#ifdef ENABLE_DEVICE_MAPPER
} else if (_is_dm_major(dev_major)) {
dev->type = PED_DEVICE_DM;
#endif
} else {
dev->type = PED_DEVICE_UNKNOWN;
}
return 1;
}
static int
_get_linux_version ()
{
static int kver = -1;
struct utsname uts;
int major;
int minor;
int teeny;
if (kver != -1)
return kver;
if (uname (&uts))
return kver = 0;
if (sscanf (uts.release, "%u.%u.%u", &major, &minor, &teeny) != 3)
return kver = 0;
return kver = KERNEL_VERSION (major, minor, teeny);
}
static int
_have_devfs ()
{
static int have_devfs = -1;
struct stat sb;
if (have_devfs != -1)
return have_devfs;
/* the presence of /dev/.devfsd implies that DevFS is active */
if (stat("/dev/.devfsd", &sb) < 0)
return have_devfs = 0;
return have_devfs = S_ISCHR(sb.st_mode) ? 1 : 0;
}
static void
_device_set_sector_size (PedDevice* dev)
{
LinuxSpecific* arch_specific = LINUX_SPECIFIC (dev);
int sector_size;
dev->sector_size = PED_SECTOR_SIZE_DEFAULT;
dev->phys_sector_size = PED_SECTOR_SIZE_DEFAULT;
PED_ASSERT (dev->open_count, return);
if (_get_linux_version() < KERNEL_VERSION (2,3,0)) {
dev->sector_size = PED_SECTOR_SIZE_DEFAULT;
return;
}
if (ioctl (arch_specific->fd, BLKSSZGET, &sector_size)) {
ped_exception_throw (
PED_EXCEPTION_WARNING,
PED_EXCEPTION_OK,
_("Could not determine sector size for %s: %s.\n"
"Using the default sector size (%lld)."),
dev->path, strerror (errno), PED_SECTOR_SIZE_DEFAULT);
} else {
dev->sector_size = (long long)sector_size;
}
/* Return PED_SECTOR_SIZE_DEFAULT for DASDs. */
if (dev->type == PED_DEVICE_DASD) {
dev->sector_size = PED_SECTOR_SIZE_DEFAULT;
}
if (dev->sector_size != PED_SECTOR_SIZE_DEFAULT) {
ped_exception_throw (
PED_EXCEPTION_WARNING,
PED_EXCEPTION_OK,
_("Device %s has a logical sector size of %lld. Not "
"all parts of GNU Parted support this at the moment, "
"and the working code is HIGHLY EXPERIMENTAL.\n"),
dev->path, dev->sector_size);
}
}
static int
_kernel_has_blkgetsize64(void)
{
int version = _get_linux_version();
if (version >= KERNEL_VERSION (2,5,4)) return 1;
if (version < KERNEL_VERSION (2,5,0) &&
version >= KERNEL_VERSION (2,4,18)) return 1;
return 0;
}
/* TODO: do a binary search if BLKGETSIZE doesn't work?! */
static PedSector
_device_get_length (PedDevice* dev)
{
unsigned long size;
LinuxSpecific* arch_specific = LINUX_SPECIFIC (dev);
uint64_t bytes=0;
PED_ASSERT (dev->open_count > 0, return 0);
PED_ASSERT (dev->sector_size % PED_SECTOR_SIZE_DEFAULT == 0, return 0);
if (_kernel_has_blkgetsize64()) {
if (ioctl(arch_specific->fd, BLKGETSIZE64, &bytes) == 0) {
return bytes / dev->sector_size;
}
}
if (ioctl (arch_specific->fd, BLKGETSIZE, &size)) {
ped_exception_throw (
PED_EXCEPTION_BUG,
PED_EXCEPTION_CANCEL,
_("Unable to determine the size of %s (%s)."),
dev->path,
strerror (errno));
return 0;
}
return size;
}
static int
_device_probe_geometry (PedDevice* dev)
{
LinuxSpecific* arch_specific = LINUX_SPECIFIC (dev);
struct stat dev_stat;
struct hd_geometry geometry;
if (!_device_stat (dev, &dev_stat))
return 0;
PED_ASSERT (S_ISBLK (dev_stat.st_mode), return 0);
_device_set_sector_size (dev);
dev->length = _device_get_length (dev);
if (!dev->length)
return 0;
/* The GETGEO ioctl is no longer useful (as of linux 2.6.x). We could
* still use it in 2.4.x, but this is contentious. Perhaps we should
* move to EDD. */
dev->bios_geom.sectors = 63;
dev->bios_geom.heads = 255;
dev->bios_geom.cylinders
= dev->length / (63 * 255);
/* FIXME: what should we put here? (TODO: discuss on linux-kernel) */
if (!ioctl (arch_specific->fd, HDIO_GETGEO, &geometry)
&& geometry.sectors && geometry.heads) {
dev->hw_geom.sectors = geometry.sectors;
dev->hw_geom.heads = geometry.heads;
dev->hw_geom.cylinders
= dev->length / (dev->hw_geom.heads
* dev->hw_geom.sectors);
} else {
dev->hw_geom = dev->bios_geom;
}
return 1;
}
static char*
strip_name(char* str)
{
int i;
int end = 0;
for (i = 0; str[i] != 0; i++) {
if (!isspace (str[i])
|| (isspace (str[i]) && !isspace (str[i+1]) && str[i+1])) {
str [end] = str[i];
end++;
}
}
str[end] = 0;
return strdup (str);
}
static int
init_ide (PedDevice* dev)
{
LinuxSpecific* arch_specific = LINUX_SPECIFIC (dev);
struct stat dev_stat;
int dev_major;
struct hd_driveid hdi;
PedExceptionOption ex_status;
char hdi_buf[41];
int sector_multiplier = 0;
if (!_device_stat (dev, &dev_stat))
goto error;
dev_major = major (dev_stat.st_rdev);
if (!ped_device_open (dev))
goto error;
if (ioctl (arch_specific->fd, HDIO_GET_IDENTITY, &hdi)) {
ex_status = ped_exception_throw (
PED_EXCEPTION_WARNING,
PED_EXCEPTION_IGNORE_CANCEL,
_("Could not get identity of device %s - %s"),
dev->path, strerror (errno));
switch (ex_status) {
case PED_EXCEPTION_CANCEL:
goto error_close_dev;
case PED_EXCEPTION_UNHANDLED:
ped_exception_catch ();
case PED_EXCEPTION_IGNORE:
dev->model = strdup(_("Generic IDE"));
break;
default:
PED_ASSERT (0, (void) 0);
break;
}
} else {
/* hdi.model is not guaranteed to be NULL terminated */
memcpy (hdi_buf, hdi.model, 40);
hdi_buf[40] = '\0';
dev->model = strip_name (hdi_buf);
if (!hdi.ata7_sectinfo.valid1 && hdi.ata7_sectinfo.valid2)
sector_multiplier = hdi.ata7_sectinfo.multiplier;
else
sector_multiplier = 1;
if (sector_multiplier != 1) {
ex_status = ped_exception_throw (
PED_EXCEPTION_WARNING,
PED_EXCEPTION_IGNORE_CANCEL,
_("Device %s has multiple (%d) logical sectors "
"per physical sector.\n"
"GNU Parted supports this EXPERIMENTALLY for "
"some special disk label/file system "
"combinations, e.g. GPT and ext2/3.\n"
"Please consult the web site for up-to-date "
"information."),
dev->path, sector_multiplier);
switch (ex_status) {
case PED_EXCEPTION_CANCEL:
goto error_close_dev;
case PED_EXCEPTION_UNHANDLED:
ped_exception_catch ();
case PED_EXCEPTION_IGNORE:
break;
default:
PED_ASSERT (0, (void) 0);
break;
}
}
/* XXX sector_size has not been set yet! */
/* dev->phys_sector_size = dev->sector_size
* sector_multiplier;*/
dev->phys_sector_size = PED_SECTOR_SIZE_DEFAULT;
}
if (!_device_probe_geometry (dev))
goto error_close_dev;
ped_device_close (dev);
return 1;
error_close_dev:
ped_device_close (dev);
error:
return 0;
}
/* This function reads the /sys entry named "file" for device "dev". */
static char *
read_device_sysfs_file (PedDevice *dev, const char *file)
{
FILE *f;
char name_buf[128];
char buf[256];
snprintf (name_buf, 127, "/sys/block/%s/device/%s",
basename (dev->path), file);
if ((f = fopen (name_buf, "r")) == NULL)
return NULL;
if (fgets (buf, 255, f) == NULL)
return NULL;
fclose (f);
return strip_name (buf);
}
/* This function sends a query to a SCSI device for vendor and product
* information. It uses the deprecated SCSI_IOCTL_SEND_COMMAND to
* issue this query.
*/
static int
scsi_query_product_info (PedDevice* dev, char **vendor, char **product)
{
/* The following are defined by the SCSI-2 specification. */
typedef struct _scsi_inquiry_cmd
{
uint8_t op;
uint8_t lun; /* bits 5-7 denote the LUN */
uint8_t page_code;
uint8_t reserved;
uint8_t alloc_length;
uint8_t control;
} __attribute__((packed)) scsi_inquiry_cmd_t;
typedef struct _scsi_inquiry_data
{
uint8_t peripheral_info;
uint8_t device_info;
uint8_t version_info;
uint8_t _field1;
uint8_t additional_length;
uint8_t _reserved1;
uint8_t _reserved2;
uint8_t _field2;
uint8_t vendor_id[8];
uint8_t product_id[16];
uint8_t product_revision[4];
uint8_t vendor_specific[20];
uint8_t _reserved3[40];
} __attribute__((packed)) scsi_inquiry_data_t;
struct scsi_arg
{
unsigned int inlen;
unsigned int outlen;
union arg_data
{
scsi_inquiry_data_t out;
scsi_inquiry_cmd_t in;
} data;
} arg;
LinuxSpecific* arch_specific = LINUX_SPECIFIC (dev);
char buf[32];
*vendor = NULL;
*product = NULL;
memset (&arg, 0x00, sizeof(struct scsi_arg));
arg.inlen = 0;
arg.outlen = sizeof(scsi_inquiry_data_t);
arg.data.in.op = INQUIRY;
arg.data.in.lun = dev->host << 5;
arg.data.in.alloc_length = sizeof(scsi_inquiry_data_t);
arg.data.in.page_code = 0;
arg.data.in.reserved = 0;
arg.data.in.control = 0;
if (ioctl (arch_specific->fd, SCSI_IOCTL_SEND_COMMAND, &arg) < 0)
return 0;
memcpy (buf, arg.data.out.vendor_id, 8);
buf[8] = '\0';
*vendor = strip_name (buf);
memcpy (buf, arg.data.out.product_id, 16);
buf[16] = '\0';
*product = strip_name (buf);
return 1;
}
/* This function provides the vendor and product name for a SCSI device.
* It supports both the modern /sys interface and direct queries
* via the deprecated ioctl, SCSI_IOCTL_SEND_COMMAND.
*/
static int
scsi_get_product_info (PedDevice* dev, char **vendor, char **product)
{
*vendor = read_device_sysfs_file (dev, "vendor");
*product = read_device_sysfs_file (dev, "model");
if (*vendor && *product)
return 1;
return scsi_query_product_info (dev, vendor, product);
}
static int
init_scsi (PedDevice* dev)
{
struct scsi_idlun
{
uint32_t dev_id;
uint32_t host_unique_id;
} idlun;
LinuxSpecific* arch_specific = LINUX_SPECIFIC (dev);
char* vendor;
char* product;
if (!ped_device_open (dev))
goto error;
if (ioctl (arch_specific->fd, SCSI_IOCTL_GET_IDLUN, &idlun) < 0) {
dev->host = 0;
dev->did = 0;
if (ped_exception_throw (
PED_EXCEPTION_ERROR, PED_EXCEPTION_IGNORE_CANCEL,
_("Error initialising SCSI device %s - %s"),
dev->path, strerror (errno))
!= PED_EXCEPTION_IGNORE)
goto error_close_dev;
if (!_device_probe_geometry (dev))
goto error_close_dev;
ped_device_close (dev);
return 1;
}
dev->host = idlun.host_unique_id;
dev->did = idlun.dev_id;
dev->model = (char*) ped_malloc (8 + 16 + 2);
if (!dev->model)
goto error_close_dev;
if (scsi_get_product_info (dev, &vendor, &product)) {
sprintf (dev->model, "%.8s %.16s", vendor, product);
ped_free (vendor);
ped_free (product);
} else {
strcpy (dev->model, "Generic SCSI");
}
if (!_device_probe_geometry (dev))
goto error_close_dev;
ped_device_close (dev);
return 1;
error_close_dev:
ped_device_close (dev);
error:
return 0;
}
static int
init_file (PedDevice* dev)
{
struct stat dev_stat;
if (!_device_stat (dev, &dev_stat))
goto error;
if (!ped_device_open (dev))
goto error;
if (S_ISBLK(dev_stat.st_mode))
dev->length = _device_get_length (dev);
else
dev->length = dev_stat.st_size / 512;
if (dev->length <= 0) {
ped_exception_throw (
PED_EXCEPTION_ERROR,
PED_EXCEPTION_CANCEL,
_("The device %s has zero length, and can't possibly "
"store a file system or partition table. Perhaps "
"you selected the wrong device?"),
dev->path);
goto error_close_dev;
}
ped_device_close (dev);
dev->bios_geom.cylinders = dev->length / 4 / 32;
dev->bios_geom.heads = 4;
dev->bios_geom.sectors = 32;
dev->hw_geom = dev->bios_geom;
dev->sector_size = PED_SECTOR_SIZE_DEFAULT;
dev->phys_sector_size = PED_SECTOR_SIZE_DEFAULT;
dev->model = strdup ("");
return 1;
error_close_dev:
ped_device_close (dev);
error:
return 0;
}
static int
init_dasd (PedDevice* dev, char* model_name)
{
struct stat dev_stat;
struct hd_geometry geo;
char *errstr = 0;
if (!_device_stat (dev, &dev_stat))
goto error;
if (!ped_device_open (dev))
goto error;
LinuxSpecific* arch_specific = LINUX_SPECIFIC (dev);
PED_ASSERT (S_ISBLK (dev_stat.st_mode), return 0);
_device_set_sector_size (dev);
if (!dev->sector_size)
goto error_close_dev;
dev->length = _device_get_length (dev);
if (!dev->length)
goto error_close_dev;
if (!ioctl (arch_specific->fd, HDIO_GETGEO, &geo)) {
dev->hw_geom.sectors = geo.sectors;
dev->hw_geom.heads = geo.heads;
dev->hw_geom.cylinders = dev->length
/ (dev->hw_geom.heads * dev->hw_geom.sectors)
/ (dev->sector_size / PED_SECTOR_SIZE_DEFAULT);
dev->bios_geom = dev->hw_geom;
} else {
dev->bios_geom.sectors = 12;
dev->bios_geom.heads = 15;
dev->bios_geom.cylinders = dev->length
/ (dev->hw_geom.heads * dev->hw_geom.sectors)
/ (dev->sector_size / PED_SECTOR_SIZE_DEFAULT);
dev->hw_geom = dev->bios_geom;
}
dev->model = strdup (model_name);
ped_device_close (dev);
return 1;
ped_exception_throw ( PED_EXCEPTION_ERROR,
PED_EXCEPTION_IGNORE_CANCEL,
errstr );
error_close_dev:
ped_device_close (dev);
error:
return 0;
}
static int
init_generic (PedDevice* dev, char* model_name)
{
struct stat dev_stat;
PedExceptionOption ex_status;
if (!_device_stat (dev, &dev_stat))
goto error;
if (!ped_device_open (dev))
goto error;
ped_exception_fetch_all ();
if (_device_probe_geometry (dev)) {
ped_exception_leave_all ();
} else {
/* hack to allow use of files, for testing */
ped_exception_catch ();
ped_exception_leave_all ();
ex_status = ped_exception_throw (
PED_EXCEPTION_WARNING,
PED_EXCEPTION_IGNORE_CANCEL,
_("Unable to determine geometry of "
"file/device %s. You should not use Parted "
"unless you REALLY know what you're doing!"),
dev->path);
switch (ex_status) {
case PED_EXCEPTION_CANCEL:
goto error_close_dev;
case PED_EXCEPTION_UNHANDLED:
ped_exception_catch ();
case PED_EXCEPTION_IGNORE:
break;
default:
PED_ASSERT (0, (void) 0);
break;
}
/* what should we stick in here? */
dev->length = dev_stat.st_size / PED_SECTOR_SIZE_DEFAULT;
dev->bios_geom.cylinders = dev->length / 4 / 32;
dev->bios_geom.heads = 4;
dev->bios_geom.sectors = 32;
dev->sector_size = PED_SECTOR_SIZE_DEFAULT;
dev->phys_sector_size = PED_SECTOR_SIZE_DEFAULT;
}
dev->model = strdup (model_name);
ped_device_close (dev);
return 1;
error_close_dev:
ped_device_close (dev);
error:
return 0;
}
static PedDevice*
linux_new (const char* path)
{
PedDevice* dev;
PED_ASSERT (path != NULL, return NULL);
dev = (PedDevice*) ped_malloc (sizeof (PedDevice));
if (!dev)
goto error;
dev->path = strdup (path);
if (!dev->path)
goto error_free_dev;
dev->arch_specific
= (LinuxSpecific*) ped_malloc (sizeof (LinuxSpecific));
if (!dev->arch_specific)
goto error_free_path;
dev->open_count = 0;
dev->read_only = 0;
dev->external_mode = 0;
dev->dirty = 0;
dev->boot_dirty = 0;
if (!_device_probe_type (dev))
goto error_free_arch_specific;
switch (dev->type) {
case PED_DEVICE_IDE:
if (!init_ide (dev))
goto error_free_arch_specific;
break;
case PED_DEVICE_SCSI:
if (!init_scsi (dev))
goto error_free_arch_specific;
break;
case PED_DEVICE_DAC960:
if (!init_generic (dev, _("DAC960 RAID controller")))
goto error_free_arch_specific;
break;
case PED_DEVICE_SX8:
if (!init_generic (dev, _("Promise SX8 SATA Device")))
goto error_free_arch_specific;
break;
case PED_DEVICE_DASD:
if (!init_dasd (dev, _("IBM S390 DASD drive")))
goto error_free_arch_specific;
break;
case PED_DEVICE_VIODASD:
if (!init_generic (dev, _("IBM iSeries Virtual DASD")))
goto error_free_arch_specific;
break;
case PED_DEVICE_CPQARRAY:
if (!init_generic (dev, _("Compaq Smart Array")))
goto error_free_arch_specific;
break;
case PED_DEVICE_ATARAID:
if (!init_generic (dev, _("ATARAID Controller")))
goto error_free_arch_specific;
break;
case PED_DEVICE_I2O:
if (!init_generic (dev, _("I2O Controller")))
goto error_free_arch_specific;
break;
case PED_DEVICE_UBD:
if (!init_generic (dev, _("User-Mode Linux UBD")))
goto error_free_arch_specific;
break;
case PED_DEVICE_FILE:
if (!init_file (dev))
goto error_free_arch_specific;
break;
#ifdef ENABLE_DEVICE_MAPPER
case PED_DEVICE_DM:
if (!init_generic (dev, _("Linux device-mapper")))
goto error_free_arch_specific;
break;
#endif
case PED_DEVICE_UNKNOWN:
if (!init_generic (dev, _("Unknown")))
goto error_free_arch_specific;
break;
default:
ped_exception_throw (PED_EXCEPTION_NO_FEATURE,
PED_EXCEPTION_CANCEL,
_("ped_device_new() Unsupported device type"));
goto error_free_arch_specific;
}
return dev;
error_free_arch_specific:
ped_free (dev->arch_specific);
error_free_path:
ped_free (dev->path);
error_free_dev:
ped_free (dev);
error:
return NULL;
}
static void
linux_destroy (PedDevice* dev)
{
ped_free (dev->arch_specific);
ped_free (dev->path);
ped_free (dev->model);
ped_free (dev);
}
static int
linux_is_busy (PedDevice* dev)
{
int i;
char* part_name;
if (_partition_is_mounted_by_path (dev->path))
return 1;
for (i = 0; i < 32; i++) {
int status;
part_name = _device_get_part_path (dev, i);
if (!part_name)
return 1;
status = _partition_is_mounted_by_path (part_name);
ped_free (part_name);
if (status)
return 1;
}
return 0;
}
/* we need to flush the master device, and all the partition devices,
* because there is no coherency between the caches.
* We should only flush unmounted partition devices, because:
* - there is never a need to flush them (we're not doing IO there)
* - flushing a device that is mounted causes unnecessary IO, and can
* even screw journaling & friends up. Even cause oopsen!
*/
static void
_flush_cache (PedDevice* dev)
{
LinuxSpecific* arch_specific = LINUX_SPECIFIC (dev);
int i;
if (dev->read_only)
return;
dev->dirty = 0;
ioctl (arch_specific->fd, BLKFLSBUF);
for (i = 1; i < 16; i++) {
char* name;
int fd;
name = _device_get_part_path (dev, i);
if (!name)
break;
if (!_partition_is_mounted_by_path (name)) {
fd = open (name, WR_MODE, 0);
if (fd > 0) {
ioctl (fd, BLKFLSBUF);
close (fd);
}
}
ped_free (name);
}
}
static int
linux_open (PedDevice* dev)
{
LinuxSpecific* arch_specific = LINUX_SPECIFIC (dev);
retry:
arch_specific->fd = open (dev->path, RW_MODE);
if (arch_specific->fd == -1) {
char* rw_error_msg = strerror (errno);
arch_specific->fd = open (dev->path, RD_MODE);
if (arch_specific->fd == -1) {
if (ped_exception_throw (
PED_EXCEPTION_ERROR,
PED_EXCEPTION_RETRY_CANCEL,
_("Error opening %s: %s"),
dev->path, strerror (errno))
!= PED_EXCEPTION_RETRY) {
return 0;
} else {
goto retry;
}
} else {
ped_exception_throw (
PED_EXCEPTION_WARNING,
PED_EXCEPTION_OK,
_("Unable to open %s read-write (%s). %s has "
"been opened read-only."),
dev->path, rw_error_msg, dev->path);
dev->read_only = 1;
}
} else {
dev->read_only = 0;
}
_flush_cache (dev);
return 1;
}
static int
linux_refresh_open (PedDevice* dev)
{
return 1;
}
static int
linux_close (PedDevice* dev)
{
LinuxSpecific* arch_specific = LINUX_SPECIFIC (dev);
if (dev->dirty)
_flush_cache (dev);
close (arch_specific->fd);
return 1;
}
static int
linux_refresh_close (PedDevice* dev)
{
if (dev->dirty)
_flush_cache (dev);
return 1;
}
#if SIZEOF_OFF_T < 8
static _syscall5(int,_llseek,
unsigned int, fd,
unsigned long, offset_high,
unsigned long, offset_low,
loff_t*, result,
unsigned int, origin)
loff_t
llseek (unsigned int fd, loff_t offset, unsigned int whence)
{
loff_t result;
int retval;
retval = _llseek(fd,
((unsigned long long)offset) >> 32,
((unsigned long long)offset) & 0xffffffff,
&result,
whence);
return (retval==-1 ? (loff_t) retval : result);
}
#endif /* SIZEOF_OFF_T < 8 */
static int
_device_seek (const PedDevice* dev, PedSector sector)
{
LinuxSpecific* arch_specific;
PED_ASSERT (dev->sector_size % PED_SECTOR_SIZE_DEFAULT == 0, return 0);
PED_ASSERT (dev != NULL, return 0);
PED_ASSERT (!dev->external_mode, return 0);
arch_specific = LINUX_SPECIFIC (dev);
#if SIZEOF_OFF_T < 8
if (sizeof (off_t) < 8) {
loff_t pos = (loff_t)(sector * dev->sector_size);
return llseek (arch_specific->fd, pos, SEEK_SET) == pos;
} else
#endif
{
off_t pos = sector * dev->sector_size;
return lseek (arch_specific->fd, pos, SEEK_SET) == pos;
}
}
static int
_read_lastoddsector (const PedDevice* dev, void* buffer)
{
LinuxSpecific* arch_specific;
struct blkdev_ioctl_param ioctl_param;
PED_ASSERT(dev != NULL, return 0);
PED_ASSERT(buffer != NULL, return 0);
arch_specific = LINUX_SPECIFIC (dev);
retry:
ioctl_param.block = 0; /* read the last sector */
ioctl_param.content_length = dev->sector_size;
ioctl_param.block_contents = buffer;
if (ioctl(arch_specific->fd, BLKGETLASTSECT, &ioctl_param) == -1) {
PedExceptionOption opt;
opt = ped_exception_throw (
PED_EXCEPTION_ERROR,
PED_EXCEPTION_RETRY_IGNORE_CANCEL,
_("%s during read on %s"),
strerror (errno), dev->path);
if (opt == PED_EXCEPTION_CANCEL)
return 0;
if (opt == PED_EXCEPTION_RETRY)
goto retry;
}
return 1;
}
static int
linux_read (const PedDevice* dev, void* buffer, PedSector start,
PedSector count)
{
LinuxSpecific* arch_specific = LINUX_SPECIFIC (dev);
int status;
PedExceptionOption ex_status;
void* diobuf;
PED_ASSERT (dev->sector_size % PED_SECTOR_SIZE_DEFAULT == 0, return 0);
if (_get_linux_version() < KERNEL_VERSION (2,6,0)) {
/* Kludge. This is necessary to read/write the last
block of an odd-sized disk, until Linux 2.5.x kernel fixes.
*/
if (dev->type != PED_DEVICE_FILE && (dev->length & 1)
&& start + count - 1 == dev->length - 1)
return ped_device_read (dev, buffer, start, count - 1)
&& _read_lastoddsector (
dev, (char *) buffer + (count-1) * 512);
}
while (1) {
if (_device_seek (dev, start))
break;
ex_status = ped_exception_throw (
PED_EXCEPTION_ERROR,
PED_EXCEPTION_RETRY_IGNORE_CANCEL,
_("%s during seek for read on %s"),
strerror (errno), dev->path);
switch (ex_status) {
case PED_EXCEPTION_IGNORE:
return 1;
case PED_EXCEPTION_RETRY:
break;
case PED_EXCEPTION_UNHANDLED:
ped_exception_catch ();
case PED_EXCEPTION_CANCEL:
return 0;
default:
PED_ASSERT (0, (void) 0);
break;
}
}
size_t read_length = count * dev->sector_size;
if (posix_memalign (&diobuf, dev->sector_size, read_length) != 0)
return 0;
while (1) {
status = read (arch_specific->fd, diobuf, read_length);
if (status > 0)
memcpy(buffer, diobuf, status);
if (status == count * dev->sector_size) break;
if (status > 0) {
read_length -= status;
buffer = (char *) buffer + status;
continue;
}
ex_status = ped_exception_throw (
PED_EXCEPTION_ERROR,
PED_EXCEPTION_RETRY_IGNORE_CANCEL,
_("%s during read on %s"),
strerror (errno),
dev->path);
switch (ex_status) {
case PED_EXCEPTION_IGNORE:
free(diobuf);
return 1;
case PED_EXCEPTION_RETRY:
break;
case PED_EXCEPTION_UNHANDLED:
ped_exception_catch ();
case PED_EXCEPTION_CANCEL:
free(diobuf);
return 0;
default:
PED_ASSERT (0, (void) 0);
break;
}
}
free(diobuf);
return 1;
}
static int
_write_lastoddsector (PedDevice* dev, const void* buffer)
{
LinuxSpecific* arch_specific;
struct blkdev_ioctl_param ioctl_param;
PED_ASSERT(dev != NULL, return 0);
PED_ASSERT(buffer != NULL, return 0);
arch_specific = LINUX_SPECIFIC (dev);
retry:
ioctl_param.block = 0; /* write the last sector */
ioctl_param.content_length = dev->sector_size;
ioctl_param.block_contents = (void*) buffer;
if (ioctl(arch_specific->fd, BLKSETLASTSECT, &ioctl_param) == -1) {
PedExceptionOption opt;
opt = ped_exception_throw (
PED_EXCEPTION_ERROR,
PED_EXCEPTION_RETRY_IGNORE_CANCEL,
_("%s during write on %s"),
strerror (errno), dev->path);
if (opt == PED_EXCEPTION_CANCEL)
return 0;
if (opt == PED_EXCEPTION_RETRY)
goto retry;
}
return 1;
}
static int
linux_write (PedDevice* dev, const void* buffer, PedSector start,
PedSector count)
{
LinuxSpecific* arch_specific = LINUX_SPECIFIC (dev);
int status;
PedExceptionOption ex_status;
size_t write_length = count * dev->sector_size;
void* diobuf;
void* diobuf_start;
PED_ASSERT(dev->sector_size % PED_SECTOR_SIZE_DEFAULT == 0, return 0);
if (dev->read_only) {
if (ped_exception_throw (
PED_EXCEPTION_ERROR,
PED_EXCEPTION_IGNORE_CANCEL,
_("Can't write to %s, because it is opened read-only."),
dev->path)
!= PED_EXCEPTION_IGNORE)
return 0;
else
return 1;
}
if (_get_linux_version() < KERNEL_VERSION (2,6,0)) {
/* Kludge. This is necessary to read/write the last
block of an odd-sized disk, until Linux 2.5.x kernel fixes.
*/
if (dev->type != PED_DEVICE_FILE && (dev->length & 1)
&& start + count - 1 == dev->length - 1)
return ped_device_write (dev, buffer, start, count - 1)
&& _write_lastoddsector (
dev, (char*) buffer + (count-1) * 512);
}
while (1) {
if (_device_seek (dev, start))
break;
ex_status = ped_exception_throw (
PED_EXCEPTION_ERROR, PED_EXCEPTION_RETRY_IGNORE_CANCEL,
_("%s during seek for write on %s"),
strerror (errno), dev->path);
switch (ex_status) {
case PED_EXCEPTION_IGNORE:
return 1;
case PED_EXCEPTION_RETRY:
break;
case PED_EXCEPTION_UNHANDLED:
ped_exception_catch ();
case PED_EXCEPTION_CANCEL:
return 0;
default:
PED_ASSERT (0, (void) 0);
break;
}
}
#ifdef READ_ONLY
printf ("ped_device_write (\"%s\", %p, %d, %d)\n",
dev->path, buffer, (int) start, (int) count);
#else
dev->dirty = 1;
if (posix_memalign(&diobuf, PED_SECTOR_SIZE_DEFAULT,
count * PED_SECTOR_SIZE_DEFAULT) != 0)
return 0;
memcpy(diobuf, buffer, count * PED_SECTOR_SIZE_DEFAULT);
diobuf_start = diobuf;
while (1) {
status = write (arch_specific->fd, diobuf, write_length);
if (status == count * dev->sector_size) break;
if (status > 0) {
write_length -= status;
diobuf = (char *) diobuf + status;
continue;
}
ex_status = ped_exception_throw (
PED_EXCEPTION_ERROR,
PED_EXCEPTION_RETRY_IGNORE_CANCEL,
_("%s during write on %s"),
strerror (errno), dev->path);
switch (ex_status) {
case PED_EXCEPTION_IGNORE:
free(diobuf_start);
return 1;
case PED_EXCEPTION_RETRY:
break;
case PED_EXCEPTION_UNHANDLED:
ped_exception_catch ();
case PED_EXCEPTION_CANCEL:
free(diobuf_start);
return 0;
default:
PED_ASSERT (0, (void) 0);
break;
}
}
free(diobuf_start);
#endif /* !READ_ONLY */
return 1;
}
/* returns the number of sectors that are ok.
*/
static PedSector
linux_check (PedDevice* dev, void* buffer, PedSector start, PedSector count)
{
LinuxSpecific* arch_specific = LINUX_SPECIFIC (dev);
PedSector done = 0;
int status;
void* diobuf;
PED_ASSERT(dev != NULL, return 0);
if (!_device_seek (dev, start))
return 0;
if (posix_memalign(&diobuf, PED_SECTOR_SIZE_DEFAULT,
count * PED_SECTOR_SIZE_DEFAULT) != 0)
return 0;
for (done = 0; done < count; done += status / dev->sector_size) {
status = read (arch_specific->fd, diobuf,
(size_t) ((count-done) * dev->sector_size));
if (status > 0)
memcpy(buffer, diobuf, status);
if (status < 0)
break;
}
free(diobuf);
return done;
}
static int
_do_fsync (PedDevice* dev)
{
LinuxSpecific* arch_specific = LINUX_SPECIFIC (dev);
int status;
PedExceptionOption ex_status;
while (1) {
status = fsync (arch_specific->fd);
if (status >= 0) break;
ex_status = ped_exception_throw (
PED_EXCEPTION_ERROR,
PED_EXCEPTION_RETRY_IGNORE_CANCEL,
_("%s during write on %s"),
strerror (errno), dev->path);
switch (ex_status) {
case PED_EXCEPTION_IGNORE:
return 1;
case PED_EXCEPTION_RETRY:
break;
case PED_EXCEPTION_UNHANDLED:
ped_exception_catch ();
case PED_EXCEPTION_CANCEL:
return 0;
default:
PED_ASSERT (0, (void) 0);
break;
}
}
return 1;
}
static int
linux_sync (PedDevice* dev)
{
PED_ASSERT (dev != NULL, return 0);
PED_ASSERT (!dev->external_mode, return 0);
if (dev->read_only)
return 1;
if (!_do_fsync (dev))
return 0;
_flush_cache (dev);
return 1;
}
static int
linux_sync_fast (PedDevice* dev)
{
PED_ASSERT (dev != NULL, return 0);
PED_ASSERT (!dev->external_mode, return 0);
if (dev->read_only)
return 1;
if (!_do_fsync (dev))
return 0;
/* no cache flush... */
return 1;
}
static inline int
_compare_digit_state (char ch, int need_digit)
{
return !!isdigit (ch) == need_digit;
}
/* matches the regexp "[^0-9]+[0-9]+[^0-9]+[0-9]+$".
* Motivation: accept devices looking like /dev/rd/c0d0, but
* not looking like /dev/hda1 and /dev/rd/c0d0p1
*/
static int
_match_rd_device (const char* name)
{
const char* pos;
int state;
/* exclude directory names from test */
pos = strrchr(name, '/') ?: name;
/* states:
* 0 non-digits
* 1 digits
* 2 non-digits
* 3 digits
*/
for (state = 0; state < 4; state++) {
int want_digits = (state % 2 == 1);
do {
if (!*pos)
return 0;
if (!_compare_digit_state (*pos, want_digits))
return 0;
pos++;
} while (_compare_digit_state (*pos, want_digits));
}
return *pos == 0;
}
static int
_probe_proc_partitions ()
{
FILE* proc_part_file;
int major, minor, size;
char buf [512];
char part_name [256];
char dev_name [256];
proc_part_file = fopen ("/proc/partitions", "r");
if (!proc_part_file)
return 0;
if (fgets (buf, 256, proc_part_file) == NULL)
return 0;
if (fgets (buf, 256, proc_part_file) == NULL)
return 0;
while (fgets (buf, 512, proc_part_file)
&& sscanf (buf, "%d %d %d %255s", &major, &minor, &size,
part_name) == 4) {
/* Heuristic for telling partitions and devices apart
* Probably needs to be improved
*/
if (!_match_rd_device (part_name)
&& isdigit (part_name [strlen (part_name) - 1]))
continue;
strcpy (dev_name, "/dev/");
strcat (dev_name, part_name);
_ped_device_probe (dev_name);
}
fclose (proc_part_file);
return 1;
}
struct _entry {
const char *name;
size_t len;
};
static int
_skip_entry (const char *name)
{
struct _entry *i;
static struct _entry entries[] = {
{ ".", sizeof (".") - 1 },
{ "..", sizeof ("..") - 1 },
{ "dm-", sizeof ("dm-") - 1 },
{ "loop", sizeof ("loop") - 1 },
{ "ram", sizeof ("ram") - 1 },
{ 0, 0 },
};
for (i = entries; i->name != 0; i++) {
if (strncmp (name, i->name, i->len) == 0)
return 1;
}
return 0;
}
static int
_probe_sys_block ()
{
DIR *blockdir;
struct dirent *dirent;
char dev_name [256];
char *ptr;
if (!(blockdir = opendir ("/sys/block")))
return 0;
while ((dirent = readdir (blockdir))) {
if (_skip_entry (dirent->d_name))
continue;
if (strlen (dirent->d_name) > sizeof (dev_name) - 6)
continue; /* device name too long! */
strcpy (dev_name, "/dev/");
strcat (dev_name, dirent->d_name);
/* in /sys/block, '/'s are replaced with '!' or '.' */
for (ptr = dev_name; *ptr != '\0'; ptr++) {
if (*ptr == '!' || *ptr == '.')
*ptr = '/';
}
_ped_device_probe (dev_name);
}
closedir (blockdir);
return 1;
}
static int
_probe_standard_devices ()
{
_ped_device_probe ("/dev/hda");
_ped_device_probe ("/dev/hdb");
_ped_device_probe ("/dev/hdc");
_ped_device_probe ("/dev/hdd");
_ped_device_probe ("/dev/hde");
_ped_device_probe ("/dev/hdf");
_ped_device_probe ("/dev/hdg");
_ped_device_probe ("/dev/hdh");
_ped_device_probe ("/dev/sda");
_ped_device_probe ("/dev/sdb");
_ped_device_probe ("/dev/sdc");
_ped_device_probe ("/dev/sdd");
_ped_device_probe ("/dev/sde");
_ped_device_probe ("/dev/sdf");
return 1;
}
static void
linux_probe_all ()
{
/* we should probe the standard devs too, even with /proc/partitions,
* because /proc/partitions might return devfs stuff, and we might not
* have devfs available
*/
_probe_standard_devices ();
/* /sys/block is more reliable and consistent; fall back to using
* /proc/partitions if the former is unavailable, however.
*/
if (!_probe_sys_block ())
_probe_proc_partitions ();
}
static char*
_device_get_part_path (PedDevice* dev, int num)
{
int path_len = strlen (dev->path);
int result_len = path_len + 16;
char* result;
result = (char*) ped_malloc (result_len);
if (!result)
return NULL;
/* Check for devfs-style /disc => /partN transformation
unconditionally; the system might be using udev with devfs rules,
and if not the test is harmless. */
if (!strcmp (dev->path + path_len - 5, "/disc")) {
/* replace /disc with /path%d */
strcpy (result, dev->path);
snprintf (result + path_len - 5, 16, "/part%d", num);
} else if (dev->type == PED_DEVICE_DAC960
|| dev->type == PED_DEVICE_CPQARRAY
|| dev->type == PED_DEVICE_ATARAID
#ifdef ENABLE_DEVICE_MAPPER
|| dev->type == PED_DEVICE_DM
#endif
|| isdigit (dev->path[path_len - 1]))
snprintf (result, result_len, "%sp%d", dev->path, num);
else
snprintf (result, result_len, "%s%d", dev->path, num);
return result;
}
static char*
linux_partition_get_path (const PedPartition* part)
{
return _device_get_part_path (part->disk->dev, part->num);
}
static dev_t
_partition_get_part_dev (const PedPartition* part)
{
struct stat dev_stat;
int dev_major, dev_minor;
if (!_device_stat (part->disk->dev, &dev_stat))
return (dev_t)0;
dev_major = major (dev_stat.st_rdev);
dev_minor = minor (dev_stat.st_rdev);
return (dev_t)makedev (dev_major, dev_minor + part->num);
}
static int
_mount_table_search (const char* file_name, dev_t dev)
{
struct stat part_stat;
char line[512];
char part_name[512];
FILE* file;
int junk;
file = fopen (file_name, "r");
if (!file)
return 0;
while (fgets (line, 512, file)) {
junk = sscanf (line, "%s", part_name);
if (stat (part_name, &part_stat) == 0) {
if (part_stat.st_rdev == dev) {
fclose (file);
return 1;
}
}
}
fclose (file);
return 0;
}
static int
_partition_is_mounted_by_dev (dev_t dev)
{
return _mount_table_search( "/proc/mounts", dev)
|| _mount_table_search( "/proc/swaps", dev)
|| _mount_table_search( "/etc/mtab", dev);
}
static int
_partition_is_mounted_by_path (const char *path)
{
struct stat part_stat;
if (stat (path, &part_stat) != 0)
return 0;
if (!S_ISBLK(part_stat.st_mode))
return 0;
return _partition_is_mounted_by_dev (part_stat.st_rdev);
}
static int
_partition_is_mounted (const PedPartition *part)
{
dev_t dev;
if (!ped_partition_is_active (part))
return 0;
dev = _partition_get_part_dev (part);
return _partition_is_mounted_by_dev (dev);
}
static int
linux_partition_is_busy (const PedPartition* part)
{
PedPartition* walk;
PED_ASSERT (part != NULL, return 0);
if (_partition_is_mounted (part))
return 1;
if (part->type == PED_PARTITION_EXTENDED) {
for (walk = part->part_list; walk; walk = walk->next) {
if (linux_partition_is_busy (walk))
return 1;
}
}
return 0;
}
static int
_blkpg_part_command (PedDevice* dev, struct blkpg_partition* part, int op)
{
LinuxSpecific* arch_specific = LINUX_SPECIFIC (dev);
struct blkpg_ioctl_arg ioctl_arg;
ioctl_arg.op = op;
ioctl_arg.flags = 0;
ioctl_arg.datalen = sizeof (struct blkpg_partition);
ioctl_arg.data = (void*) part;
return ioctl (arch_specific->fd, BLKPG, &ioctl_arg) == 0;
}
static int
_blkpg_add_partition (PedDisk* disk, PedPartition* part)
{
struct blkpg_partition linux_part;
const char* vol_name;
char* dev_name;
PED_ASSERT(disk != NULL, return 0);
PED_ASSERT(disk->dev->sector_size % PED_SECTOR_SIZE_DEFAULT == 0,
return 0);
if (ped_disk_type_check_feature (disk->type,
PED_DISK_TYPE_PARTITION_NAME))
vol_name = ped_partition_get_name (part);
else
vol_name = NULL;
dev_name = _device_get_part_path (disk->dev, part->num);
if (!dev_name)
return 0;
memset (&linux_part, 0, sizeof (linux_part));
linux_part.start = part->geom.start * disk->dev->sector_size;
/* see fs/partitions/msdos.c:msdos_partition(): "leave room for LILO" */
if (part->type & PED_PARTITION_EXTENDED)
linux_part.length = part->geom.length == 1 ? 512 : 1024;
else
linux_part.length = part->geom.length * disk->dev->sector_size;
linux_part.pno = part->num;
strncpy (linux_part.devname, dev_name, BLKPG_DEVNAMELTH);
if (vol_name)
strncpy (linux_part.volname, vol_name, BLKPG_VOLNAMELTH);
ped_free (dev_name);
if (!_blkpg_part_command (disk->dev, &linux_part,
BLKPG_ADD_PARTITION)) {
return ped_exception_throw (
PED_EXCEPTION_ERROR,
PED_EXCEPTION_IGNORE_CANCEL,
_("Error informing the kernel about modifications to "
"partition %s -- %s. This means Linux won't know "
"about any changes you made to %s until you reboot "
"-- so you shouldn't mount it or use it in any way "
"before rebooting."),
linux_part.devname,
strerror (errno),
linux_part.devname)
== PED_EXCEPTION_IGNORE;
}
return 1;
}
static int
_blkpg_remove_partition (PedDisk* disk, int n)
{
struct blkpg_partition linux_part;
memset (&linux_part, 0, sizeof (linux_part));
linux_part.pno = n;
return _blkpg_part_command (disk->dev, &linux_part,
BLKPG_DEL_PARTITION);
}
static int
_disk_sync_part_table (PedDisk* disk)
{
int i;
int last = PED_MAX (ped_disk_get_last_partition_num (disk), 16);
int* rets = ped_malloc(sizeof(int) * last);
int* errnums = ped_malloc(sizeof(int) * last);
int ret = 1;
for (i = 1; i <= last; i++) {
rets[i - 1] = _blkpg_remove_partition (disk, i);
errnums[i - 1] = errno;
}
for (i = 1; i <= last; i++) {
PedPartition* part;
part = ped_disk_get_partition (disk, i);
if (part) {
/* busy... so we won't (can't!) disturb ;) Prolly
* doesn't matter anyway, because users shouldn't be
* changing mounted partitions anyway...
*/
if (!rets[i - 1] && errnums[i - 1] == EBUSY)
continue;
/* add the (possibly modified or new) partition */
if (!_blkpg_add_partition (disk, part))
ret = 0;
}
}
return ret;
}
#ifdef ENABLE_DEVICE_MAPPER
static int
_dm_remove_map_name(char *name)
{
struct dm_task *task = NULL;
int rc;
task = dm_task_create(DM_DEVICE_REMOVE);
if (!task)
return 1;
dm_task_set_name (task, name);
rc = dm_task_run(task);
dm_task_update_nodes();
dm_task_destroy(task);
if (rc < 0)
return 1;
return 0;
}
static int
_dm_is_part (struct dm_info *this, char *name)
{
struct dm_task* task = NULL;
struct dm_info* info = alloca(sizeof *info);
struct dm_deps* deps = NULL;
int rc = 0;
unsigned int i;
task = dm_task_create(DM_DEVICE_DEPS);
if (!task)
return 0;
dm_task_set_name(task, name);
rc = dm_task_run(task);
if (rc < 0) {
rc = 0;
goto err;
}
rc = 0;
memset(info, '\0', sizeof *info);
dm_task_get_info(task, info);
if (!info->exists)
goto err;
deps = dm_task_get_deps(task);
if (!deps)
goto err;
rc = 0;
for (i = 0; i < deps->count; i++) {
unsigned int ma = major(deps->device[i]),
mi = minor(deps->device[i]);
if (ma == this->major && mi == this->minor)
rc = 1;
}
err:
dm_task_destroy(task);
return rc;
}
static int
_dm_remove_parts (PedDevice* dev)
{
struct stat dev_stat;
struct dm_task* task = NULL;
struct dm_info* info = alloca(sizeof *info);
struct dm_names* names = NULL;
unsigned int next = 0;
int rc;
if (!_device_stat (dev, &dev_stat))
goto err;
task = dm_task_create(DM_DEVICE_LIST);
if (!task)
goto err;
dm_task_set_major (task, major (dev_stat.st_rdev));
dm_task_set_minor (task, minor (dev_stat.st_rdev));
rc = dm_task_run(task);
if (rc < 0)
goto err;
memset(info, '\0', sizeof *info);
dm_task_get_info(task, info);
if (!info->exists)
goto err;
names = dm_task_get_names(task);
if (!names)
goto err;
rc = 0;
do {
names = (void *)names + next;
if (_dm_is_part(info, names->name))
rc += _dm_remove_map_name(names->name);
next = names->next;
} while (next);
dm_task_update_nodes();
dm_task_destroy(task);
task = NULL;
if (!rc)
return 1;
err:
if (task)
dm_task_destroy(task);
ped_exception_throw (PED_EXCEPTION_WARNING, PED_EXCEPTION_IGNORE,
_("parted was unable to re-read the partition "
"table on %s (%s). This means Linux won't know "
"anything about the modifications you made. "),
dev->path, strerror (errno));
return 0;
}
static int
_dm_add_partition (PedDisk* disk, PedPartition* part)
{
struct stat dev_stat;
struct dm_task* task = NULL;
int rc;
char* vol_name = NULL;
char* dev_name = NULL;
char* params = NULL;
dev_name = _device_get_part_path (disk->dev, part->num);
if (!dev_name)
return 0;
vol_name = strrchr (dev_name, '/');
if (vol_name && *vol_name && *(++vol_name))
vol_name = strdup (vol_name);
else
vol_name = strdup (dev_name);
if (!vol_name)
return 0;
if (!_device_stat (disk->dev, &dev_stat))
goto err;
if (asprintf (&params, "%d:%d %lld", major (dev_stat.st_rdev),
minor (dev_stat.st_rdev), part->geom.start) == -1)
goto err;
if (!params)
goto err;
task = dm_task_create (DM_DEVICE_CREATE);
if (!task)
goto err;
dm_task_set_name (task, vol_name);
dm_task_add_target (task, 0, part->geom.length,
"linear", params);
rc = dm_task_run(task);
if (rc >= 0) {
//printf("0 %ld linear %s\n", part->geom.length, params);
dm_task_update_nodes();
dm_task_destroy(task);
free(params);
free(vol_name);
return 1;
} else {
_dm_remove_map_name(vol_name);
}
err:
dm_task_update_nodes();
if (task)
dm_task_destroy (task);
if (params)
free (params);
free (vol_name);
return 0;
}
static int
_dm_reread_part_table (PedDisk* disk)
{
int rc = 1;
int last = PED_MAX (ped_disk_get_last_partition_num (disk), 16);
int i;
sync();
if (!_dm_remove_parts(disk->dev))
rc = 0;
for (i = 1; i <= last; i++) {
PedPartition* part;
part = ped_disk_get_partition (disk, i);
if (!part)
continue;
if (!_dm_add_partition (disk, part))
rc = 0;
}
return rc;
}
#endif
static int
_kernel_reread_part_table (PedDevice* dev)
{
LinuxSpecific* arch_specific = LINUX_SPECIFIC (dev);
int retry_count = 5;
sync();
while (ioctl (arch_specific->fd, BLKRRPART)) {
retry_count--;
sync();
if (!retry_count) {
ped_exception_throw (
PED_EXCEPTION_WARNING,
PED_EXCEPTION_IGNORE,
_("The kernel was unable to re-read the partition "
"table on %s (%s). This means Linux won't know "
"anything about the modifications you made "
"until you reboot. You should reboot your computer "
"before doing anything with %s."),
dev->path, strerror (errno), dev->path);
return 0;
}
}
return 1;
}
static int
_have_blkpg ()
{
static int have_blkpg = -1;
int kver;
if (have_blkpg != -1)
return have_blkpg;
kver = _get_linux_version();
return have_blkpg = kver >= KERNEL_VERSION (2,4,0) ? 1 : 0;
}
static int
linux_disk_commit (PedDisk* disk)
{
#ifdef ENABLE_DEVICE_MAPPER
if (disk->dev->type == PED_DEVICE_DM)
return _dm_reread_part_table (disk);
#endif
if (disk->dev->type != PED_DEVICE_FILE) {
/* The ioctl() command BLKPG_ADD_PARTITION does not notify
* the devfs system; consequently, /proc/partitions will not
* be up to date, and the proper links in /dev are not
* created. Therefore, if using DevFS, we must get the kernel
* to re-read and grok the partition table.
*/
/* Work around kernel dasd problem so we really do BLKRRPART */
if (disk->dev->type != PED_DEVICE_DASD &&
_have_blkpg () && !_have_devfs ()) {
if (_disk_sync_part_table (disk))
return 1;
}
return _kernel_reread_part_table (disk->dev);
}
return 1;
}
static PedDeviceArchOps linux_dev_ops = {
_new: linux_new,
destroy: linux_destroy,
is_busy: linux_is_busy,
open: linux_open,
refresh_open: linux_refresh_open,
close: linux_close,
refresh_close: linux_refresh_close,
read: linux_read,
write: linux_write,
check: linux_check,
sync: linux_sync,
sync_fast: linux_sync_fast,
probe_all: linux_probe_all
};
PedDiskArchOps linux_disk_ops = {
partition_get_path: linux_partition_get_path,
partition_is_busy: linux_partition_is_busy,
disk_commit: linux_disk_commit
};
PedArchitecture ped_linux_arch = {
dev_ops: &linux_dev_ops,
disk_ops: &linux_disk_ops
};