/* $NetBSD: device.c,v 1.1.1.2 2009/12/02 00:26:34 haad Exp $ */ /* * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved. * Copyright (C) 2004-2007 Red Hat, Inc. All rights reserved. * * This file is part of LVM2. * * This copyrighted material is made available to anyone wishing to use, * modify, copy, or redistribute it subject to the terms and conditions * of the GNU Lesser General Public License v.2.1. * * You should have received a copy of the GNU Lesser 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 /* dirname, basename */ #include "lib.h" #include "lvm-types.h" #include "device.h" #include "metadata.h" #include "filter.h" #include "xlate.h" /* See linux/genhd.h and fs/partitions/msdos */ #define PART_MAGIC 0xAA55 #define PART_MAGIC_OFFSET UINT64_C(0x1FE) #define PART_OFFSET UINT64_C(0x1BE) struct partition { uint8_t boot_ind; uint8_t head; uint8_t sector; uint8_t cyl; uint8_t sys_ind; /* partition type */ uint8_t end_head; uint8_t end_sector; uint8_t end_cyl; uint32_t start_sect; uint32_t nr_sects; } __attribute__((packed)); static int _is_partitionable(struct device *dev) { int parts = max_partitions(MAJOR(dev->dev)); /* All MD devices are partitionable via blkext (as of 2.6.28) */ if (MAJOR(dev->dev) == md_major()) return 1; if ((parts <= 1) || (MINOR(dev->dev) % parts)) return 0; return 1; } static int _has_partition_table(struct device *dev) { int ret = 0; unsigned p; uint16_t buf[SECTOR_SIZE/sizeof(uint16_t)]; uint16_t *part_magic; struct partition *part; if (!dev_open(dev)) { stack; return -1; } if (!dev_read(dev, UINT64_C(0), sizeof(buf), &buf)) goto_out; /* FIXME Check for other types of partition table too */ /* Check for msdos partition table */ part_magic = buf + PART_MAGIC_OFFSET/sizeof(buf[0]); if ((*part_magic == xlate16(PART_MAGIC))) { part = (struct partition *) (buf + PART_OFFSET/sizeof(buf[0])); for (p = 0; p < 4; p++, part++) { /* Table is invalid if boot indicator not 0 or 0x80 */ if ((part->boot_ind & 0x7f)) { ret = 0; break; } /* Must have at least one non-empty partition */ if (part->nr_sects) ret = 1; } } out: if (!dev_close(dev)) stack; return ret; } int is_partitioned_dev(struct device *dev) { if (!_is_partitionable(dev)) return 0; return _has_partition_table(dev); } #if 0 #include #include #include #include #include #include #include #include #include #include #include int _get_partition_type(struct dev_filter *filter, struct device *d); #define MINOR_PART(dev) (MINOR((dev)->dev) % max_partitions(MINOR((dev)->dev))) int is_extended_partition(struct device *d) { return (MINOR_PART(d) > 4) ? 1 : 0; } struct device *dev_primary(struct dev_mgr *dm, struct device *d) { struct device *ret; ret = dev_by_dev(dm, d->dev - MINOR_PART(dm, d)); /* FIXME: Needs replacing with a 'refresh' */ if (!ret) { init_dev_scan(dm); ret = dev_by_dev(dm, d->dev - MINOR_PART(dm, d)); } return ret; } int partition_type_is_lvm(struct dev_mgr *dm, struct device *d) { int pt; pt = _get_partition_type(dm, d); if (!pt) { if (is_whole_disk(dm, d)) /* FIXME: Overloaded pt=0 in error cases */ return 1; else { log_error ("%s: missing partition table " "on partitioned device", d->name); return 0; } } if (is_whole_disk(dm, d)) { log_error("%s: looks to possess partition table", d->name); return 0; } /* check part type */ if (pt != LVM_PARTITION && pt != LVM_NEW_PARTITION) { log_error("%s: invalid partition type 0x%x " "(must be 0x%x)", d->name, pt, LVM_NEW_PARTITION); return 0; } if (pt == LVM_PARTITION) { log_error ("%s: old LVM partition type found - please change to 0x%x", d->name, LVM_NEW_PARTITION); return 0; } return 1; } int _get_partition_type(struct dev_mgr *dm, struct device *d) { int pv_handle = -1; struct device *primary; ssize_t read_ret; ssize_t bytes_read = 0; char *buffer; unsigned short *s_buffer; struct partition *part; loff_t offset = 0; loff_t extended_offset = 0; int part_sought; int part_found = 0; int first_partition = 1; int extended_partition = 0; int p; if (!(primary = dev_primary(dm, d))) { log_error ("Failed to find main device containing partition %s", d->name); return 0; } if (!(buffer = dm_malloc(SECTOR_SIZE))) { log_error("Failed to allocate partition table buffer"); return 0; } /* Get partition table */ if ((pv_handle = open(primary->name, O_RDONLY)) < 0) { log_error("%s: open failed: %s", primary->name, strerror(errno)); return 0; } s_buffer = (unsigned short *) buffer; part = (struct partition *) (buffer + 0x1be); part_sought = MINOR_PART(dm, d); do { bytes_read = 0; if (llseek(pv_handle, offset * SECTOR_SIZE, SEEK_SET) == -1) { log_error("%s: llseek failed: %s", primary->name, strerror(errno)); return 0; } while ((bytes_read < SECTOR_SIZE) && (read_ret = read(pv_handle, buffer + bytes_read, SECTOR_SIZE - bytes_read)) != -1) bytes_read += read_ret; if (read_ret == -1) { log_error("%s: read failed: %s", primary->name, strerror(errno)); return 0; } if (s_buffer[255] == 0xAA55) { if (is_whole_disk(dm, d)) return -1; } else return 0; extended_partition = 0; /* Loop through primary partitions */ for (p = 0; p < 4; p++) { if (part[p].sys_ind == DOS_EXTENDED_PARTITION || part[p].sys_ind == LINUX_EXTENDED_PARTITION || part[p].sys_ind == WIN98_EXTENDED_PARTITION) { extended_partition = 1; offset = extended_offset + part[p].start_sect; if (extended_offset == 0) extended_offset = part[p].start_sect; if (first_partition == 1) part_found++; } else if (first_partition == 1) { if (p == part_sought) { if (part[p].sys_ind == 0) { /* missing primary? */ return 0; } } else part_found++; } else if (!part[p].sys_ind) part_found++; if (part_sought == part_found) return part[p].sys_ind; } first_partition = 0; } while (extended_partition == 1); return 0; } #endif #ifdef linux int get_primary_dev(const char *sysfs_dir, struct device *dev, dev_t *result) { char path[PATH_MAX+1]; char temp_path[PATH_MAX+1]; char buffer[64]; struct stat info; FILE *fp; uint32_t pri_maj, pri_min; int ret = 0; /* check if dev is a partition */ if (dm_snprintf(path, PATH_MAX, "%s/dev/block/%d:%d/partition", sysfs_dir, (int)MAJOR(dev->dev), (int)MINOR(dev->dev)) < 0) { log_error("dm_snprintf partition failed"); return ret; } if (stat(path, &info) == -1) { if (errno != ENOENT) log_sys_error("stat", path); return ret; } /* * extract parent's path from the partition's symlink, e.g.: * - readlink /sys/dev/block/259:0 = ../../block/md0/md0p1 * - dirname ../../block/md0/md0p1 = ../../block/md0 * - basename ../../block/md0/md0 = md0 * Parent's 'dev' sysfs attribute = /sys/block/md0/dev */ if (readlink(dirname(path), temp_path, PATH_MAX) < 0) { log_sys_error("readlink", path); return ret; } if (dm_snprintf(path, PATH_MAX, "%s/block/%s/dev", sysfs_dir, basename(dirname(temp_path))) < 0) { log_error("dm_snprintf dev failed"); return ret; } /* finally, parse 'dev' attribute and create corresponding dev_t */ if (stat(path, &info) == -1) { if (errno == ENOENT) log_error("sysfs file %s does not exist", path); else log_sys_error("stat", path); return ret; } fp = fopen(path, "r"); if (!fp) { log_sys_error("fopen", path); return ret; } if (!fgets(buffer, sizeof(buffer), fp)) { log_sys_error("fgets", path); goto out; } if (sscanf(buffer, "%d:%d", &pri_maj, &pri_min) != 2) { log_error("sysfs file %s not in expected MAJ:MIN format: %s", path, buffer); goto out; } *result = MKDEV(pri_maj, pri_min); ret = 1; out: if (fclose(fp)) log_sys_error("fclose", path); return ret; } static unsigned long _dev_topology_attribute(const char *attribute, const char *sysfs_dir, struct device *dev) { const char *sysfs_fmt_str = "%s/dev/block/%d:%d/%s"; char path[PATH_MAX+1], buffer[64]; FILE *fp; struct stat info; dev_t uninitialized_var(primary); unsigned long result = 0UL; if (!attribute || !*attribute) return_0; if (!sysfs_dir || !*sysfs_dir) return_0; if (dm_snprintf(path, PATH_MAX, sysfs_fmt_str, sysfs_dir, (int)MAJOR(dev->dev), (int)MINOR(dev->dev), attribute) < 0) { log_error("dm_snprintf %s failed", attribute); return 0; } /* * check if the desired sysfs attribute exists * - if not: either the kernel doesn't have topology support * or the device could be a partition */ if (stat(path, &info) == -1) { if (errno != ENOENT) { log_sys_error("stat", path); return 0; } if (!get_primary_dev(sysfs_dir, dev, &primary)) return 0; /* get attribute from partition's primary device */ if (dm_snprintf(path, PATH_MAX, sysfs_fmt_str, sysfs_dir, (int)MAJOR(primary), (int)MINOR(primary), attribute) < 0) { log_error("primary dm_snprintf %s failed", attribute); return 0; } if (stat(path, &info) == -1) { if (errno != ENOENT) log_sys_error("stat", path); return 0; } } if (!(fp = fopen(path, "r"))) { log_sys_error("fopen", path); return 0; } if (!fgets(buffer, sizeof(buffer), fp)) { log_sys_error("fgets", path); goto out; } if (sscanf(buffer, "%lu", &result) != 1) { log_error("sysfs file %s not in expected format: %s", path, buffer); goto out; } log_very_verbose("Device %s %s is %lu bytes.", dev_name(dev), attribute, result); out: if (fclose(fp)) log_sys_error("fclose", path); return result >> SECTOR_SHIFT; } unsigned long dev_alignment_offset(const char *sysfs_dir, struct device *dev) { return _dev_topology_attribute("alignment_offset", sysfs_dir, dev); } unsigned long dev_minimum_io_size(const char *sysfs_dir, struct device *dev) { return _dev_topology_attribute("queue/minimum_io_size", sysfs_dir, dev); } unsigned long dev_optimal_io_size(const char *sysfs_dir, struct device *dev) { return _dev_topology_attribute("queue/optimal_io_size", sysfs_dir, dev); } #else int get_primary_dev(const char *sysfs_dir, struct device *dev, dev_t *result) { return 0; } unsigned long dev_alignment_offset(const char *sysfs_dir, struct device *dev) { return 0UL; } unsigned long dev_minimum_io_size(const char *sysfs_dir, struct device *dev) { return 0UL; } unsigned long dev_optimal_io_size(const char *sysfs_dir, struct device *dev) { return 0UL; } #endif