Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for decoding erofs into lcfs_node, and some tooling using it #185

Merged
merged 19 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion tools/Makefile.am
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
bin_PROGRAMS = mkcomposefs
COMPOSEFS_HASH_CFLAGS = -DUSE_OBSTACK=0 -DTESTING=0 -DUSE_DIFF_HASH=0

bin_PROGRAMS = mkcomposefs composefs-info
sbin_PROGRAMS = mount.composefs
noinst_PROGRAMS =

Expand All @@ -21,6 +23,10 @@ mount_composefs_LDADD = ../libcomposefs/libcomposefs.la $(LIBCRYPTO_LIBS)
composefs_from_json_SOURCES = composefs-from-json.c read-file.c read-file.h
composefs_from_json_LDADD = ../libcomposefs/libcomposefs.la $(LIBS_YAJL) $(LIBCRYPTO_LIBS) $(LIBS_SECCOMP)

composefs_info_SOURCES = composefs-info.c ../libcomposefs/hash.c
composefs_info_CFLAGS = $(AM_CFLAGS) $(COMPOSEFS_HASH_CFLAGS)
composefs_info_LDADD = ../libcomposefs/libcomposefs.la

composefs_fuse_SOURCES = cfs-fuse.c
composefs_fuse_LDADD = ../libcomposefs/libcomposefs.la $(FUSE3_LIBS)
composefs_fuse_CFLAGS = $(AM_CFLAGS) $(FUSE3_CFLAGS)
Expand Down
294 changes: 294 additions & 0 deletions tools/composefs-info.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,294 @@
/* lcfs
Copyright (C) 2023 Alexander Larsson <[email protected]>

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#define _GNU_SOURCE

#include "config.h"

#include "libcomposefs/lcfs-writer.h"
#include "libcomposefs/lcfs-utils.h"
#include "libcomposefs/lcfs-internal.h"
#include "libcomposefs/hash.h"

#include <stdio.h>
#include <string.h>
#include <err.h>
#include <error.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <ctype.h>

#define ESCAPE_STANDARD 0
#define NOESCAPE_SPACE (1 << 0)
#define ESCAPE_EQUAL (1 << 1)
#define ESCAPE_LONE_DASH (1 << 2)

static void print_escaped(const char *val, ssize_t len, int escape)
{
bool noescape_space = (escape & NOESCAPE_SPACE) != 0;
bool escape_equal = (escape & ESCAPE_EQUAL) != 0;
bool escape_lone_dash = (escape & ESCAPE_LONE_DASH) != 0;

if (len < 0)
len = strlen(val);

if (escape_lone_dash && len == 1 && val[0] == '-') {
printf("\\x%.2x", val[0]);
return;
}

for (size_t i = 0; i < len; i++) {
uint8_t c = val[i];
bool hex_escape = false;
const char *special = NULL;
switch (c) {
case '\\':
special = "\\\\";
break;
case '\n':
special = "\\n";
break;
case '\r':
special = "\\r";
break;
case '\t':
special = "\\t";
break;
case '=':
hex_escape = escape_equal;
break;
default:
if (noescape_space)
hex_escape = !isprint(c);
else
hex_escape = !isgraph(c);
break;
}

if (special != NULL)
printf("%s", special);
else if (hex_escape)
printf("\\x%.2x", c);
else
printf("%c", c);
}
}

static void print_node(struct lcfs_node_s *node, char *parent_path)
{
for (size_t i = 0; i < lcfs_node_get_n_children(node); i++) {
struct lcfs_node_s *child = lcfs_node_get_child(node, i);
cleanup_free char *path = NULL;

asprintf(&path, "%s/%s", parent_path, lcfs_node_get_name(child));

uint32_t mode = lcfs_node_get_mode(child);
uint32_t type = mode & S_IFMT;
const char *payload = lcfs_node_get_payload(child);

print_escaped(path, -1, NOESCAPE_SPACE);

if (type == S_IFDIR) {
printf("/\t");
} else if (type == S_IFLNK) {
printf("\t-> ");
print_escaped(payload, -1, ESCAPE_STANDARD);
} else if (type == S_IFREG && payload) {
printf("\t@ ");
print_escaped(payload, -1, ESCAPE_STANDARD);
}
printf("\n");

print_node(child, path);
}
}

static void digest_to_string(const uint8_t *csum, char *buf)
{
static const char hexchars[] = "0123456789abcdef";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I can tell, this would literally be the fourth copy of this in tree...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ooops :)

uint32_t i, j;

for (i = 0, j = 0; i < LCFS_DIGEST_SIZE; i++, j += 2) {
uint8_t byte = csum[i];
buf[j] = hexchars[byte >> 4];
buf[j + 1] = hexchars[byte & 0xF];
}
buf[j] = '\0';
}

static void dump_node(struct lcfs_node_s *node, char *path)
{
struct lcfs_node_s *target;
struct timespec mtime;
const char *payload;
const uint8_t *digest;

target = lcfs_node_get_hardlink_target(node);
if (target == NULL)
target = node;

lcfs_node_get_mtime(target, &mtime);
payload = lcfs_node_get_payload(target);
digest = lcfs_node_get_fsverity_digest(target);

print_escaped(*path == 0 ? "/" : path, -1, ESCAPE_STANDARD);
printf(" %" PRIu64 " %s%o %u %u %u %u %" PRIi64 ".%u ",
lcfs_node_get_size(target), target == node ? "" : "@",
lcfs_node_get_mode(target), lcfs_node_get_nlink(target),
lcfs_node_get_uid(target), lcfs_node_get_gid(target),
lcfs_node_get_rdev(target), (int64_t)mtime.tv_sec,
(unsigned int)mtime.tv_nsec);
print_escaped(payload ? payload : "-", -1, ESCAPE_LONE_DASH);

if (digest) {
char digest_str[LCFS_DIGEST_SIZE * 2 + 1] = { 0 };
digest_to_string(digest, digest_str);
printf(" %s", digest_str);
} else {
printf(" -");
}

size_t n_xattr = lcfs_node_get_n_xattr(target);
for (size_t i = 0; i < n_xattr; i++) {
const char *name = lcfs_node_get_xattr_name(target, i);
size_t value_len;
const char *value = lcfs_node_get_xattr(target, name, &value_len);

printf(" ");
print_escaped(name, -1, ESCAPE_EQUAL);
printf("=");
print_escaped(value, value_len, ESCAPE_EQUAL);
}

printf("\n");

for (size_t i = 0; i < lcfs_node_get_n_children(node); i++) {
struct lcfs_node_s *child = lcfs_node_get_child(node, i);
cleanup_free char *child_path = NULL;

asprintf(&child_path, "%s/%s", path, lcfs_node_get_name(child));

dump_node(child, child_path);
}
}

static void get_objects(struct lcfs_node_s *node, Hash_table *ht)
{
uint32_t mode = lcfs_node_get_mode(node);
uint32_t type = mode & S_IFMT;
const char *payload = lcfs_node_get_payload(node);

if (type == S_IFREG && payload) {
if (hash_insert(ht, payload) == NULL) {
errx(EXIT_FAILURE, "Out of memory");
}
}

for (size_t i = 0; i < lcfs_node_get_n_children(node); i++) {
struct lcfs_node_s *child = lcfs_node_get_child(node, i);
get_objects(child, ht);
}
}

static size_t str_ht_hash(const void *entry, size_t table_size)
{
return hash_string(entry, table_size);
}

static bool str_ht_eq(const void *entry1, const void *entry2)
{
return strcmp(entry1, entry2) == 0;
}

static int cmp_obj(const void *_a, const void *_b)
{
const char *const *a = _a;
const char *const *b = _b;
return strcmp(*a, *b);
}

static void print_objects(struct lcfs_node_s *node)
{
Hash_table *ht = hash_initialize(0, NULL, str_ht_hash, str_ht_eq, NULL);
if (ht == NULL)
errx(EXIT_FAILURE, "Out of memory");

get_objects(node, ht);

size_t n_objects = hash_get_n_entries(ht);
char **objects = calloc(n_objects, sizeof(char *));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing NULL check


hash_get_entries(ht, (void **)objects, n_objects);

qsort(objects, n_objects, sizeof(char *), cmp_obj);

for (size_t i = 0; i < n_objects; i++)
printf("%s\n", objects[i]);

hash_free(ht);
}

static void usage(const char *argv0)
{
fprintf(stderr, "usage: %s [ls|objects|dump] IMAGE\n", argv0);
}

int main(int argc, char **argv)
{
const char *bin = argv[0];
int fd;
cleanup_node struct lcfs_node_s *root = NULL;
const char *image_path = NULL;
const char *command;
Comment on lines +253 to +257
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So not really related to this exactly but...can I arm twist you into using declare-and-initialize style in this codebase? I find it much more readable than "big dump of variables at the top of the function". It plays more nicely with __attribute__(cleanup) too because there's no footgun around forgetting to init to NULL etc.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure


if (argc <= 1) {
fprintf(stderr, "No command specified\n");
usage(bin);
exit(1);
}
command = argv[1];

if (argc <= 2) {
fprintf(stderr, "No image path specified\n");
usage(bin);
exit(1);
}
image_path = argv[2];

fd = open(image_path, O_RDONLY | O_CLOEXEC);
if (fd < 0) {
err(EXIT_FAILURE, "Failed to open '%s'", image_path);
}

root = lcfs_load_node_from_fd(fd);
if (root == NULL) {
err(EXIT_FAILURE, "Failed to load '%s'", image_path);
}

if (strcmp(command, "ls") == 0) {
print_node(root, "");
} else if (strcmp(command, "dump") == 0) {
dump_node(root, "");
} else if (strcmp(command, "objects") == 0) {
print_objects(root);
} else {
errx(EXIT_FAILURE, "Unknown command '%s'\n", command);
}

return 0;
}