Skip to content

Commit

Permalink
Support the use of device mapper for root filesystem mounts
Browse files Browse the repository at this point in the history
This makes it possible to detect the physical block device that the root
filesystem uses even if Linux's device mapper is being used for
encrypted partitions or overlays or anything else.
  • Loading branch information
fhunleth committed Dec 22, 2024
1 parent 083c06a commit 0fd73ea
Show file tree
Hide file tree
Showing 8 changed files with 389 additions and 10 deletions.
73 changes: 66 additions & 7 deletions src/mmc_linux.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include <sys/wait.h>
#include <sys/sysmacros.h>

#include <dirent.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdbool.h>
Expand Down Expand Up @@ -235,20 +236,78 @@ int mmc_device_size(const char *mmc_path, off_t *end_offset)
return *end_offset > 0 ? 0 : -1;
}

#define DEV_BLOCK_PATH_MAX 64
#define DEV_ID_LENGTH 16 // "major:minor"

static void dev_to_string(int dev, char *str)
{
snprintf(str, DEV_ID_LENGTH, "%d:%d", major(dev), minor(dev));
}

static int traverse_devices(const char *current, const char *goal, int depth_left)
{
// Found?
if (strcmp(current, goal) == 0)
return 1;

// Traverse more?
if (depth_left <= 0)
return 0;
depth_left--;

// Check all device-mapper devices
int found_goal = 0;
char *slaves_path;
if (asprintf(&slaves_path, "/sys/dev/block/%s/slaves", current) < 0)
return 0;

struct dirent **namelist = NULL;
int n = scandir(slaves_path, &namelist, NULL, NULL);
for (int i = 0; i < n && !found_goal; i++) {
const char *name = namelist[i]->d_name;
if (name[0] == '.')
continue;

// The dev file contains the major:minor string
char *dev_path;
if (asprintf(&dev_path, "%s/%s/dev", slaves_path, name) < 0)
return 0;

char next[DEV_ID_LENGTH];
int rc = readsysfs(dev_path, next, sizeof(next));
if (rc > 0)
found_goal = traverse_devices(next, goal, depth_left);

free(dev_path);
}

free(slaves_path);
if (namelist) {
for (int i = 0; i < n; i++)
free(namelist[i]);
free(namelist);
}
return found_goal;
}

int mmc_is_path_on_device(const char *file_path, const char *device_path)
{
char starting_device[DEV_ID_LENGTH];
char goal_device[DEV_ID_LENGTH];

// Stat both paths.
struct stat file_st;
if (stat(file_path, &file_st) < 0)
struct stat sb;
if (stat(file_path, &sb) < 0)
return -1;
dev_to_string(sb.st_dev, starting_device);

struct stat device_st;
if (stat(device_path, &device_st) < 0)
if (stat(device_path, &sb) < 0)
return -1;
dev_to_string(sb.st_rdev, goal_device);

// Check that the device's major/minor are the same
// as the file's containing device's major/minor
return device_st.st_rdev == file_st.st_dev ? 1 : 0;
// Traverse through device-mapper mappings to see if we can get from the
// starting file_path's containing device to the block device of interest.
return traverse_devices(starting_device, goal_device, 3);
}

int mmc_is_path_at_device_offset(const char *file_path, off_t block_offset)
Expand Down
33 changes: 33 additions & 0 deletions tests/210_new_require_path.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/bin/sh

#
# Tests that the "require-path-on-device" feature works. This allows one to
# update firmware images based on where the filesystem is mounted.
#

. "$(cd "$(dirname "$0")" && pwd)/common.sh"

$HAS_MOUNT_SHIM || exit 77

cat >$CONFIG <<EOF
task test.correct {
require-path-on-device("/", "/dev/rootdisk0p1")
on-init { info("correct") }
}
task test.fail {
on-init { error("something else!") }
}
EOF
cat >$WORK/expected_output.txt <<EOF
fwup: correct
EOF

# Create the firmware file the normal way
$FWUP_CREATE -c -f $CONFIG -o $FWFILE

# Use the mount shim instead of the write shim
LD_PRELOAD="$MOUNT_LD_PRELOAD" DYLD_INSERT_LIBRARIES="$MOUNT_DYLD_INSERT_LIBRARIES" $FWUP_APPLY -a -q -d $IMGFILE -i $FWFILE -t test > $WORK/actual_output.txt
diff -w $WORK/expected_output.txt $WORK/actual_output.txt

# Check that the verify logic works on this file
$FWUP_VERIFY -V -i $FWFILE
46 changes: 46 additions & 0 deletions tests/211_new_require_path_dm.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/bin/sh

