1/* 2 * Device geometry helpers for hdparm and friends. 3 * Copyright (c) Mark Lord 2008 4 * 5 * You may use/distribute this freely, under the terms of either 6 * (your choice) the GNU General Public License version 2, 7 * or a BSD style license. 8 */ 9#define _FILE_OFFSET_BITS 64 10#include <stdlib.h> 11#include <unistd.h> 12#include <string.h> 13#include <stdio.h> 14#include <fcntl.h> 15#include <errno.h> 16#include <dirent.h> 17#include <sys/stat.h> 18#include <sys/ioctl.h> 19#include <linux/types.h> 20#include <linux/fs.h> 21 22#include "hdparm.h" 23 24static int get_driver_major (const char *driver, unsigned int *major) 25{ 26 static const char proc_devices[] = "/proc/devices"; 27 char buf[256]; 28 int err = 0; 29 FILE *fp = fopen(proc_devices, "r"); 30 31 if (fp == NULL) { 32 err = EIO; 33 } else { 34 while (fgets(buf, sizeof(buf) - 1, fp)) { 35 int len = strlen(buf); 36 if (len > 5 && buf[len - 1] == '\n') { 37 buf[len - 1] = '\0'; 38 if (buf[3] == ' ' && 0 == strcmp(buf + 4, driver)) { 39 *major = atoi(buf); 40 break; 41 } 42 } 43 } 44 } 45 if (err) 46 perror(proc_devices); 47 if (fp) 48 fclose(fp); 49 return err; 50} 51 52static unsigned int md_major (void) 53{ 54 static unsigned int maj = 0; 55 56 if (!maj) { 57 unsigned int val; 58 if (0 == get_driver_major("md", &val)) 59 maj = val; 60 } 61 return maj; 62} 63 64int fd_is_raid (int fd) 65{ 66 struct stat st; 67 68 if (!md_major()) 69 return 0; /* not a RAID device */ 70 if (fstat(fd, &st)) { 71 perror("fstat()"); 72 return 0; /* ugh.. shouldn't happen */ 73 } 74 return (major(st.st_rdev) == md_major()); 75} 76 77static int get_sector_count (int fd, __u64 *nsectors) 78{ 79 int err; 80 unsigned int nsects32 = 0; 81 __u64 nbytes64 = 0; 82 83 if (0 == sysfs_get_attr(fd, "size", "%llu", nsectors, NULL, 0)) 84 return 0; 85#ifdef BLKGETSIZE64 86 if (0 == ioctl(fd, BLKGETSIZE64, &nbytes64)) { // returns bytes 87 *nsectors = nbytes64 / 512; 88 return 0; 89 } 90#endif 91 err = ioctl(fd, BLKGETSIZE, &nsects32); // returns sectors 92 if (err == 0) { 93 *nsectors = nsects32; 94 } else { 95 err = errno; 96 perror(" BLKGETSIZE failed"); 97 } 98 return err; 99} 100 101/* 102 * "md" (RAID) devices have per-member "start" offsets. 103 * Realistically, we can only support raid1 arrays here, 104 * and only then when all members have the same "start" offsets. 105 */ 106static int get_raid1_start_lba (int fd, __u64 *start_lba) 107{ 108 char buf[32]; 109 unsigned int member, raid_disks; 110 __u64 start = 0, offset = 0; 111 112 if (sysfs_get_attr(fd, "md/level", "%s", buf, NULL, 0) 113 || sysfs_get_attr(fd, "md/raid_disks", "%u", &raid_disks, NULL, 0)) 114 return ENODEV; 115 if (strcmp(buf, "raid1") || !raid_disks) 116 return EINVAL; 117 for (member = 0; member < raid_disks; ++member) { 118 __u64 member_start, member_offset; 119 char member_path[32]; 120 sprintf(member_path, "md/rd%u/offset", member); 121 if (sysfs_get_attr(fd, member_path, "%llu", &member_offset, NULL, 0)) 122 member_offset = 0; 123 sprintf(member_path, "md/rd%u/block/dev", member); 124 if (sysfs_get_attr(fd, member_path, "%s", buf, NULL, 0)) 125 return EINVAL; 126 if (md_major() == (unsigned)atoi(buf)) /* disallow recursive RAIDs */ 127 return EINVAL; 128 sprintf(member_path, "md/rd%u/block/start", member); 129 if (sysfs_get_attr(fd, member_path, "%llu", &member_start, NULL, 0)) 130 return ENODEV; 131 if (member == 0) { 132 start = member_start; 133 offset = member_offset; 134 } else if (member_start != start || member_offset != offset) 135 return EINVAL; 136 /* FIXME? Should --fibmap should account for member_offset in calculations? */ 137 } 138 *start_lba = start; 139 return 0; 140} 141 142int get_dev_geometry (int fd, __u32 *cyls, __u32 *heads, __u32 *sects, 143 __u64 *start_lba, __u64 *nsectors) 144{ 145 static struct local_hd_geometry g; 146 static struct local_hd_big_geometry bg; 147 int err = 0, try_getgeo_big_first = 1; 148 149 if (nsectors) { 150 err = get_sector_count(fd, nsectors); 151 if (err) 152 return err; 153 } 154 155 if (start_lba) { 156 /* 157 * HDIO_GETGEO uses 32-bit fields on 32-bit architectures, 158 * so it cannot be relied upon for start_lba with very large drives >= 2TB. 159 */ 160 __u64 result; 161 if (0 == sysfs_get_attr(fd, "start", "%llu", &result, NULL, 0) 162 || 0 == get_raid1_start_lba(fd, &result)) 163 { 164 *start_lba = result; 165 start_lba = NULL; 166 try_getgeo_big_first = 0; /* if kernel has sysfs, it probably lacks GETGEO_BIG */ 167 } else if (fd_is_raid(fd)) { 168 *start_lba = START_LBA_UNKNOWN; /* RAID: no such thing as a "start_lba" */ 169 start_lba = NULL; 170 try_getgeo_big_first = 0; /* no point even trying it on RAID */ 171 } 172 } 173 174 if (cyls || heads || sects || start_lba) { 175 /* Skip HDIO_GETGEO_BIG (doesn't exist) on kernels with sysfs (>= 2.6.xx) */ 176 if (try_getgeo_big_first && !ioctl(fd, HDIO_GETGEO_BIG, &bg)) { 177 if (cyls) *cyls = bg.cylinders; 178 if (heads) *heads = bg.heads; 179 if (sects) *sects = bg.sectors; 180 if (start_lba) *start_lba = bg.start; 181 } else if (!ioctl(fd, HDIO_GETGEO, &g)) { 182 if (cyls) *cyls = g.cylinders; 183 if (heads) *heads = g.heads; 184 if (sects) *sects = g.sectors; 185 if (start_lba) *start_lba = g.start; 186 } else if (!try_getgeo_big_first && !ioctl(fd, HDIO_GETGEO_BIG, &bg)) { 187 if (cyls) *cyls = bg.cylinders; 188 if (heads) *heads = bg.heads; 189 if (sects) *sects = bg.sectors; 190 if (start_lba) *start_lba = bg.start; 191 } else { 192 err = errno; 193 perror(" HDIO_GETGEO failed"); 194 return err; 195 } 196 /* 197 * On all (32 and 64 bit) systems, the cyls value is bit-limited. 198 * So try and correct it using other info we have at hand. 199 */ 200 if (nsectors && cyls && heads && sects) { 201 __u64 hs = (*heads) * (*sects); 202 __u64 cyl = (*cyls); 203 __u64 chs = cyl * hs; 204 if (chs < (*nsectors)) 205 *cyls = (*nsectors) / hs; 206 } 207 } 208 209 return 0; 210} 211 212static int find_dev_in_directory (dev_t dev, const char *dir, char *path, int verbose) 213{ 214 DIR *dp; 215 struct dirent *entry; 216 unsigned int maj = major(dev), min = minor(dev); 217 218 *path = '\0'; 219 if (!(dp = opendir(dir))) { 220 int err = errno; 221 if (verbose) 222 perror(dir); 223 return err; 224 } 225 while ((entry = readdir(dp)) != NULL) { 226 if (entry->d_type == DT_UNKNOWN || entry->d_type == DT_BLK) { 227 struct stat st; 228 sprintf(path, "%s/%s", dir, entry->d_name); 229 if (stat(path, &st)) { 230 if (verbose) 231 perror(path); 232 } else if (S_ISBLK(st.st_mode)) { 233 if (maj == (unsigned)major(st.st_rdev) && min == (unsigned)minor(st.st_rdev)) { 234 closedir(dp); 235 return 0; 236 } 237 } 238 } 239 } 240 closedir(dp); 241 *path = '\0'; 242 if (verbose) 243 fprintf(stderr, "%d,%d: device not found in %s\n", major(dev), minor(dev), dir); 244 return ENOENT; 245} 246 247int get_dev_t_geometry (dev_t dev, __u32 *cyls, __u32 *heads, __u32 *sects, 248 __u64 *start_lba, __u64 *nsectors) 249{ 250 char path[PATH_MAX]; 251 int fd, err; 252 253 err = find_dev_in_directory (dev, "/dev", path, 1); 254 if (err) 255 return err; 256 257 fd = open(path, O_RDONLY|O_NONBLOCK); 258 if (fd == -1) { 259 err = errno; 260 perror(path); 261 return err; 262 } 263 264 err = get_dev_geometry(fd, cyls, heads, sects, start_lba, nsectors); 265 close(fd); 266 return err; 267} 268 269