summaryrefslogtreecommitdiffstats
path: root/rootdev.c
blob: e4c6d55cf3fdc671d4e4966663226b9a1691bbb7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 *
 * Implements root device discovery via sysfs with optional bells and whistles.
 */

#include "rootdev.h"

#include <ctype.h>
#include <dirent.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

/*
 * Limit prevents endless looping to find slave.
 * We currently have at most 2 levels, this allows
 * for future growth.
 */
#define MAX_SLAVE_DEPTH 8

static const char *kDefaultSearchPath = "/sys/block";
static const char *kDefaultDevPath = "/dev/block";

/* Encode the root device structuring here for Chromium OS */
static const char kActiveRoot[] = "/dev/ACTIVE_ROOT";
static const char kRootDev[] = "/dev/ROOT";
static const char kRootA[] = "/dev/ROOT0";
static const char kRootB[] = "/dev/ROOT1";

struct part_config {
  const char *name;
  int offset;
};

#define CHROMEOS_PRIMARY_PARTITION 3
static const struct part_config kPrimaryPart[] = { { kRootA,    0 },
                                                   { kRootDev, -3 },
                                                   { kRootB,    2 } };
#define CHROMEOS_SECONDARY_PARTITION 5
static const struct part_config kSecondaryPart[] = { { kRootB,    0 },
                                                     { kRootDev, -5 },
                                                     { kRootA,   -2 } };

/* The number of entries in a part_config so we could add RootC easily. */
static const int kPartitionEntries = 3;

/* Converts a file of %u:%u -> dev_t. */
static dev_t devt_from_file(const char *file) {
  char candidate[10];  /* TODO(wad) system-provided constant? */
  ssize_t bytes = 0;
  unsigned int major_num = 0;
  unsigned int minor_num = 0;
  dev_t dev = 0;
  int fd = -1;

  /* Never hang. Either get the data or return 0. */
  fd = open(file, O_NONBLOCK | O_RDONLY);
  if (fd < 0)
    return 0;
  bytes = read(fd, candidate, sizeof(candidate));
  close(fd);

  /* 0:0 should be considered the minimum size. */
  if (bytes < 3)
    return 0;
  candidate[bytes] = 0;
  if (sscanf(candidate, "%u:%u", &major_num, &minor_num) == 2) {
    /* candidate's size artificially limits the size of the converted
     * %u to safely convert to a signed int. */
    dev = makedev(major_num, minor_num);
  }
  return dev;
}

/* Walks sysfs and recurses into any directory/link that represents
 * a block device to find sub-devices (partitions) for dev.
 * If dev == 0, the name fo the first device in the directory will be returned.
 * Returns the device's name in "name" */
static int match_sysfs_device(char *name, size_t name_len,
                              const char *basedir, dev_t *dev, int depth) {
  int found = -1;
  size_t basedir_len;
  DIR *dirp = NULL;
  struct dirent *entry = NULL;
  struct dirent *next = NULL;
  char *working_path = NULL;
  long working_path_size = 0;

  if (!name || !name_len || !basedir || !dev) {
    warnx("match_sysfs_device: invalid arguments supplied");
    return -1;
  }
  basedir_len = strlen(basedir);
  if (!basedir_len) {
    warnx("match_sysfs_device: basedir must not be empty");
    return -1;
  }

  errno = 0;
  dirp = opendir(basedir);
  if (!dirp) {
     /* Don't complain if the directory doesn't exist. */
     if (errno != ENOENT)
       warn("match_sysfs_device:opendir(%s)", basedir);
     return found;
  }

  /* Grab a platform appropriate path to work with.
   * Ideally, this won't vary under sys/block. */
  working_path_size = pathconf(basedir, _PC_NAME_MAX) + 1;
  /* Fallback to PATH_MAX on any pathconf error. */
  if (working_path_size < 0)
    working_path_size = PATH_MAX;

  working_path = malloc(working_path_size);
  if (!working_path) {
    warn("malloc(dirent)");
    closedir(dirp);
    return found;
  }

  /* Allocate a properly sized entry. */
  entry = malloc(offsetof(struct dirent, d_name) + working_path_size);
  if (!entry) {
    warn("malloc(dirent)");
    free(working_path);
    closedir(dirp);
    return found;
  }

  while (readdir_r(dirp, entry, &next) == 0 && next) {
    size_t candidate_len = strlen(entry->d_name);
    size_t path_len = 0;
    dev_t found_devt = 0;
    /* Ignore the usual */
    if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
      continue;
    /* TODO(wad) determine how to best bubble up this case. */
    if (candidate_len > name_len)
      continue;
    /* Only traverse directories or symlinks (to directories ideally) */
    switch (entry->d_type) {
    case DT_UNKNOWN:
    case DT_DIR:
    case DT_LNK:
      break;
    default:
      continue;
    }
    /* Determine path to block device number */
    path_len = snprintf(working_path, working_path_size, "%s/%s/dev",
                        basedir, entry->d_name);
    /* Ignore if truncation occurs. */
    if (path_len != candidate_len + basedir_len + 5)
      continue;

    found_devt = devt_from_file(working_path);
    /* *dev == 0 is a wildcard. */
    if (!*dev || found_devt == *dev) {
      snprintf(name, name_len, "%s", entry->d_name);
      *dev = found_devt;
      found = 1;
      break;
    }

    /* Prevent infinite recursion on symlink loops by limiting depth. */
    if (depth > 5)
      break;

    /* Recurse one level for devices that may have a matching partition. */
    if (major(found_devt) == major(*dev) && minor(*dev) > minor(found_devt)) {
      sprintf(working_path, "%s/%s", basedir, entry->d_name);
      found = match_sysfs_device(name, name_len, working_path, dev, depth + 1);
      if (found > 0)
        break;
    }
  }

  free(working_path);
  free(entry);
  closedir(dirp);
  return found;
}