#
# Tests that the "require-path-on-device" feature can traverse through
# device mapper mounts.
#

. "$(cd "$(dirname "$0")" && pwd)/common.sh"

$HAS_MOUNT_SHIM || exit 77

if [ "$HOST_OS" != "Linux" ]; then
echo "Skipping since device mapper logic only on Linux-specific fwup code."
exit 77
fi

cat >$CONFIG <<EOF
task test.correct {
require-path-on-device("/boot", "/dev/mmcblk0p2")
on-init { info("correct") }
}
task test.fail {
on-init { error("something else!") }
}
EOF
cat >$WORK/expected_output.txt <<EOF
fwup: correct
EOF

# Create the firmware file the normal way
$FWUP_CREATE -c -f $CONFIG -o $FWFILE

# Set up some fake /sys/dev/block directories similar to device mapper
mkdir -p $WORK/sys/dev/block/254:0/slaves/mmcblk0p1 # Fake entry to test scanning
mkdir -p $WORK/sys/dev/block/254:0/slaves/mmcblk0p2
mkdir -p $WORK/sys/dev/block/254:0/slaves/mmcblk0p3 # Fake entry to test scanning
echo "1:1" > $WORK/sys/dev/block/254:0/slaves/mmcblk0p1/dev
echo "179:2" > $WORK/sys/dev/block/254:0/slaves/mmcblk0p2/dev
echo "1:1" > $WORK/sys/dev/block/254:0/slaves/mmcblk0p3/dev

# Use the mount shim instead of the write shim
LD_PRELOAD="$MOUNT_LD_PRELOAD" DYLD_INSERT_LIBRARIES="$MOUNT_DYLD_INSERT_LIBRARIES" $FWUP_APPLY -a -q -d $IMGFILE -i $FWFILE -t test > $WORK/actual_output.txt
diff -w $WORK/expected_output.txt $WORK/actual_output.txt

# Check that the verify logic works on this file
$FWUP_VERIFY -V -i $FWFILE
4 changes: 3 additions & 1 deletion tests/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,8 @@ TESTS = 001_simple_fw.test \
206_mbr_ebr_missing_partition.test \
207_mbr_ebr_overlapping_records.test \
208_mbr_ebr_overlapping_parts.test \
209_reboot_param.test
209_reboot_param.test \
210_new_require_path.test \
211_new_require_path_dm.test

EXTRA_DIST = $(TESTS) common.sh 1K.bin 1K-corrupt.bin 150K.bin
14 changes: 13 additions & 1 deletion tests/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,19 @@ else
export HAS_WRITE_SHIM=false
fi

WORK=$TESTS_DIR/work-$(basename "$0")
# The mount simulator only runs only runs on a subset of
# platforms. Let autoconf figure out which ones.
MOUNT_SHIM="$TESTS_DIR/fixture/.libs/libmount_shim.$SO_EXT"
if [ -e "$MOUNT_SHIM" ]; then
export HAS_MOUNT_SHIM=true
export MOUNT_LD_PRELOAD="$MOUNT_SHIM"
export MOUNT_DYLD_INSERT_LIBRARIES="$MOUNT_SHIM"
else
export HAS_MOUNT_SHIM=false
fi

# Export WORK for the shims to use if needed
export WORK=$TESTS_DIR/work-$(basename "$0")
RESULTS=$WORK/results

CONFIG=$WORK/fwup.conf
Expand Down
1 change: 1 addition & 0 deletions tests/fixture/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
/verify-syscalls
/.libs
/libwrite_shim*
/libmount_shim*
6 changes: 5 additions & 1 deletion tests/fixture/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,15 @@ verify_syscalls_SOURCES=verify-syscalls.c
endif

if HAS_WRITE_SHIM
check_LTLIBRARIES = libwrite_shim.la
check_LTLIBRARIES = libwrite_shim.la libmount_shim.la
libwrite_shim_la_SOURCES = write_shim.c

# The "-rpath /nowhere" is the trick to getting libtool to create a shared library for
# "check" LTLIBRARIES.
libwrite_shim_la_LDFLAGS = ${AM_LDFLAGS} -ldl -dynamiclib -avoid-version -shared -rpath /nowhere
libwrite_shim_la_CFLAGS = ${AM_CFLAGS}

libmount_shim_la_SOURCES = mount_shim.c
libmount_shim_la_LDFLAGS = ${AM_LDFLAGS} -ldl -dynamiclib -avoid-version -shared -rpath /nowhere
libmount_shim_la_CFLAGS = ${AM_CFLAGS} -D_FILE_OFFSET_BITS=64
endif
Loading

0 comments on commit 0fd73ea

Please sign in to comment.