Skip to content

Commit 1a40f4e

Browse files
committed
dissect: add --manifest switch to generate a UAPI.16 manifest file from a directory tree
The UAPI.16 is being discussed here: uapi-group/specifications#213
1 parent a500465 commit 1a40f4e

1 file changed

Lines changed: 222 additions & 5 deletions

File tree

src/dissect/dissect.c

Lines changed: 222 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
#include "pretty-print.h"
5050
#include "process-util.h"
5151
#include "recurse-dir.h"
52+
#include "rm-rf.h"
5253
#include "runtime-scope.h"
5354
#include "sha256.h"
5455
#include "shift-uid.h"
@@ -70,6 +71,7 @@ static enum {
7071
ACTION_DETACH,
7172
ACTION_LIST,
7273
ACTION_MTREE,
74+
ACTION_MANIFEST,
7375
ACTION_WITH,
7476
ACTION_COPY_FROM,
7577
ACTION_COPY_TO,
@@ -150,6 +152,7 @@ static int help(void) {
150152
"%1$s [OPTIONS...] --detach PATH\n"
151153
"%1$s [OPTIONS...] --list IMAGE\n"
152154
"%1$s [OPTIONS...] --mtree IMAGE\n"
155+
"%1$s [OPTIONS...] --manifest IMAGE\n"
153156
"%1$s [OPTIONS...] --with IMAGE [COMMAND…]\n"
154157
"%1$s [OPTIONS...] --copy-from IMAGE PATH [TARGET]\n"
155158
"%1$s [OPTIONS...] --copy-to IMAGE [SOURCE] PATH\n"
@@ -478,6 +481,11 @@ static int parse_argv(int argc, char *argv[]) {
478481
arg_flags |= DISSECT_IMAGE_READ_ONLY;
479482
break;
480483

484+
OPTION_LONG("manifest", NULL, "Show UAPI.16 manifest of OS image"):
485+
arg_action = ACTION_MANIFEST;
486+
arg_flags |= DISSECT_IMAGE_READ_ONLY;
487+
break;
488+
481489
OPTION_FULL(OPTION_STOPS_PARSING, /* sc= */ 0, "with", NULL, "Mount, run command, unmount"):
482490
arg_action = ACTION_WITH;
483491
break;
@@ -581,6 +589,7 @@ static int parse_argv(int argc, char *argv[]) {
581589

582590
case ACTION_LIST:
583591
case ACTION_MTREE:
592+
case ACTION_MANIFEST:
584593
if (strv_length(args) != 1)
585594
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
586595
"Expected an image file or directory path as only argument.");
@@ -1328,13 +1337,208 @@ static int mtree_print_item(
13281337
return RECURSE_DIR_CONTINUE;
13291338
}
13301339

1331-
static int action_list_or_mtree_or_copy_or_make_archive(DissectedImage *m, LoopDevice *d, int userns_fd) {
1340+
typedef struct HardlinkData {
1341+
int db_fd;
1342+
char *db_path;
1343+
uint64_t counter;
1344+
} HardlinkData;
1345+
1346+
static void hardlink_data_done(HardlinkData *hd) {
1347+
assert(hd);
1348+
1349+
if (hd->db_fd >= 0)
1350+
(void) rm_rf_children(TAKE_FD(hd->db_fd), REMOVE_PHYSICAL, /* root_dev= */ NULL);
1351+
1352+
if (hd->db_path)
1353+
hd->db_path = rmdir_and_free(hd->db_path);
1354+
}
1355+
1356+
static int manifest_print_item(
1357+
RecurseDirEvent event,
1358+
const char *path,
1359+
int dir_fd,
1360+
int inode_fd,
1361+
const struct dirent *de,
1362+
const struct statx *sx,
1363+
void *userdata) {
1364+
1365+
HardlinkData *hd = ASSERT_PTR(userdata);
1366+
int r;
1367+
1368+
/* Generates a UAPI.16 Manifest of the directory tree */
1369+
1370+
if (!IN_SET(event, RECURSE_DIR_ENTER, RECURSE_DIR_ENTRY))
1371+
return RECURSE_DIR_CONTINUE;
1372+
1373+
bool is_root = isempty(path);
1374+
1375+
/* We ignore this to avoid input is output cycles */
1376+
if (!is_root && (streq(path, "Uapi16Manifest") || startswith(path, "Uapi16Manifest.")))
1377+
return RECURSE_DIR_CONTINUE;
1378+
1379+
_cleanup_free_ char *data = NULL;
1380+
uint8_t hash[SHA256_DIGEST_SIZE];
1381+
uint64_t size = UINT64_MAX;
1382+
bool have_size = false, have_hash = false;
1383+
const char* type = NULL;
1384+
uint64_t hardlink_counter = 0;
1385+
1386+
if (FLAGS_SET(sx->stx_mask, STATX_NLINK|STATX_TYPE) && !S_ISDIR(sx->stx_mask) && sx->stx_nlink > 1) {
1387+
1388+
/* This is a hardlinked inode, apparently. Let's figure out its inode token */
1389+
1390+
bool is_empty = false;
1391+
if (hd->db_fd < 0) {
1392+
assert(!hd->db_path);
1393+
1394+
hd->db_fd = mkdtemp_open(/* template= */ NULL, O_NONBLOCK, &hd->db_path);
1395+
if (hd->db_fd < 0)
1396+
return log_error_errno(hd->db_fd, "Failed to create temporary directory: %m");
1397+
1398+
is_empty = true;
1399+
}
1400+
1401+
_cleanup_free_ struct file_handle *handle = NULL;
1402+
uint64_t unique_mnt_id = 0;
1403+
int mnt_id = 0;
1404+
r = name_to_handle_at_try_fid(inode_fd, /* path= */ NULL, &handle, &mnt_id, &unique_mnt_id, AT_EMPTY_PATH);
1405+
if (r < 0)
1406+
return log_error_errno(r, "Failed to acquire FID of path: %m");
1407+
1408+
struct sha256_ctx ctx;
1409+
sha256_init_ctx(&ctx);
1410+
sha256_process_bytes(&handle->handle_bytes, sizeof(handle->handle_bytes), &ctx);
1411+
sha256_process_bytes(&handle->handle_type, sizeof(handle->handle_type), &ctx);
1412+
sha256_process_bytes(&handle->f_handle, handle->handle_bytes, &ctx);
1413+
sha256_process_bytes(&mnt_id, sizeof(mnt_id), &ctx);
1414+
sha256_process_bytes(&unique_mnt_id, sizeof(unique_mnt_id), &ctx);
1415+
1416+
uint8_t fid_hash[SHA256_DIGEST_SIZE];
1417+
sha256_finish_ctx(&ctx, fid_hash);
1418+
1419+
_cleanup_free_ char *fhs = hexmem(fid_hash, sizeof(fid_hash));
1420+
if (!fhs)
1421+
return log_oom();
1422+
1423+
_cleanup_free_ char *slt = NULL;
1424+
r = is_empty ? -ENOENT : readlinkat_malloc(hd->db_fd, fhs, &slt);
1425+
if (r < 0) {
1426+
if (r != -ENOENT)
1427+
return log_error_errno(r, "Failed to symlink '%s': %m", fhs);
1428+
1429+
if (hd->counter >= UINT64_MAX)
1430+
return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Hardlink overflow.");
1431+
1432+
hardlink_counter = ++hd->counter;
1433+
1434+
slt = asprintf_safe("%" PRIu64, hardlink_counter);
1435+
if (!slt)
1436+
return log_oom();
1437+
1438+
if (S_ISREG(sx->stx_mode)) {
1439+
r = get_file_sha256(inode_fd, hash);
1440+
if (r < 0)
1441+
log_warning_errno(r, "Failed to calculate file SHA256 sum for '%s', ignoring: %m", path);
1442+
else {
1443+
_cleanup_free_ char *hs = hexmem(hash, sizeof(hash));
1444+
if (!hs)
1445+
return log_oom();
1446+
1447+
if (!strextend(&slt, ":", hs))
1448+
return log_oom();
1449+
1450+
have_hash = true;
1451+
}
1452+
}
1453+
1454+
if (symlinkat(slt, hd->db_fd, fhs) < 0)
1455+
return log_error_errno(errno, "Failed to create symlink '%s': %m", fhs);
1456+
} else {
1457+
char *colon = strchr(slt, ':');
1458+
1459+
if (colon) {
1460+
_cleanup_free_ void *unhexxed = NULL;
1461+
size_t unhexxed_size = 0;
1462+
r = unhexmem(colon +1, &unhexxed, &unhexxed_size);
1463+
if (r < 0)
1464+
return log_error_errno(r, "Failed to unhex file hash: %m");
1465+
if (unhexxed_size != sizeof(hash))
1466+
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Malformed hash entry: %m");
1467+
1468+
memcpy(hash, unhexxed, unhexxed_size);
1469+
have_hash = true;
1470+
*colon = 0;
1471+
}
1472+
1473+
r = safe_atou64(slt, &hardlink_counter);
1474+
if (r < 0)
1475+
return log_error_errno(r, "Failed to decode hardlink counter: %m");
1476+
}
1477+
}
1478+
1479+
if (FLAGS_SET(sx->stx_mask, STATX_TYPE)) {
1480+
switch (sx->stx_mode & S_IFMT) {
1481+
1482+
case S_IFREG:
1483+
size = sx->stx_size;
1484+
have_size = true;
1485+
1486+
if (!have_hash) {
1487+
r = get_file_sha256(inode_fd, hash);
1488+
if (r < 0)
1489+
log_warning_errno(r, "Failed to calculate file SHA256 sum for '%s', ignoring: %m", path);
1490+
else
1491+
have_hash = true;
1492+
}
1493+
1494+
break;
1495+
1496+
case S_IFLNK:
1497+
r = readlinkat_malloc(inode_fd, "", &data);
1498+
if (r < 0)
1499+
log_warning_errno(r, "Failed to read symlink '%s', ignoring: %m", path);
1500+
else {
1501+
size = strlen(data);
1502+
have_size = true;
1503+
}
1504+
}
1505+
1506+
/* Suppress the type on the top-level entry and on regular files. */
1507+
type = !is_root && !S_ISREG(sx->stx_mode) ? inode_type_to_string(sx->stx_mode) : NULL;
1508+
}
1509+
1510+
_cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL;
1511+
r = sd_json_buildo(
1512+
&j,
1513+
SD_JSON_BUILD_PAIR_CONDITION(is_root, "mediaType", JSON_BUILD_CONST_STRING("application/vnd.uapi.16.manifest")),
1514+
SD_JSON_BUILD_PAIR_CONDITION(!is_root, "name", SD_JSON_BUILD_STRING(path)),
1515+
SD_JSON_BUILD_PAIR_CONDITION(!!type, "type", SD_JSON_BUILD_STRING(type)),
1516+
SD_JSON_BUILD_PAIR_CONDITION(have_size, "size", SD_JSON_BUILD_UNSIGNED(size)),
1517+
SD_JSON_BUILD_PAIR_CONDITION(!!data, "contents", SD_JSON_BUILD_ARRAY(SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("literal", SD_JSON_BUILD_BASE64(data, size))))),
1518+
SD_JSON_BUILD_PAIR_CONDITION(have_hash, "sha256", SD_JSON_BUILD_HEX(hash, sizeof(hash))),
1519+
SD_JSON_BUILD_PAIR_CONDITION(FLAGS_SET(sx->stx_mask, STATX_TYPE) && (S_ISBLK(sx->stx_mode) || S_ISCHR(sx->stx_mode)), "major", SD_JSON_BUILD_UNSIGNED(sx->stx_rdev_major)),
1520+
SD_JSON_BUILD_PAIR_CONDITION(FLAGS_SET(sx->stx_mask, STATX_TYPE) && (S_ISBLK(sx->stx_mode) || S_ISCHR(sx->stx_mode)), "minor", SD_JSON_BUILD_UNSIGNED(sx->stx_rdev_minor)),
1521+
SD_JSON_BUILD_PAIR_CONDITION(FLAGS_SET(sx->stx_mask, STATX_TYPE|STATX_MODE) && !S_ISLNK(sx->stx_mode), "mode", SD_JSON_BUILD_UNSIGNED(sx->stx_mode & 07777)),
1522+
SD_JSON_BUILD_PAIR_CONDITION(FLAGS_SET(sx->stx_mask, STATX_UID), "uid", SD_JSON_BUILD_UNSIGNED(sx->stx_uid)),
1523+
SD_JSON_BUILD_PAIR_CONDITION(FLAGS_SET(sx->stx_mask, STATX_GID), "gid", SD_JSON_BUILD_UNSIGNED(sx->stx_gid)),
1524+
SD_JSON_BUILD_PAIR_CONDITION(hardlink_counter > 0, "inodeToken", SD_JSON_BUILD_UNSIGNED(hardlink_counter)));
1525+
if (r < 0)
1526+
return r;
1527+
1528+
r = sd_json_variant_dump(j, arg_json_format_flags | SD_JSON_FORMAT_SEQ | SD_JSON_FORMAT_FLUSH, stdout, /* prefix= */ NULL);
1529+
if (r < 0)
1530+
return r;
1531+
1532+
return RECURSE_DIR_CONTINUE;
1533+
}
1534+
1535+
static int action_list_or_mtree_or_manifest_or_copy_or_make_archive(DissectedImage *m, LoopDevice *d, int userns_fd) {
13321536
_cleanup_(umount_and_freep) char *mounted_dir = NULL;
13331537
_cleanup_free_ char *t = NULL;
13341538
const char *root;
13351539
int r;
13361540

1337-
assert(IN_SET(arg_action, ACTION_LIST, ACTION_MTREE, ACTION_COPY_FROM, ACTION_COPY_TO, ACTION_MAKE_ARCHIVE, ACTION_SHIFT));
1541+
assert(IN_SET(arg_action, ACTION_LIST, ACTION_MTREE, ACTION_MANIFEST, ACTION_COPY_FROM, ACTION_COPY_TO, ACTION_MAKE_ARCHIVE, ACTION_SHIFT));
13381542

13391543
/* Determine whether to copy ownership:
13401544
* --copy-ownership=yes: always try to preserve ownership
@@ -1535,7 +1739,8 @@ static int action_list_or_mtree_or_copy_or_make_archive(DissectedImage *m, LoopD
15351739
}
15361740

15371741
case ACTION_LIST:
1538-
case ACTION_MTREE: {
1742+
case ACTION_MTREE:
1743+
case ACTION_MANIFEST: {
15391744
_cleanup_close_ int dfd = -EBADF;
15401745

15411746
dfd = open(root, O_DIRECTORY|O_CLOEXEC|O_RDONLY);
@@ -1548,7 +1753,18 @@ static int action_list_or_mtree_or_copy_or_make_archive(DissectedImage *m, LoopD
15481753
r = recurse_dir(dfd, NULL, 0, UINT_MAX, RECURSE_DIR_SORT, list_print_item, NULL);
15491754
else if (arg_action == ACTION_MTREE)
15501755
r = recurse_dir(dfd, ".", STATX_TYPE|STATX_MODE|STATX_UID|STATX_GID|STATX_SIZE, UINT_MAX, RECURSE_DIR_SORT|RECURSE_DIR_INODE_FD|RECURSE_DIR_TOPLEVEL, mtree_print_item, NULL);
1551-
else
1756+
else if (arg_action == ACTION_MANIFEST) {
1757+
_cleanup_(hardlink_data_done) HardlinkData hd = {
1758+
.db_fd = -EBADF,
1759+
};
1760+
r = recurse_dir(dfd,
1761+
/* path= */ NULL,
1762+
STATX_TYPE|STATX_MODE|STATX_UID|STATX_GID|STATX_SIZE|STATX_NLINK,
1763+
/* n_depth_max= */ UINT_MAX,
1764+
RECURSE_DIR_SORT|RECURSE_DIR_INODE_FD|RECURSE_DIR_TOPLEVEL,
1765+
manifest_print_item,
1766+
&hd);
1767+
} else
15521768
assert_not_reached();
15531769
if (r < 0)
15541770
return log_error_errno(r, "Failed to list image: %m");
@@ -2145,11 +2361,12 @@ static int run(int argc, char *argv[]) {
21452361

21462362
case ACTION_LIST:
21472363
case ACTION_MTREE:
2364+
case ACTION_MANIFEST:
21482365
case ACTION_COPY_FROM:
21492366
case ACTION_COPY_TO:
21502367
case ACTION_MAKE_ARCHIVE:
21512368
case ACTION_SHIFT:
2152-
return action_list_or_mtree_or_copy_or_make_archive(m, d, userns_fd);
2369+
return action_list_or_mtree_or_manifest_or_copy_or_make_archive(m, d, userns_fd);
21532370

21542371
case ACTION_WITH:
21552372
return action_with(m, d);

0 commit comments

Comments
 (0)