const char *rootdev_get_partition(const char *dst, size_t len) {
  const char *end = dst + strnlen(dst, len);
  const char *part = end - 1;
  if (!len)
    return NULL;

  if (!isdigit(*part--))
    return NULL;

  while (part > dst && isdigit(*part)) part--;
  part++;

  if (part >= end)
    return NULL;

  return part;
}

void rootdev_strip_partition(char *dst, size_t len) {
  char *part = (char *)rootdev_get_partition(dst, len);
  if (!part)
    return;
  /* For devices that end with a digit, the kernel uses a 'p'
   * as a separator. E.g., mmcblk1p2. */
  if (*(part - 1) == 'p')
    part--;
  *part = '\0';
}

int rootdev_symlink_active(const char *path) {
  int ret = 0;
  /* Don't overwrite an existing link. */
  errno = 0;
  if ((symlink(path, kActiveRoot)) && errno != EEXIST) {
    warn("failed to symlink %s -> %s", kActiveRoot, path);
    ret = -1;
  }
  return ret;
}

int rootdev_get_device(char *dst, size_t size, dev_t dev,
                       const char *search) {
  struct stat active_root_statbuf;

  if (search == NULL)
    search = kDefaultSearchPath;

  /* Check if the -s symlink exists. */
  if ((stat(kActiveRoot, &active_root_statbuf) == 0) &&
      active_root_statbuf.st_rdev == dev) {
    /* Note, if the link is not fully qualified, this won't be
     * either. */
    ssize_t len = readlink(kActiveRoot, dst, PATH_MAX);
    if (len > 0) {
      dst[len] = 0;
      return 0;
    }
    /* If readlink fails or is empty, fall through */
  }

  snprintf(dst, size, "%s", search);
  if (match_sysfs_device(dst, size, dst, &dev, 0) <= 0) {
    fprintf (stderr, "unable to find match\n");
    return 1;
  }

  return 0;
}

/*
 * rootdev_get_device_slave returns results in slave which
 * may be the original device or the name of the slave.
 *
 * Because slave and device may point to the same data,
 * must be careful how they are handled because slave
 * is modified (can't use snprintf).
 */
void rootdev_get_device_slave(char *slave, size_t size, dev_t *dev,
                              const char *device, const char *search) {
  char dst[PATH_MAX];
  int len = 0;
  int i;

  if (search == NULL)
    search = kDefaultSearchPath;

  /*
   * With stacked device mappers, we have to chain through all the levels
   * and find the last device. For example, verity can be stacked on bootcache
   * that is stacked on a disk partition.
   */
  if (slave != device)
    strncpy(slave, device, size);
  slave[size - 1] = '\0';
  for (i = 0; i < MAX_SLAVE_DEPTH; i++) {
    len = snprintf(dst, sizeof(dst), "%s/%s/slaves", search, slave);
    if (len != strlen(device) + strlen(search) + 8) {
      warnx("rootdev_get_device_slave: device name too long");
      return;
    }
    *dev = 0;
    if (match_sysfs_device(slave, size, dst, dev, 0) <= 0) {
      return;
    }
  }
  warnx("slave depth greater than %d at %s", i, slave);
}

