/* libparted - a library for manipulating disk partitions Copyright (C) 1999-2001, 2005, 2007, 2009-2014, 2019-2023 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 3 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, see . */ #include #include #include #include #include #include #include #include #include #include #if ENABLE_NLS # include # define _(String) dgettext (PACKAGE, String) #else # define _(String) (String) #endif /* ENABLE_NLS */ #include "../architecture.h" #define GNU_SPECIFIC(dev) ((GNUSpecific*) (dev)->arch_specific) typedef struct _GNUSpecific GNUSpecific; struct _GNUSpecific { struct store* store; int consume; }; /* Initialize a PedDevice using SOURCE. The SOURCE will NOT be destroyed; the caller created it, it is the caller's responsilbility to free it after it calls ped_device_destory. SOURCE is not registered in Parted's list of devices. */ PedDevice* ped_device_new_from_store (struct store *source); static int _device_get_sector_size (PedDevice* dev) { GNUSpecific* arch_specific = GNU_SPECIFIC (dev); size_t store_block_size = arch_specific->store->block_size; return PED_SECTOR_SIZE_DEFAULT; } static PedSector _device_get_length (PedDevice* dev) { GNUSpecific* arch_specific = GNU_SPECIFIC (dev); size_t store_blocks = arch_specific->store->blocks; size_t store_block_size = arch_specific->store->block_size; return ((long long) store_blocks * store_block_size) / PED_SECTOR_SIZE_DEFAULT; } static int _device_probe_geometry (PedDevice* dev) { PedSector cyl_size; dev->length = _device_get_length (dev); if (!dev->length) return 0; dev->sector_size = _device_get_sector_size (dev); if (!dev->sector_size) return 0; /* XXX: We have no way to get this! */ dev->bios_geom.sectors = 63; dev->bios_geom.heads = 255; cyl_size = dev->bios_geom.sectors * dev->bios_geom.heads; dev->bios_geom.cylinders = dev->length / cyl_size * (dev->sector_size / PED_SECTOR_SIZE_DEFAULT); dev->hw_geom = dev->bios_geom; return 1; } static int init_file (PedDevice* dev) { PedExceptionOption ex_status; retry_open: if (!ped_device_open (dev)) { ex_status = ped_exception_throw ( PED_EXCEPTION_WARNING, PED_EXCEPTION_RETRY_CANCEL, _("Unable to open %s."), dev->path); switch (ex_status) { case PED_EXCEPTION_RETRY: goto retry_open; case PED_EXCEPTION_UNHANDLED: ped_exception_catch (); case PED_EXCEPTION_CANCEL: goto error; } return 0; } retry_probe: if (!_device_probe_geometry (dev)) { ex_status = ped_exception_throw ( PED_EXCEPTION_WARNING, PED_EXCEPTION_RETRY_CANCEL, _("Unable to probe store.")); switch (ex_status) { case PED_EXCEPTION_RETRY: goto retry_probe; case PED_EXCEPTION_UNHANDLED: ped_exception_catch (); case PED_EXCEPTION_CANCEL: goto error_close_dev; } return 0; } dev->model = ""; ped_device_close (dev); return 1; error_close_dev: ped_device_close (dev); error: return 0; } static void _flush_cache (PedDevice* dev) { GNUSpecific* arch_specific = GNU_SPECIFIC (dev); if (dev->read_only) return; /* Wait for a complete sync to finish. */ file_sync (arch_specific->store->source, 1, 0); } /* Initialize by allocating memory and filling in a few defaults, a PedDevice structure. */ static PedDevice* _init_device (const char *path) { PedDevice *dev; GNUSpecific* arch_specific; dev = (PedDevice*) ped_malloc (sizeof (PedDevice)); if (!dev) goto error; dev->path = strdup (path); if (!dev->path) goto error_free_dev; dev->arch_specific = (GNUSpecific*) ped_malloc (sizeof (GNUSpecific)); if (!dev->arch_specific) goto error_free_path; dev->type = PED_DEVICE_UNKNOWN; /* It's deprecated anyway */ dev->open_count = 0; dev->read_only = 0; dev->external_mode = 0; dev->dirty = 0; dev->boot_dirty = 0; return dev; error_free_arch_specific: free (dev->arch_specific); error_free_path: free (dev->path); error_free_dev: free (dev); error: return NULL; } /* Ask the kernel and translators to reload the partition table. XXX: Will probably be replaced by some RPC to partfs when it's finished. In the meantime, gnumach's glue layer will pass BLKRRPART to the Linux drivers. */ #define BLKRRPART 0x125F static int _reread_part_table (PedDevice* dev) { struct store *store = GNU_SPECIFIC (dev)->store; int retry_count = 9; int len = strlen (dev->path); char path[len + 3 + 1]; int i; int done = 1; sync (); if(strcmp (store->class->name, "device") == 0) { while (device_set_status (store->port, BLKRRPART, NULL, 0)) { retry_count--; sync (); if (retry_count == 3) sleep (1); /* Pause to allow system to settle */ if (!retry_count) { ped_exception_throw ( PED_EXCEPTION_WARNING, PED_EXCEPTION_IGNORE, _("WARNING: the kernel failed to re-read the " "partition table on %s (%s). As a result, " "it may not reflect all of your changes " "until after reboot."), dev->path, strerror (errno)); return 0; } } } i = 1; while (1) { file_t node; error_t err; /* Throw away all active parted-based translators */ snprintf (path, sizeof (path), "%ss%u", dev->path, i); node = file_name_lookup (path, O_NOTRANS, 0666); if (node == MACH_PORT_NULL) { if (errno == ENOENT) /* Finished looping over them */ break; ped_exception_throw ( PED_EXCEPTION_WARNING, PED_EXCEPTION_IGNORE, _("Warning: unable to open %s (%s). As a " "result, it may not reflect all of your " "changes until after reboot."), path, strerror (errno)); done = 0; } err = file_set_translator (node, 0, FS_TRANS_SET, 0, 0, 0, MACH_PORT_NULL, MACH_MSG_TYPE_COPY_SEND); if (err) { ped_exception_throw ( PED_EXCEPTION_WARNING, PED_EXCEPTION_IGNORE, _("Warning: failed to make translator go away " "on %s (%s). As a result, it may not reflect " "all of your changes until after reboot."), dev->path, strerror (errno)); done = 0; } i++; } return done; } /* Free the memory associated with a PedDevice structure. */ static void _done_device (PedDevice *dev) { free (dev->arch_specific); free (dev->path); free (dev); } /* Release all resources that libparted owns in DEV. */ static void gnu_destroy (PedDevice* dev) { GNUSpecific* arch_specific = GNU_SPECIFIC (dev); if (arch_specific->consume) store_free (arch_specific->store); _done_device (dev); } static PedDevice* gnu_new (const char* path) { PedDevice* dev; GNUSpecific* arch_specific; error_t ro_err, rw_err; int ispath; PED_ASSERT (path != NULL); dev = _init_device (path); if (!dev) return NULL; arch_specific = GNU_SPECIFIC (dev); arch_specific->consume = 1; retry_open: /* Try read-write. */ if (strchr (dev->path, '/') != NULL) { /* We set this to prevent having to use strchr more then once. */ ispath = 1; rw_err = store_open (dev->path, 0, NULL, &arch_specific->store); } else { rw_err = store_typed_open (dev->path, 0, NULL, &arch_specific->store); } /* Try readonly. */ if (rw_err) { if (ispath) { ro_err = store_open (dev->path, STORE_READONLY, NULL, &arch_specific->store); } else { ro_err = store_typed_open (dev->path, STORE_READONLY, NULL, &arch_specific->store); } if (ro_err) { if (ped_exception_throw ( PED_EXCEPTION_ERROR, PED_EXCEPTION_RETRY_CANCEL, _("Error opening %s: %s"), dev->path, strerror (ro_err)) != PED_EXCEPTION_RETRY) { return NULL; } else goto retry_open; } 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, strerror (rw_err), dev->path); dev->read_only = 1; } } else { dev->read_only = 0; } _flush_cache (dev); if (!init_file (dev)) { gnu_destroy(dev); return NULL; } return dev; } PedDevice* ped_device_new_from_store (struct store *source) { PedDevice* dev; GNUSpecific* arch_specific; PED_ASSERT (source != NULL); dev = _init_device (source->name ?: "(unknown)"); if (!dev) return NULL; arch_specific = GNU_SPECIFIC (dev); arch_specific->store = source; arch_specific->consume = 0; dev->read_only = source->flags & (STORE_READONLY|STORE_HARD_READONLY); if (!init_file (dev)) { _done_device (dev); return NULL; } return dev; } static int gnu_is_busy (PedDevice* dev) { return 0; } static int gnu_open (PedDevice* dev) { return 1; } static int gnu_refresh_open (PedDevice* dev) { return 1; } static int gnu_close (PedDevice* dev) { GNUSpecific* arch_specific = GNU_SPECIFIC (dev); _flush_cache (dev); if (dev->dirty && dev->type != PED_DEVICE_FILE) { if (_reread_part_table (dev)) dev->dirty = 0; } return 1; } static int gnu_refresh_close (PedDevice* dev) { _flush_cache (dev); return 1; } static int gnu_read (const PedDevice* dev, void* user_buffer, PedSector device_start, PedSector count) { GNUSpecific* arch_specific = GNU_SPECIFIC (dev); error_t err; PedExceptionOption ex_status; size_t start; size_t store_start_block; /* In bytes. This can be larger than COUNT when store pages are larger than PED_SECTOR_SIZE_DEFAULT. */ size_t store_read_length; char local_buffer[PED_SECTOR_SIZE_DEFAULT]; void * store_read_buffer; size_t have_read; size_t read_offset; size_t device_read_length = count * PED_SECTOR_SIZE_DEFAULT; start = device_start * PED_SECTOR_SIZE_DEFAULT; if (PED_SECTOR_SIZE_DEFAULT != arch_specific->store->block_size) { store_start_block = start / arch_specific->store->block_size; store_read_length = (device_read_length + arch_specific->store->block_size - 1) / arch_specific->store->block_size; } else { store_start_block = device_start; store_read_length = device_read_length; } read_offset = start - store_start_block * arch_specific->store->block_size; if (store_read_length % arch_specific->store->block_size != 0) store_read_length = store_read_length + arch_specific->store->block_size - store_read_length % arch_specific->store->block_size; retry: have_read = 0; while (1) { size_t did_read; size_t offset; store_read_buffer = local_buffer; did_read = sizeof (local_buffer); err = store_read (arch_specific->store, store_start_block, store_read_length - have_read, &store_read_buffer, &did_read); if (err) { ex_status = ped_exception_throw ( PED_EXCEPTION_ERROR, PED_EXCEPTION_RETRY_IGNORE_CANCEL, _("%s during read on %s"), strerror (err), dev->path); switch (ex_status) { case PED_EXCEPTION_IGNORE: return 1; case PED_EXCEPTION_RETRY: goto retry; case PED_EXCEPTION_UNHANDLED: ped_exception_catch (); case PED_EXCEPTION_CANCEL: return 0; } } memcpy (user_buffer + have_read - read_offset, store_read_buffer + (have_read >= read_offset ? 0 : read_offset - have_read), have_read + did_read > device_read_length + read_offset ? device_read_length + read_offset - have_read : did_read); if (store_read_buffer != local_buffer) vm_deallocate (mach_task_self (), (long) store_read_buffer, did_read); have_read += did_read; store_start_block += did_read / arch_specific->store->block_size; if (have_read >= device_read_length) break; } return 1; } static int gnu_write (PedDevice* dev, const void* buffer, PedSector start, PedSector count) { GNUSpecific* arch_specific = GNU_SPECIFIC (dev); error_t err; PedExceptionOption ex_status; void * temp; char local_buffer[PED_SECTOR_SIZE_DEFAULT]; size_t did_read; size_t did_write; /* Map a disk sector to a store sector. */ #define PED_TO_STORE(store, sector) (((sector) * PED_SECTOR_SIZE_DEFAULT) \ / (store)->block_size) 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; } #ifdef READ_ONLY printf ("ped_device_write (\"%s\", %p, %d, %d)\n", dev->path, buffer, (int) start, (int) count); #else dev->dirty = 1; /* If the first ``device'' block (PedSector) is not aligned on a store block, then we need to fetch the old block, copy in the overlaping area and finally, write the modified data out to the store. */ if ((PED_SECTOR_SIZE_DEFAULT * start) % arch_specific->store->block_size != 0) { size_t write_offset; size_t flushing; doggy_first_block_read: /* We do not bother looping as we are only reading a single block. */ temp = local_buffer; did_read = sizeof (local_buffer); err = store_read (arch_specific->store, PED_TO_STORE (arch_specific->store, start), arch_specific->store->block_size, &temp, &did_read); if (! err && did_read != arch_specific->store->block_size) err = EIO; if (err) { ex_status = ped_exception_throw ( PED_EXCEPTION_ERROR, PED_EXCEPTION_RETRY_IGNORE_CANCEL, _("%s during read on %s"), strerror (err), dev->path); switch (ex_status) { case PED_EXCEPTION_IGNORE: break; case PED_EXCEPTION_RETRY: goto doggy_first_block_read; case PED_EXCEPTION_UNHANDLED: ped_exception_catch (); case PED_EXCEPTION_CANCEL: return 0; } } write_offset = (start * PED_SECTOR_SIZE_DEFAULT) % arch_specific->store->block_size; flushing = arch_specific->store->block_size - write_offset; if (flushing > count * PED_SECTOR_SIZE_DEFAULT) flushing = count * PED_SECTOR_SIZE_DEFAULT; memcpy (temp + write_offset, buffer, flushing); doggy_first_block_write: err = store_write (arch_specific->store, PED_TO_STORE (arch_specific->store, start), temp, arch_specific->store->block_size, &did_write); if (! err && did_write != arch_specific->store->block_size) err = EIO; if (err) { ex_status = ped_exception_throw ( PED_EXCEPTION_ERROR, PED_EXCEPTION_RETRY_IGNORE_CANCEL, _("%s during write on %s"), strerror (err), dev->path); switch (ex_status) { case PED_EXCEPTION_IGNORE: break; case PED_EXCEPTION_RETRY: goto doggy_first_block_write; case PED_EXCEPTION_UNHANDLED: ped_exception_catch (); case PED_EXCEPTION_CANCEL: if (temp != local_buffer) vm_deallocate ( mach_task_self (), (long) temp, did_read); return 0; } } start += flushing / PED_SECTOR_SIZE_DEFAULT; count -= flushing / PED_SECTOR_SIZE_DEFAULT; buffer += write_offset; if (temp != local_buffer) vm_deallocate (mach_task_self (), (long) temp, did_read); if (count == 0) return 1; } while (count > 0 && count >= arch_specific->store->block_size / PED_SECTOR_SIZE_DEFAULT) { err = store_write (arch_specific->store, PED_TO_STORE (arch_specific->store, start), buffer, count * PED_SECTOR_SIZE_DEFAULT, &did_write); if (err) { ex_status = ped_exception_throw ( PED_EXCEPTION_ERROR, PED_EXCEPTION_RETRY_IGNORE_CANCEL, _("%s during write on %s"), strerror (err), dev->path); switch (ex_status) { case PED_EXCEPTION_IGNORE: break; case PED_EXCEPTION_RETRY: continue; case PED_EXCEPTION_UNHANDLED: ped_exception_catch (); case PED_EXCEPTION_CANCEL: return 0; } } start += did_write / PED_SECTOR_SIZE_DEFAULT; count -= did_write / PED_SECTOR_SIZE_DEFAULT; buffer += did_write; } if (count == 0) return 1; /* We are now left with (strictly) less then a store block to write to disk. Thus, we read the block, overlay the buffer and flush. */ PED_ASSERT (count * PED_SECTOR_SIZE_DEFAULT < arch_specific->store->block_size); doggy_last_block_read: /* We do not bother looping as we are only reading a single block. */ temp = local_buffer; did_read = sizeof (local_buffer); err = store_read (arch_specific->store, PED_TO_STORE (arch_specific->store, start), arch_specific->store->block_size, &temp, &did_read); if (! err && did_read != arch_specific->store->block_size) err = EIO; if (err) { ex_status = ped_exception_throw ( PED_EXCEPTION_ERROR, PED_EXCEPTION_RETRY_IGNORE_CANCEL, _("%s during read on %s"), strerror (err), dev->path); switch (ex_status) { case PED_EXCEPTION_IGNORE: break; case PED_EXCEPTION_RETRY: goto doggy_last_block_read; case PED_EXCEPTION_UNHANDLED: ped_exception_catch (); case PED_EXCEPTION_CANCEL: return 0; } } memcpy (temp, buffer, count * PED_SECTOR_SIZE_DEFAULT); doggy_last_block_write: err = store_write (arch_specific->store, PED_TO_STORE (arch_specific->store, start), temp, arch_specific->store->block_size, &did_write); if (! err && did_write != arch_specific->store->block_size) err = EIO; if (err) { ex_status = ped_exception_throw ( PED_EXCEPTION_ERROR, PED_EXCEPTION_RETRY_IGNORE_CANCEL, _("%s during write on %s"), strerror (err), dev->path); switch (ex_status) { case PED_EXCEPTION_IGNORE: break; case PED_EXCEPTION_RETRY: goto doggy_last_block_write; case PED_EXCEPTION_UNHANDLED: ped_exception_catch (); case PED_EXCEPTION_CANCEL: if (temp != local_buffer) vm_deallocate (mach_task_self (), (long) temp, did_read); return 0; } } #endif /* !READ_ONLY */ return 1; } /* TODO: returns the number of sectors that are ok. */ static PedSector gnu_check (PedDevice* dev, void* buffer, PedSector start, PedSector count) { int status; int done = 0; PED_ASSERT (dev != NULL); PED_ASSERT (!dev->external_mode); PED_ASSERT (buffer != NULL); return count; } static int gnu_sync (PedDevice* dev) { GNUSpecific* arch_specific; error_t err; PedExceptionOption ex_status; static char *last_failure = NULL; PED_ASSERT (dev != NULL); PED_ASSERT (!dev->external_mode); arch_specific = GNU_SPECIFIC (dev); if (dev->read_only || ! dev->dirty) return 1; while (1) { err = file_sync (arch_specific->store->source, 1, 0); if (! err || err == EOPNOTSUPP || err == EPERM || (last_failure && strcmp (last_failure, dev->path) == 0)) break; ex_status = ped_exception_throw ( PED_EXCEPTION_ERROR, PED_EXCEPTION_RETRY_IGNORE_CANCEL, _("%s trying to sync %s to disk"), strerror (errno), dev->path); switch (ex_status) { case PED_EXCEPTION_IGNORE: free (last_failure); last_failure = strdup (dev->path); return 1; case PED_EXCEPTION_RETRY: break; case PED_EXCEPTION_UNHANDLED: ped_exception_catch (); case PED_EXCEPTION_CANCEL: return 0; } } return 1; } static int probe_standard_devices () { _ped_device_probe ("/dev/sd0"); _ped_device_probe ("/dev/sd1"); _ped_device_probe ("/dev/sd2"); _ped_device_probe ("/dev/sd3"); _ped_device_probe ("/dev/sd4"); _ped_device_probe ("/dev/sd5"); _ped_device_probe ("/dev/hd0"); _ped_device_probe ("/dev/hd1"); _ped_device_probe ("/dev/hd2"); _ped_device_probe ("/dev/hd3"); _ped_device_probe ("/dev/hd4"); _ped_device_probe ("/dev/hd5"); _ped_device_probe ("/dev/hd6"); _ped_device_probe ("/dev/hd7"); _ped_device_probe ("/dev/wd0"); _ped_device_probe ("/dev/wd1"); _ped_device_probe ("/dev/wd2"); _ped_device_probe ("/dev/wd3"); _ped_device_probe ("/dev/wd4"); _ped_device_probe ("/dev/wd5"); _ped_device_probe ("/dev/wd6"); _ped_device_probe ("/dev/wd7"); return 1; } static void gnu_probe_all () { probe_standard_devices (); } static char* gnu_partition_get_path (const PedPartition* part) { const char* dev_path = part->disk->dev->path; int result_len = strlen (dev_path) + 16; char* result; result = (char*) ped_malloc (result_len); if (!result) return NULL; snprintf (result, result_len, "%ss%d", dev_path, part->num); return result; } static int gnu_partition_is_busy (const PedPartition* part) { return 0; } static int gnu_disk_commit (PedDisk* disk) { return _reread_part_table (disk->dev); } static PedDeviceArchOps gnu_dev_ops = { _new: gnu_new, destroy: gnu_destroy, is_busy: gnu_is_busy, open: gnu_open, refresh_open: gnu_refresh_open, close: gnu_close, refresh_close: gnu_refresh_close, read: gnu_read, write: gnu_write, check: gnu_check, sync: gnu_sync, sync_fast: gnu_sync, probe_all: gnu_probe_all }; static PedDiskArchOps gnu_disk_ops = { partition_get_path: gnu_partition_get_path, partition_is_busy: gnu_partition_is_busy, disk_commit: gnu_disk_commit }; PedArchitecture ped_gnu_arch = { dev_ops: &gnu_dev_ops, disk_ops: &gnu_disk_ops };