master xplshn/aruu / shared / libutil / diskutil.c
  1/* See LICENSE file for copyright and license details. */
  2#include "../diskutil.h"
  3#include "../util.h"
  4
  5#include <sys/types.h>
  6#include <sys/stat.h>
  7#include <fcntl.h>
  8#include <unistd.h>
  9#include <stdio.h>
 10#include <stdlib.h>
 11#include <string.h>
 12#include <errno.h>
 13
 14#if defined(__linux__)
 15#include <sys/ioctl.h>
 16#include <linux/fs.h>
 17#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__Apple__)
 18#include <sys/ioctl.h>
 19#include <sys/disk.h>
 20#if defined(__OpenBSD__) || defined(__NetBSD__)
 21#include <sys/dkio.h>
 22#include <sys/disklabel.h>
 23#include <sys/sysctl.h>
 24#elif defined(__FreeBSD__)
 25#include <sys/disk.h>
 26#include <sys/sysctl.h>
 27#include <dirent.h>
 28#endif
 29#endif
 30
 31int
 32blockdev_open(struct BlockDev *dev, const char *path, int writable)
 33{
 34	struct stat st;
 35	int fd, flags;
 36
 37	flags = writable ? O_RDWR : O_RDONLY;
 38	fd = open(path, flags);
 39	if (fd < 0)
 40		return -1;
 41
 42	if (fstat(fd, &st) < 0) {
 43		close(fd);
 44		return -1;
 45	}
 46
 47	dev->fd = fd;
 48	dev->size = st.st_size;
 49	dev->sec_size = 512;
 50
 51	if (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode)) {
 52#if defined(__linux__)
 53		uint64_t size_bytes = 0;
 54		int sec_size = 0;
 55		if (ioctl(fd, BLKGETSIZE64, &size_bytes) >= 0)
 56			dev->size = size_bytes;
 57		if (ioctl(fd, BLKSSZGET, &sec_size) >= 0)
 58			dev->sec_size = sec_size;
 59#elif defined(__FreeBSD__)
 60		off_t size_bytes = 0;
 61		unsigned int sec_size = 0;
 62		if (ioctl(fd, DIOCGMEDIASIZE, &size_bytes) >= 0)
 63			dev->size = size_bytes;
 64		if (ioctl(fd, DIOCGSECTORSIZE, &sec_size) >= 0)
 65			dev->sec_size = sec_size;
 66#elif defined(__OpenBSD__) || defined(__NetBSD__)
 67		struct disklabel dl;
 68		if (ioctl(fd, DIOCGDINFO, &dl) >= 0) {
 69			dev->size = (uint64_t)dl.d_secsize * DL_GETDSIZE(&dl);
 70			dev->sec_size = dl.d_secsize;
 71		}
 72#endif
 73	}
 74
 75	return 0;
 76}
 77
 78int
 79blockdev_read(struct BlockDev *dev, uint64_t sector, void *buf, size_t sector_count)
 80{
 81	off_t offset = (off_t)sector * dev->sec_size;
 82	size_t size = sector_count * dev->sec_size;
 83	ssize_t n;
 84
 85	if (lseek(dev->fd, offset, SEEK_SET) == (off_t)-1)
 86		return -1;
 87
 88	n = read(dev->fd, buf, size);
 89	if (n < 0 || (size_t)n != size)
 90		return -1;
 91
 92	return 0;
 93}
 94
 95int
 96blockdev_write(struct BlockDev *dev, uint64_t sector, const void *buf, size_t sector_count)
 97{
 98	off_t offset = (off_t)sector * dev->sec_size;
 99	size_t size = sector_count * dev->sec_size;
100	ssize_t n;
101
102	if (lseek(dev->fd, offset, SEEK_SET) == (off_t)-1)
103		return -1;
104
105	n = write(dev->fd, buf, size);
106	if (n < 0 || (size_t)n != size)
107		return -1;
108
109	return 0;
110}
111
112void
113blockdev_close(struct BlockDev *dev)
114{
115	if (dev->fd >= 0) {
116		close(dev->fd);
117		dev->fd = -1;
118	}
119}
120
121struct BlockDevInfo *
122blockdev_get_list(void)
123{
124#if defined(__linux__)
125	FILE *fp;
126	char line[256];
127	struct BlockDevInfo *head = NULL, *tail = NULL;
128	unsigned int major, minor;
129	unsigned long long blocks;
130	char name[64];
131
132	fp = fopen("/proc/partitions", "r");
133	if (!fp)
134		return NULL;
135
136	while (fgets(line, sizeof(line), fp)) {
137		if (sscanf(line, " %u %u %llu %63s", &major, &minor, &blocks, name) != 4)
138			continue;
139
140		struct BlockDevInfo *info = calloc(1, sizeof(*info));
141		if (!info)
142			break;
143
144		strlcpy(info->name, name, sizeof(info->name));
145		info->size = blocks * 1024;
146		info->major = major;
147		info->minor = minor;
148
149		/* check if this is a partition of an existing disk */
150		struct BlockDevInfo *curr;
151		int is_part = 0;
152		for (curr = head; curr; curr = curr->next) {
153			size_t len = strlen(curr->name);
154			if (strncmp(name, curr->name, len) == 0) {
155				char c = name[len];
156				if ((c >= '0' && c <= '9') || (c == 'p' && name[len+1] >= '0' && name[len+1] <= '9')) {
157					is_part = 1;
158					strlcpy(info->type, "part", sizeof(info->type));
159					struct BlockDevInfo *p = curr->parts;
160					if (!p) {
161						curr->parts = info;
162					} else {
163						while (p->next)
164							p = p->next;
165						p->next = info;
166					}
167					break;
168				}
169			}
170		}
171
172		if (!is_part) {
173			strlcpy(info->type, "disk", sizeof(info->type));
174			if (!head) {
175				head = info;
176			} else {
177				tail->next = info;
178			}
179			tail = info;
180		}
181	}
182	fclose(fp);
183
184	/* parse mounts to populate mountpoint and fstype */
185	FILE *mfile = fopen("/proc/mounts", "r");
186	if (mfile) {
187		char mline[512];
188		char devpath[128], mnt[256], fs[64];
189		while (fgets(mline, sizeof(mline), mfile)) {
190			if (sscanf(mline, "%127s %255s %63s", devpath, mnt, fs) == 3) {
191				char *devname = devpath;
192				if (strncmp(devpath, "/dev/", 5) == 0)
193					devname = devpath + 5;
194				struct BlockDevInfo *curr;
195				for (curr = head; curr; curr = curr->next) {
196					if (strcmp(curr->name, devname) == 0) {
197						strlcpy(curr->mountpoint, mnt, sizeof(curr->mountpoint));
198						strlcpy(curr->fstype, fs, sizeof(curr->fstype));
199					}
200					struct BlockDevInfo *part;
201					for (part = curr->parts; part; part = part->next) {
202						if (strcmp(part->name, devname) == 0) {
203							strlcpy(part->mountpoint, mnt, sizeof(part->mountpoint));
204							strlcpy(part->fstype, fs, sizeof(part->fstype));
205						}
206					}
207				}
208			}
209		}
210		fclose(mfile);
211	}
212
213	return head;
214
215#elif defined(__FreeBSD__)
216	char disks[1024];
217	size_t len = sizeof(disks);
218	struct BlockDevInfo *head = NULL, *tail = NULL;
219
220	if (sysctlbyname("kern.disks", disks, &len, NULL, 0) < 0)
221		return NULL;
222
223	char *tok, *ptr = disks;
224	while ((tok = strsep(&ptr, " \t")) != NULL) {
225		if (!*tok)
226			continue;
227
228		struct BlockDevInfo *info = calloc(1, sizeof(*info));
229		if (!info)
230			break;
231
232		strlcpy(info->name, tok, sizeof(info->name));
233		strlcpy(info->type, "disk", sizeof(info->type));
234
235		char devpath[64];
236		snprintf(devpath, sizeof(devpath), "/dev/%s", tok);
237		struct BlockDev dev;
238		if (blockdev_open(&dev, devpath, 0) == 0) {
239			info->size = dev.size;
240			blockdev_close(&dev);
241		}
242
243		DIR *dir = opendir("/dev");
244		if (dir) {
245			struct dirent *de;
246			size_t disk_len = strlen(tok);
247			while ((de = readdir(dir)) != NULL) {
248				if (strncmp(de->d_name, tok, disk_len) == 0) {
249					char c = de->d_name[disk_len];
250					if (c == 's' || c == 'p') {
251						struct BlockDevInfo *part = calloc(1, sizeof(*part));
252						if (part) {
253							strlcpy(part->name, de->d_name, sizeof(part->name));
254							strlcpy(part->type, "part", sizeof(part->type));
255							snprintf(devpath, sizeof(devpath), "/dev/%s", de->d_name);
256							if (blockdev_open(&dev, devpath, 0) == 0) {
257								part->size = dev.size;
258								blockdev_close(&dev);
259							}
260							struct BlockDevInfo *p = info->parts;
261							if (!p) {
262								info->parts = part;
263							} else {
264								while (p->next)
265									p = p->next;
266								p->next = part;
267							}
268						}
269					}
270				}
271			}
272			closedir(dir);
273		}
274
275		if (!head) {
276			head = info;
277		} else {
278			tail->next = info;
279		}
280		tail = info;
281	}
282
283	struct statfs *mntbuf;
284	int mntsize = getmntinfo(&mntbuf, MNT_NOWAIT);
285	for (int i = 0; i < mntsize; i++) {
286		char *devpath = mntbuf[i].f_mntfromname;
287		char *devname = devpath;
288		if (strncmp(devpath, "/dev/", 5) == 0)
289			devname = devpath + 5;
290		struct BlockDevInfo *curr;
291		for (curr = head; curr; curr = curr->next) {
292			if (strcmp(curr->name, devname) == 0) {
293				strlcpy(curr->mountpoint, mntbuf[i].f_mntonname, sizeof(curr->mountpoint));
294				strlcpy(curr->fstype, mntbuf[i].f_fstypename, sizeof(curr->fstype));
295			}
296			struct BlockDevInfo *part;
297			for (part = curr->parts; part; part = part->next) {
298				if (strcmp(part->name, devname) == 0) {
299					strlcpy(part->mountpoint, mntbuf[i].f_mntonname, sizeof(part->mountpoint));
300					strlcpy(part->fstype, mntbuf[i].f_fstypename, sizeof(part->fstype));
301				}
302			}
303		}
304	}
305
306	return head;
307
308#elif defined(__OpenBSD__)
309	char disknames[1024];
310	size_t len = sizeof(disknames);
311	int mib[2] = { CTL_HW, HW_DISKNAMES };
312	struct BlockDevInfo *head = NULL, *tail = NULL;
313
314	if (sysctl(mib, 2, disknames, &len, NULL, 0) < 0)
315		return NULL;
316
317	char *tok, *ptr = disknames;
318	while ((tok = strsep(&ptr, ",")) != NULL) {
319		char *colon = strchr(tok, ':');
320		if (colon)
321			*colon = '\0';
322		if (!*tok)
323			continue;
324
325		struct BlockDevInfo *info = calloc(1, sizeof(*info));
326		if (!info)
327			break;
328
329		strlcpy(info->name, tok, sizeof(info->name));
330		strlcpy(info->type, "disk", sizeof(info->type));
331
332		char devpath[64];
333		snprintf(devpath, sizeof(devpath), "/dev/r%sc", tok);
334		struct BlockDev dev;
335		if (blockdev_open(&dev, devpath, 0) == 0) {
336			info->size = dev.size;
337			blockdev_close(&dev);
338		}
339
340		for (char c = 'a'; c <= 'p'; c++) {
341			if (c == 'c')
342				continue;
343			snprintf(devpath, sizeof(devpath), "/dev/r%s%c", tok, c);
344			if (blockdev_open(&dev, devpath, 0) == 0) {
345				struct BlockDevInfo *part = calloc(1, sizeof(*part));
346				if (part) {
347					snprintf(part->name, sizeof(part->name), "%s%c", tok, c);
348					strlcpy(part->type, "part", sizeof(part->type));
349					part->size = dev.size;
350					struct BlockDevInfo *p = info->parts;
351					if (!p) {
352						info->parts = part;
353					} else {
354						while (p->next)
355							p = p->next;
356						p->next = part;
357					}
358				}
359				blockdev_close(&dev);
360			}
361		}
362
363		if (!head) {
364			head = info;
365		} else {
366			tail->next = info;
367		}
368		tail = info;
369	}
370
371	struct statfs *mntbuf;
372	int mntsize = getmntinfo(&mntbuf, MNT_NOWAIT);
373	for (int i = 0; i < mntsize; i++) {
374		char *devpath = mntbuf[i].f_mntfromname;
375		char *devname = devpath;
376		if (strncmp(devpath, "/dev/", 5) == 0)
377			devname = devpath + 5;
378		struct BlockDevInfo *curr;
379		for (curr = head; curr; curr = curr->next) {
380			if (strcmp(curr->name, devname) == 0) {
381				strlcpy(curr->mountpoint, mntbuf[i].f_mntonname, sizeof(curr->mountpoint));
382				strlcpy(curr->fstype, mntbuf[i].f_fstypename, sizeof(curr->fstype));
383			}
384			struct BlockDevInfo *part;
385			for (part = curr->parts; part; part = part->next) {
386				if (strcmp(part->name, devname) == 0) {
387					strlcpy(part->mountpoint, mntbuf[i].f_mntonname, sizeof(part->mountpoint));
388					strlcpy(part->fstype, mntbuf[i].f_fstypename, sizeof(part->fstype));
389				}
390			}
391		}
392	}
393
394	return head;
395
396#else
397	struct BlockDevInfo *head = NULL, *tail = NULL;
398	char devname[16];
399	for (char c = 'a'; c <= 'z'; c++) {
400		snprintf(devname, sizeof(devname), "sd%c", c);
401		char devpath[32];
402		snprintf(devpath, sizeof(devpath), "/dev/%s", devname);
403		struct BlockDev dev;
404		if (blockdev_open(&dev, devpath, 0) == 0) {
405			struct BlockDevInfo *info = calloc(1, sizeof(*info));
406			if (info) {
407				strlcpy(info->name, devname, sizeof(info->name));
408				strlcpy(info->type, "disk", sizeof(info->type));
409				info->size = dev.size;
410				if (!head) {
411					head = info;
412				} else {
413					tail->next = info;
414				}
415				tail = info;
416			}
417			blockdev_close(&dev);
418		}
419	}
420	return head;
421#endif
422}
423
424void
425blockdev_free_list(struct BlockDevInfo *list)
426{
427	struct BlockDevInfo *curr = list;
428	while (curr) {
429		struct BlockDevInfo *next = curr->next;
430		struct BlockDevInfo *part = curr->parts;
431		while (part) {
432			struct BlockDevInfo *next_part = part->next;
433			free(part);
434			part = next_part;
435		}
436		free(curr);
437		curr = next;
438	}
439}