b6c8975dbd08c9e3f78a2ab982f71e81c3fedb73
[platform/upstream/cryptsetup.git] / lib / utils_devpath.c
1 /*
2  * devname - search for device name
3  *
4  * Copyright (C) 2004 Jana Saout <jana@saout.de>
5  * Copyright (C) 2004-2007 Clemens Fruhwirth <clemens@endorphin.org>
6  * Copyright (C) 2009-2020 Red Hat, Inc. All rights reserved.
7  * Copyright (C) 2009-2020 Milan Broz
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License
11  * as published by the Free Software Foundation; either version 2
12  * of the License, or (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22  */
23
24 #include <string.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <unistd.h>
28 #include <dirent.h>
29 #include <errno.h>
30 #include <limits.h>
31 #include <sys/stat.h>
32 #include <sys/types.h>
33 #ifdef HAVE_SYS_SYSMACROS_H
34 # include <sys/sysmacros.h>     /* for major, minor */
35 #endif
36 #include "internal.h"
37
38 static char *__lookup_dev(char *path, dev_t dev, int dir_level, const int max_level)
39 {
40         struct dirent *entry;
41         struct stat st;
42         char *ptr;
43         char *result = NULL;
44         DIR *dir;
45         int space;
46
47         /* Ignore strange nested directories */
48         if (dir_level > max_level)
49                 return NULL;
50
51         path[PATH_MAX - 1] = '\0';
52         ptr = path + strlen(path);
53         *ptr++ = '/';
54         *ptr = '\0';
55         space = PATH_MAX - (ptr - path);
56
57         dir = opendir(path);
58         if (!dir)
59                 return NULL;
60
61         while((entry = readdir(dir))) {
62                 if (entry->d_name[0] == '.' ||
63                     !strncmp(entry->d_name, "..", 2))
64                         continue;
65
66                 if (dir_level == 0 &&
67                     (!strcmp(entry->d_name, "shm") ||
68                      !strcmp(entry->d_name, "fd") ||
69                      !strcmp(entry->d_name, "char") ||
70                      !strcmp(entry->d_name, "pts")))
71                         continue;
72
73                 strncpy(ptr, entry->d_name, space);
74                 if (stat(path, &st) < 0)
75                         continue;
76
77                 if (S_ISDIR(st.st_mode)) {
78                         result = __lookup_dev(path, dev, dir_level + 1, max_level);
79                         if (result)
80                                 break;
81                 } else if (S_ISBLK(st.st_mode)) {
82                         /* workaround: ignore dm-X devices, these are internal kernel names */
83                         if (dir_level == 0 && dm_is_dm_kernel_name(entry->d_name))
84                                 continue;
85                         if (st.st_rdev == dev) {
86                                 result = strdup(path);
87                                 break;
88                         }
89                 }
90         }
91
92         closedir(dir);
93         return result;
94 }
95
96 /*
97  * Non-udev systemd need to scan for device here.
98  */
99 static char *lookup_dev_old(int major, int minor)
100 {
101         dev_t dev;
102         char *result = NULL, buf[PATH_MAX + 1];
103
104         dev = makedev(major, minor);
105         strncpy(buf, "/dev", PATH_MAX);
106         buf[PATH_MAX] = '\0';
107
108         /* First try low level device */
109         if ((result = __lookup_dev(buf, dev, 0, 0)))
110                 return result;
111
112         /* If it is dm, try DM dir  */
113         if (dm_is_dm_device(major)) {
114                 strncpy(buf, dm_get_dir(), PATH_MAX);
115                 if ((result = __lookup_dev(buf, dev, 0, 0)))
116                         return result;
117         }
118
119         strncpy(buf, "/dev", PATH_MAX);
120         return  __lookup_dev(buf, dev, 0, 4);
121 }
122
123 /*
124  * Returns string pointing to device in /dev according to "major:minor" dev_id
125  */
126 char *crypt_lookup_dev(const char *dev_id)
127 {
128         int major, minor;
129         char link[PATH_MAX], path[PATH_MAX], *devname, *devpath = NULL;
130         struct stat st;
131         ssize_t len;
132
133         if (sscanf(dev_id, "%d:%d", &major, &minor) != 2)
134                 return NULL;
135
136         if (snprintf(path, sizeof(path), "/sys/dev/block/%s", dev_id) < 0)
137                 return NULL;
138
139         len = readlink(path, link, sizeof(link) - 1);
140         if (len < 0) {
141                 /* Without /sys use old scan */
142                 if (stat("/sys/dev/block", &st) < 0)
143                         return lookup_dev_old(major, minor);
144                 return NULL;
145         }
146
147         link[len] = '\0';
148         devname = strrchr(link, '/');
149         if (!devname)
150                 return NULL;
151         devname++;
152
153         if (dm_is_dm_kernel_name(devname))
154                 devpath = dm_device_path("/dev/mapper/", major, minor);
155         else if (snprintf(path, sizeof(path), "/dev/%s", devname) > 0)
156                 devpath = strdup(path);
157
158         /*
159          * Check that path is correct.
160          */
161         if (devpath && ((stat(devpath, &st) < 0) ||
162             !S_ISBLK(st.st_mode) ||
163             (st.st_rdev != makedev(major, minor)))) {
164                 free(devpath);
165                 /* Should never happen unless user mangles with dev nodes. */
166                 return lookup_dev_old(major, minor);
167         }
168
169         return devpath;
170 }
171
172 static int _read_uint64(const char *sysfs_path, uint64_t *value)
173 {
174         char tmp[64] = {0};
175         int fd, r;
176
177         if ((fd = open(sysfs_path, O_RDONLY)) < 0)
178                 return 0;
179         r = read(fd, tmp, sizeof(tmp));
180         close(fd);
181
182         if (r <= 0)
183                 return 0;
184
185         if (sscanf(tmp, "%" PRIu64, value) != 1)
186                 return 0;
187
188         return 1;
189 }
190
191 static int _sysfs_get_uint64(int major, int minor, uint64_t *value, const char *attr)
192 {
193         char path[PATH_MAX];
194
195         if (snprintf(path, sizeof(path), "/sys/dev/block/%d:%d/%s",
196                      major, minor, attr) < 0)
197                 return 0;
198
199         return _read_uint64(path, value);
200 }
201
202 static int _path_get_uint64(const char *sysfs_path, uint64_t *value, const char *attr)
203 {
204         char path[PATH_MAX];
205
206         if (snprintf(path, sizeof(path), "%s/%s",
207                      sysfs_path, attr) < 0)
208                 return 0;
209
210         return _read_uint64(path, value);
211 }
212
213 int crypt_dev_is_rotational(int major, int minor)
214 {
215         uint64_t val;
216
217         if (!_sysfs_get_uint64(major, minor, &val, "queue/rotational"))
218                 return 1; /* if failed, expect rotational disk */
219
220         return val ? 1 : 0;
221 }
222
223 int crypt_dev_is_partition(const char *dev_path)
224 {
225         uint64_t val;
226         struct stat st;
227
228         if (stat(dev_path, &st) < 0)
229                 return 0;
230
231         if (!S_ISBLK(st.st_mode))
232                 return 0;
233
234         if (!_sysfs_get_uint64(major(st.st_rdev), minor(st.st_rdev),
235                               &val, "partition"))
236                 return 0;
237
238         return val ? 1 : 0;
239 }
240
241 uint64_t crypt_dev_partition_offset(const char *dev_path)
242 {
243         uint64_t val;
244         struct stat st;
245
246         if (!crypt_dev_is_partition(dev_path))
247                 return 0;
248
249         if (stat(dev_path, &st) < 0)
250                 return 0;
251
252         if (!_sysfs_get_uint64(major(st.st_rdev), minor(st.st_rdev),
253                               &val, "start"))
254                 return 0;
255
256         return val;
257 }
258
259 /* Try to find partition which match offset and size on top level device */
260 char *crypt_get_partition_device(const char *dev_path, uint64_t offset, uint64_t size)
261 {
262         char link[PATH_MAX], path[PATH_MAX], part_path[PATH_MAX], *devname;
263         char *result = NULL;
264         struct stat st;
265         size_t devname_len;
266         ssize_t len;
267         struct dirent *entry;
268         DIR *dir;
269         uint64_t part_offset, part_size;
270
271         if (stat(dev_path, &st) < 0)
272                 return NULL;
273
274         if (!S_ISBLK(st.st_mode))
275                 return NULL;
276
277         if (snprintf(path, sizeof(path), "/sys/dev/block/%d:%d",
278                 major(st.st_rdev), minor(st.st_rdev)) < 0)
279                 return NULL;
280
281         dir = opendir(path);
282         if (!dir)
283                 return NULL;
284
285         len = readlink(path, link, sizeof(link) - 1);
286         if (len < 0) {
287                 closedir(dir);
288                 return NULL;
289         }
290
291         /* Get top level disk name for sysfs search */
292         link[len] = '\0';
293         devname = strrchr(link, '/');
294         if (!devname) {
295                 closedir(dir);
296                 return NULL;
297         }
298         devname++;
299
300         /* DM devices do not use kernel partitions. */
301         if (dm_is_dm_kernel_name(devname)) {
302                 closedir(dir);
303                 return NULL;
304         }
305
306         devname_len = strlen(devname);
307         while((entry = readdir(dir))) {
308                 if (strncmp(entry->d_name, devname, devname_len))
309                         continue;
310
311                 if (snprintf(part_path, sizeof(part_path), "%s/%s",
312                     path, entry->d_name) < 0)
313                         continue;
314
315                 if (stat(part_path, &st) < 0)
316                         continue;
317
318                 if (S_ISDIR(st.st_mode)) {
319                         if (!_path_get_uint64(part_path, &part_offset, "start") ||
320                             !_path_get_uint64(part_path, &part_size, "size"))
321                                 continue;
322                         if (part_offset == offset && part_size == size &&
323                             snprintf(part_path, sizeof(part_path), "/dev/%s",
324                                      entry->d_name) > 0) {
325                                 result = strdup(part_path);
326                                 break;
327                         }
328                 }
329         }
330         closedir(dir);
331
332         return result;
333 }
334
335 /* Try to find base device from partition */
336 char *crypt_get_base_device(const char *dev_path)
337 {
338         char link[PATH_MAX], path[PATH_MAX], part_path[PATH_MAX], *devname;
339         struct stat st;
340         ssize_t len;
341
342         if (!crypt_dev_is_partition(dev_path))
343                 return NULL;
344
345         if (stat(dev_path, &st) < 0)
346                 return NULL;
347
348         if (snprintf(path, sizeof(path), "/sys/dev/block/%d:%d",
349                 major(st.st_rdev), minor(st.st_rdev)) < 0)
350                 return NULL;
351
352         len = readlink(path, link, sizeof(link) - 1);
353         if (len < 0)
354                 return NULL;
355
356         /* Get top level disk name for sysfs search */
357         link[len] = '\0';
358         devname = strrchr(link, '/');
359         if (!devname)
360                 return NULL;
361         *devname = '\0';
362         devname = strrchr(link, '/');
363         if (!devname)
364                 return NULL;
365         devname++;
366
367         if (dm_is_dm_kernel_name(devname))
368                 return NULL;
369
370         snprintf(part_path, sizeof(part_path), "/dev/%s", devname);
371         return strdup(part_path);
372 }
373
374 int lookup_by_disk_id(const char *dm_uuid)
375 {
376         struct dirent *entry;
377         struct stat st;
378         int r = 0; /* not found */
379         DIR *dir = opendir("/dev/disk/by-id");
380
381         if (!dir)
382                 /* map ENOTDIR to ENOENT we'll handle both errors same */
383                 return errno == ENOTDIR ? -ENOENT : -errno;
384
385         while ((entry = readdir(dir))) {
386                 if (entry->d_name[0] == '.' ||
387                     !strncmp(entry->d_name, "..", 2))
388                         continue;
389
390                 if (fstatat(dirfd(dir), entry->d_name, &st, AT_SYMLINK_NOFOLLOW)) {
391                         r = -EINVAL;
392                         break;
393                 }
394
395                 if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode))
396                         continue;
397
398                 if (!strncmp(entry->d_name, dm_uuid, strlen(dm_uuid))) {
399                         r = 1;
400                         break;
401                 }
402         }
403
404         closedir(dir);
405
406         return r;
407 }
408
409 int lookup_by_sysfs_uuid_field(const char *dm_uuid, size_t max_len)
410 {
411         struct dirent *entry;
412         char subpath[PATH_MAX], uuid[max_len];
413         ssize_t s;
414         struct stat st;
415         int fd, len, r = 0; /* not found */
416         DIR *dir = opendir("/sys/block/");
417
418         if (!dir)
419                 /* map ENOTDIR to ENOENT we'll handle both errors same */
420                 return errno == ENOTDIR ? -ENOENT : -errno;
421
422         while (r != 1 && (entry = readdir(dir))) {
423                 if (entry->d_name[0] == '.' ||
424                     !strncmp(entry->d_name, "..", 2))
425                         continue;
426
427                 len = snprintf(subpath, PATH_MAX, "%s/%s", entry->d_name, "dm/uuid");
428                 if (len < 0 || len >= PATH_MAX) {
429                         r = -EINVAL;
430                         break;
431                 }
432
433                 /* looking for dm-X/dm/uuid file, symlinks are fine */
434                 fd = openat(dirfd(dir), subpath, O_RDONLY | O_CLOEXEC);
435                 if (fd < 0)
436                         continue;
437
438                 if (fstat(fd, &st) || !S_ISREG(st.st_mode)) {
439                         close(fd);
440                         continue;
441                 }
442
443                 /* reads binary data */
444                 s = read_buffer(fd, uuid, max_len - 1);
445                 if (s > 0) {
446                         uuid[s] = '\0';
447                         if (!strncmp(uuid, dm_uuid, strlen(dm_uuid)))
448                                 r = 1;
449                 }
450
451                 close(fd);
452         }
453
454         closedir(dir);
455
456         return r;
457 }