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}