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