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