/* libparted - a library for manipulating disk partitions Copyright (C) 1998-2000 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "config.h" #include #define N_(String) String #if ENABLE_NLS # define _(String) dgettext (PACKAGE, String) #else # define _(String) (String) #endif /* ENABLE_NLS */ #include "llseek.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef SCSI_IOCTL_SEND_COMMAND #define SCSI_IOCTL_SEND_COMMAND 1 #endif /* from */ #ifdef linux #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 */ #endif static PedDevice* devices = NULL; static PedDevice* ped_device_new (const char* path); /* O(n) add function, hehe */ static void ped_device_add (PedDevice* dev) { PedDevice* walk; for (walk = devices; walk && walk->next; walk = walk->next); if (walk) walk->next = dev; else devices = dev; dev->next = NULL; } static void ped_device_remove (PedDevice* dev) { PedDevice* walk; PedDevice* last = NULL; for (walk = devices; walk != NULL; last = walk, walk = walk->next) { if (walk == dev) break; } if (last) last->next = dev->next; else devices = dev->next; } PedDevice* ped_device_get_next (const PedDevice* dev) { if (dev) return dev->next; else return devices; } /* First searches through probed devices, then attempts to open the device * regardless. */ PedDevice* ped_device_get (const char* path) { PedDevice* walk; PED_ASSERT (path != NULL, return NULL); for (walk = devices; walk != NULL; walk = walk->next) { if (!strcmp (walk->path, path)) return walk; } walk = ped_device_new (path); if (!walk) return NULL; ped_device_add (walk); return walk; } static int ped_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 char* strip_name(char* n) { int len = strlen (n); int end = 0; int i = 0; while (i < len) { if ((n[i] != ' ') || ((n[i] == ' ') && (n[i + 1] != ' '))) { n[end] = n[i]; end++; } i++; } n[end] = 0; return (strdup(n)); } 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; } */ static int device_probe_type (PedDevice* dev) { struct stat dev_stat; int dev_major; int dev_minor; PedExceptionOption ex_status; if (!ped_device_stat (dev, &dev_stat)) return 0; if (!S_ISBLK(dev_stat.st_mode)) { /* if (ped_exception_throw ( PED_EXCEPTION_WARNING, PED_EXCEPTION_IGNORE_CANCEL, _("%s is not a block device."), dev->path) == PED_EXCEPTION_CANCEL) return 0; */ dev->type = PED_DEVICE_UNKNOWN; 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 (_is_cpqarray_major (dev_major) && (dev_minor % 0x10 == 0)) { dev->type = PED_DEVICE_CPQARRAY; */ } else { dev->type = PED_DEVICE_UNKNOWN; } return 1; } #ifdef linux static int _get_linux_version () { FILE* f; int major; int minor; f = fopen ("/proc/sys/kernel/osrelease", "r"); if (!f) return 0; if (fscanf (f, "%d.%d.", &major, &minor) != 2) return 0; fclose (f); return major * 0x100 + minor; } #endif /* linux */ static int device_get_sector_size (PedDevice* dev) { int sector_size; PED_ASSERT (dev->open_count, return 0); #ifdef linux if (_get_linux_version() < 0x203) /* BLKSSZGET is broken < 2.3.x */ return 512; if (ioctl (dev->fd, BLKSSZGET, §or_size)) return 512; if (sector_size != 512) { if (ped_exception_throw ( PED_EXCEPTION_BUG, PED_EXCEPTION_IGNORE_CANCEL, _("The sector size on %s is %d bytes. Parted is known " "not to work properly with drives with sector sizes " "other than 512 bytes"), dev->path, sector_size) == PED_EXCEPTION_IGNORE) return sector_size; else return 0; } return sector_size; #else /* linux */ return 512; #endif /* !linux */ } /* TODO: do a binary search if BLKGETSIZE doesn't work?! */ static PedSector device_get_length (PedDevice* dev) { unsigned long size; PED_ASSERT (dev->open_count, return 0); if (ioctl (dev->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) { struct stat dev_stat; struct hd_geometry geometry; if (!ped_device_stat (dev, &dev_stat)) goto error; PED_ASSERT (S_ISBLK (dev_stat.st_mode), goto error); dev->length = device_get_length (dev); if (!dev->length) goto error; if (ioctl (dev->fd, HDIO_GETGEO, &geometry)) { ped_exception_throw (PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL, _("Could not read geometry of %s - %s."), dev->path, strerror (errno)); goto error; } dev->sector_size = device_get_sector_size (dev); if (!dev->sector_size) goto error; dev->sectors = geometry.sectors; dev->heads = geometry.heads; if (!dev->sectors || !dev->heads) goto error_dodgey_geometry; dev->cylinders = dev->length / (dev->heads * dev->sectors * (dev->sector_size / 512)); return 1; error_dodgey_geometry: ped_exception_throw ( PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL, _("Device %s has dodgey geometry."), dev->path); error: return 0; } static int init_ide (PedDevice* dev) { struct stat dev_stat; int dev_major; struct hd_driveid hdi; PedExceptionOption ex_status; if (!ped_device_stat (dev, &dev_stat)) goto error; dev_major = major (dev_stat.st_rdev); if (!ped_device_open (dev)) goto error; if (ioctl (dev->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(_("unknown")); } } else { dev->model = strip_name (hdi.model); } 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_scsi (PedDevice* dev) { unsigned char idlun [8]; unsigned char buffer [128]; unsigned char* cmd; struct hd_geometry geometry; if (!ped_device_open (dev)) goto error; memset (buffer, 0, 96); *((int *) buffer) = 0; /* length of input data */ *(((int *) buffer) + 1) = 96; /* length of output buffer */ cmd = (char *) (((int *) buffer) + 2); cmd[0] = 0x12; /* INQUIRY */ cmd[1] = 0x00; /* lun=0, evpd=0 */ cmd[2] = 0x00; /* page code = 0 */ cmd[3] = 0x00; /* (reserved) */ cmd[4] = 96; /* allocation length */ cmd[5] = 0x00; /* control */ if (ioctl(dev->fd, SCSI_IOCTL_SEND_COMMAND, buffer)) { buffer[40] = 0; dev->model = strip_name (buffer + 16); } else { dev->model = _("Unknown SCSI"); } if (ioctl (dev->fd, SCSI_IOCTL_GET_IDLUN, idlun)) { ped_exception_throw (PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL, _("Error initialising SCSI device " "%s - %s"), dev->path, strerror (errno)); goto error_close_dev; } if (!device_probe_geometry (dev)) goto error_close_dev; dev->host = *((unsigned long *) (idlun + 4)); dev->did = idlun [0]; ped_device_close (dev); return 1; error_close_dev: ped_device_close (dev); error: return 0; } static int init_generic (PedDevice* dev, char* model_name) { struct stat dev_stat; struct hd_geometry geometry; PedExceptionOption ex_status; if (!ped_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. You should not use Parted " "unless you REALLY know what you're doing!")); switch (ex_status) { case PED_EXCEPTION_CANCEL: goto error_close_dev; case PED_EXCEPTION_UNHANDLED: ped_exception_catch (); case PED_EXCEPTION_IGNORE: } /* what should we stick in here? */ dev->length = dev_stat.st_size / 512; dev->cylinders = dev->length / 4 / 32; dev->heads = 4; dev->sectors = 32; dev->sector_size = 512; dev->geom_known = 0; } dev->model = model_name; ped_device_close (dev); return 1; error_close_dev: ped_device_close (dev); error: return 0; } static PedDevice* ped_device_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); dev->open_count = 0; dev->external_mode = 0; dev->dirty = 0; dev->boot_dirty = 0; dev->geom_known = 1; dev->geom_already_guessed = 0; if (!device_probe_type (dev)) goto error_free_dev; switch (dev->type) { case PED_DEVICE_IDE: if (!init_ide (dev)) goto error_free_dev; break; case PED_DEVICE_SCSI: if (!init_scsi (dev)) goto error_free_dev; break; case PED_DEVICE_DAC960: if (!init_generic (dev, _("DAC960 RAID controller"))) goto error_free_dev; break; case PED_DEVICE_CPQARRAY: if (!init_generic (dev, _("Compaq Smart Array"))) goto error_free_dev; break; case PED_DEVICE_UNKNOWN: if (ped_exception_throw ( PED_EXCEPTION_WARNING, PED_EXCEPTION_IGNORE_CANCEL, _("Device %s is neither a SCSI nor IDE drive."), dev->path) == PED_EXCEPTION_CANCEL) goto error_free_dev; if (!init_generic (dev, _("Unknown"))) goto error_free_dev; break; default: ped_exception_throw (PED_EXCEPTION_NO_FEATURE, PED_EXCEPTION_CANCEL, _("ped_device_new() Unsupported device type")); goto error_free_dev; } return dev; error_free_dev: ped_free (dev); error: return NULL; } static void ped_device_destroy (PedDevice* dev) { PED_ASSERT (dev != NULL, return); while (dev->open_count) ped_device_close (dev); ped_free (dev->path); ped_free (dev); } #ifdef linux static void _flush_cache (PedDevice* dev) { int i; char* name; int fd; if (dev->read_only) return; ioctl (dev->fd, BLKFLSBUF); name = (char*) ped_malloc (strlen (dev->path) + 3); if (!name) return; for (i = 0; i < 16; i++) { sprintf (name, "%s%d", dev->path, i); fd = open (name, O_WRONLY, 0); if (fd == -1) continue; ioctl (fd, BLKFLSBUF); close (fd); } ped_free (name); } static int _kernel_reread_part_table (PedDevice* dev) { int retry_count = 5; sync(); while (ioctl (dev->fd, BLKRRPART)) { retry_count--; sync(); if (!retry_count) return 0; } return 1; } #endif /* linux */ static int _do_refresh_open (PedDevice* dev) { #ifdef linux _flush_cache (dev); #endif return 1; } static int _do_open (PedDevice* dev) { char* rw_error_msg; retry: dev->fd = open (dev->path, O_RDWR); if (dev->fd == -1) { rw_error_msg = strerror (errno); dev->fd = open (dev->path, O_RDONLY); if (dev->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; } #ifdef linux _flush_cache (dev); #endif return 1; } static int _do_refresh_close (PedDevice* dev) { return 1; } static int _do_close (PedDevice* dev) { #ifdef linux _flush_cache (dev); if (dev->dirty && dev->type != PED_DEVICE_UNKNOWN) { if (_kernel_reread_part_table (dev)) dev->dirty = 0; } if (dev->dirty && dev->boot_dirty && dev->type != PED_DEVICE_UNKNOWN) { /* ouch! */ ped_exception_throw ( PED_EXCEPTION_WARNING, PED_EXCEPTION_OK, _("The kernel was unable to re-read your partition " "table, so you need to reboot before mounting any " "modified partitions. You also need to reinstall " "your boot loader before you reboot (which may " "require mounting modified partitions). It is " "impossible do both things! So you'll need to " "boot off a rescue disk, and reinstall your boot " "loader from the rescue disk. Read section 4 of " "the Parted User documentation for more " "information.")); close (dev->fd); return 1; } if (dev->dirty && dev->type != PED_DEVICE_UNKNOWN) { 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 knows nothing " "about any modifications you made. You should " "reboot your computer before doing anything with " "%s."), dev->path, strerror (errno), dev->path); } if (dev->boot_dirty && dev->type != PED_DEVICE_UNKNOWN) { ped_exception_throw ( PED_EXCEPTION_WARNING, PED_EXCEPTION_OK, _("You should reinstall your boot loader before " "rebooting. Read section 4 of the Parted User " "documentation for more information.")); } #endif /* linux */ close (dev->fd); return 1; } int ped_device_open (PedDevice* dev) { PED_ASSERT (dev != NULL, return 0); PED_ASSERT (!dev->external_mode, return 0); if (dev->open_count++) return _do_refresh_open (dev); return _do_open (dev); } int ped_device_close (PedDevice* dev) { PED_ASSERT (dev != NULL, return 0); PED_ASSERT (!dev->external_mode, return 0); if (--dev->open_count) return _do_refresh_close (dev); return _do_close (dev); } int ped_device_begin_external_access (PedDevice* dev) { PED_ASSERT (dev != NULL, return 0); PED_ASSERT (!dev->external_mode, return 0); dev->external_mode = 1; if (dev->open_count) _do_close (dev); return 1; } int ped_device_end_external_access (PedDevice* dev) { PED_ASSERT (dev != NULL, return 0); PED_ASSERT (dev->external_mode, return 0); dev->external_mode = 0; if (dev->open_count) _do_open (dev); return 1; } static int ped_device_seek (const PedDevice* dev, PedSector sector) { PED_ASSERT (dev != NULL, return 0); PED_ASSERT (!dev->external_mode, return 0); #if SIZEOF_OFF_T < 8 && defined(linux) if (sizeof (off_t) < 8) { loff_t pos = sector * 512; return ped_llseek (dev->fd, pos, SEEK_SET) == pos; } else #endif { off_t pos = sector * 512; return lseek (dev->fd, pos, SEEK_SET) == pos; } } int ped_device_read (const PedDevice* dev, void* buffer, PedSector start, PedSector count) { int status; PedExceptionOption ex_status; size_t read_length = count * PED_SECTOR_SIZE; PED_ASSERT (dev != NULL, return 0); PED_ASSERT (!dev->external_mode, return 0); PED_ASSERT (buffer != NULL, return 0); while (1) { if (ped_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; } } while (1) { status = read (dev->fd, buffer, read_length); if (status == count * PED_SECTOR_SIZE) break; if (status > 0) { read_length -= status; 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: return 1; case PED_EXCEPTION_RETRY: break; case PED_EXCEPTION_UNHANDLED: ped_exception_catch (); case PED_EXCEPTION_CANCEL: return 0; } } return 1; } int ped_device_sync (PedDevice* dev) { int status; PedExceptionOption ex_status; PED_ASSERT (dev != NULL, return 0); PED_ASSERT (!dev->external_mode, return 0); if (dev->read_only) return 1; while (1) { status = fsync (dev->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; } } return 1; } int ped_device_write (PedDevice* dev, const void* buffer, PedSector start, PedSector count) { int status; PedExceptionOption ex_status; size_t write_length = count * PED_SECTOR_SIZE; PED_ASSERT (dev != NULL, return 0); PED_ASSERT (!dev->external_mode, return 0); PED_ASSERT (buffer != NULL, 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; } while (1) { if (ped_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; } } #ifdef READONLY printf ("ped_device_write (\"%s\", %p, %d, %d)\n", dev->path, buffer, (int) start, (int) count); #else dev->dirty = 1; while (1) { status = write (dev->fd, buffer, write_length); if (status == count * PED_SECTOR_SIZE) break; if (status > 0) { write_length -= status; buffer += 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: return 1; case PED_EXCEPTION_RETRY: break; case PED_EXCEPTION_UNHANDLED: ped_exception_catch (); case PED_EXCEPTION_CANCEL: return 0; } } #endif return 1; } /* returns the number of sectors that are ok. */ PedSector ped_device_check (PedDevice* dev, void* buffer, PedSector start, PedSector count) { int status; int done = 0; PED_ASSERT (dev != NULL, return 0); PED_ASSERT (!dev->external_mode, return 0); PED_ASSERT (buffer != NULL, return 0); if (!ped_device_seek (dev, start)) return 0; while (1) { status = read (dev->fd, buffer, (count-done) * PED_SECTOR_SIZE); if (status < 0) return 0; done += status / PED_SECTOR_SIZE; if (done == count * PED_SECTOR_SIZE) break; else continue; } return count; } static void probe (char* path) { PedDevice* dev; PED_ASSERT (path != NULL, return); for (dev = devices; dev; dev = dev->next) { if (!strcmp (dev->path, path)) return; } ped_exception_fetch_all (); dev = ped_device_new (path); if (!dev) goto error; if (!ped_device_open (dev)) { ped_device_destroy (dev); goto error; } ped_exception_leave_all (); ped_device_close (dev); ped_device_add (dev); return; error: ped_exception_catch (); ped_exception_leave_all (); } static int probe_proc_partitions () { FILE* proc_part_file; int major, minor, size; char part_name [256]; char dev_name [256]; proc_part_file = fopen ("/proc/partitions", "r"); if (!proc_part_file) return 0; fgets (part_name, 256, proc_part_file); fgets (part_name, 256, proc_part_file); while (fscanf (proc_part_file, "%d %d %d %255s", &major, &minor, &size, part_name) == 4) { if (isdigit (part_name [strlen (part_name) - 1])) continue; strcpy (dev_name, "/dev/"); strcat (dev_name, part_name); probe (dev_name); } fclose (proc_part_file); return 1; } static int probe_standard_devices () { probe ("/dev/sda"); probe ("/dev/sdb"); probe ("/dev/sdc"); probe ("/dev/sdd"); probe ("/dev/sde"); probe ("/dev/sdf"); probe ("/dev/hda"); probe ("/dev/hdb"); probe ("/dev/hdc"); probe ("/dev/hdd"); probe ("/dev/hde"); probe ("/dev/hdf"); probe ("/dev/hdg"); probe ("/dev/hdh"); return 1; } void ped_device_probe_all () { probe_proc_partitions (); probe_standard_devices (); } void ped_device_free_all () { PedDevice* dev; while (devices) { dev = devices; ped_device_remove (dev); ped_device_destroy (dev); } }