int rootdev_create_devices(const char *name, dev_t dev, bool symlink) {
  int ret = 0;
  unsigned int major_num = major(dev);
  unsigned int minor_num = minor(dev);
  int i;
  const struct part_config *config;
  const char *part_s = rootdev_get_partition(name, strlen(name));

  if (part_s == NULL) {
    warnx("create_devices: unable to determine partition");
    return -1;
  }

  switch (atoi(part_s)) {
  case CHROMEOS_PRIMARY_PARTITION:
    config = kPrimaryPart;
    break;
  case CHROMEOS_SECONDARY_PARTITION:
    config = kSecondaryPart;
    break;
  default:
    warnx("create_devices: unable to determine partition: %s",
          part_s);
    return -1;
  }

  for (i = 0; i < kPartitionEntries; ++i) {
    dev = makedev(major_num, minor_num + config[i].offset);
    errno = 0;
    if (mknod(config[i].name,
              S_IFBLK | S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH,
              dev) && errno != EEXIST) {
      warn("failed to create %s", config[i].name);
      return -1;
    }
  }

  if (symlink)
    ret = rootdev_symlink_active(config[0].name);
  return ret;
}

int rootdev_get_path(char *path, size_t size, const char *device,
                     const char *dev_path) {
  int path_len;

  if (!dev_path)
    dev_path = kDefaultDevPath;

  if (!path || !size || !device)
    return -1;

  path_len = snprintf(path, size, "%s/%s", dev_path, device);
  if (path_len != strlen(dev_path) + 1 + strlen(device))
    return -1;

  // TODO(bsimonnet): We should check that |path| exists and is the right
  // device. We don't do this currently as OEMs can add custom SELinux rules
  // which may prevent us from accessing this.
  // See b/24267261.

  return 0;
}

int rootdev_wrapper(char *path, size_t size,
                    bool full, bool strip,
                    dev_t *dev,
                    const char *search, const char *dev_path) {
  int res = 0;
  char devname[PATH_MAX];
  if (!search)
    search = kDefaultSearchPath;
  if (!dev_path)
   dev_path = kDefaultDevPath;
  if (!dev)
    return -1;

  res = rootdev_get_device(devname, sizeof(devname), *dev, search);
  if (res != 0)
    return res;

  if (full)
    rootdev_get_device_slave(devname, sizeof(devname), dev, devname,
                             search);

  /* TODO(wad) we should really just track the block dev, partition number, and
   *           dev path.  When we rewrite this, we can track all the sysfs info
   *           in the class. */
  if (strip) {
    /* When we strip the partition, we don't want get_path to return non-zero
     * because of dev mismatch.  Passing in 0 tells it to not test. */
    *dev = 0;
    rootdev_strip_partition(devname, size);
  }

  res = rootdev_get_path(path, size, devname, dev_path);

  return res;
}

int rootdev(char *path, size_t size, bool full, bool strip) {
  struct stat root_statbuf;
  dev_t _root_dev, *root_dev = &_root_dev;

  /* Yields the containing dev_t in st_dev. */
  if (stat("/data", &root_statbuf) != 0)
    return -1;

  /* Some ABIs (like mips o32) are broken and the st_dev field isn't actually
   * a dev_t.  In that case, pass a pointer to a local dev_t who we took care
   * of truncating the value into.  On sane arches, gcc can optimize this to
   * the same code, so should only be a penalty when the ABI is broken. */
  if (sizeof(root_statbuf.st_dev) == sizeof(*root_dev)) {
    /* Cast is OK since we verified size here. */
    root_dev = (dev_t *)&root_statbuf.st_dev;
  } else {
    *root_dev = root_statbuf.st_dev;
  }

  return rootdev_wrapper(path,
                         size,
                         full,
                         strip,
                         root_dev,
                         NULL,  /* default /sys dir */
                         NULL);  /* default /dev dir */
}