Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
19 changes: 14 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,24 @@ jobs:
test -f build/dmf/blkid.dmf
test -f build/dmf/dmpart.dmf
test -f build/dmf/test_dmpart.dmf
test -f build/dmf/automount.dmf
test -f build/dmf/libautomount.dmf
test -f build/dmf/test_libautomount.dmf
echo "Module files present"

# dmblkid uses the 64-bit file API (Dmod_FileSeek/_FileTell/_FileSize
# 2.0, dmod#312). The dmod_loader in the 1.0.4 image predates it, so
# the tests run in one built from the same dmod develop the module
# is built against.
# is built against. Cloned into a fresh directory: the image already
# has a /tmp/dmod of its own.
- name: Build dmod_loader from dmod develop
run: |
git clone --depth 1 -b develop https://github.com/choco-technologies/dmod.git /tmp/dmod
cmake -S /tmp/dmod -B /tmp/dmod/build -DDMOD_MODE=DMOD_SYSTEM \
DMOD_SRC=$(mktemp -d)
git clone --depth 1 -b develop https://github.com/choco-technologies/dmod.git "$DMOD_SRC"
cmake -S "$DMOD_SRC" -B "$DMOD_SRC/build" -DDMOD_MODE=DMOD_SYSTEM \
-DDMOD_BUILD_EXAMPLES=ON -DDMOD_BUILD_TESTS=OFF
cmake --build /tmp/dmod/build --target dmod_loader
echo "DMOD_LOADER=/tmp/dmod/build/examples/system/dmod_loader/dmod_loader" >> $GITHUB_ENV
cmake --build "$DMOD_SRC/build" --target dmod_loader
echo "DMOD_LOADER=$DMOD_SRC/build/examples/system/dmod_loader/dmod_loader" >> $GITHUB_ENV

- name: Run tests with dmod_loader
run: |
Expand All @@ -57,6 +62,8 @@ jobs:
${DMOD_LOADER} build/dmf/test_dmpart.dmf
dmf-get install -d ${DMOD_DMF_DIR}/test_dmblkid-local.dmd -y
${DMOD_LOADER} build/dmf/test_dmblkid.dmf
dmf-get install -d ${DMOD_DMF_DIR}/test_libautomount-local.dmd -y
${DMOD_LOADER} build/dmf/test_libautomount.dmf

build-targets:
name: Build for ${{ matrix.arch }}
Expand Down Expand Up @@ -87,3 +94,5 @@ jobs:
test -f dmf/dmblkid.dmf
test -f dmf/blkid.dmf
test -f dmf/dmpart.dmf
test -f dmf/automount.dmf
test -f dmf/libautomount.dmf
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -91,5 +91,11 @@ add_subdirectory(libs/dmpart)
# blkid - command line front end
add_subdirectory(services/blkid)

# libautomount - mounting block nodes (everything automount does, loaded once)
add_subdirectory(libs/libautomount)

# automount - the per-node service around libautomount
add_subdirectory(services/automount)

enable_testing()
add_subdirectory(tests)
23 changes: 18 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ prints it.
| blank, damaged or unsupported | `unknown` | - | size |

Every type is mapped to the DMOD filesystem module that handles it and
whether that module can mount the node. Today none can: FAT and exFAT have
no module yet, and dmffs mounts memory-mapped flash rather than a node - so
they are reported, but marked not mountable.
whether that module can mount the node: FAT and exFAT are mounted by
[dmfatfs](https://github.com/choco-technologies/dmfatfs) from the node
itself; dmffs is reported, but marked not mountable - it mounts
memory-mapped flash rather than a node.

Detection is strict, so that nothing is mounted by mistake:

Expand Down Expand Up @@ -72,11 +73,20 @@ The command line front end:
```
> blkid /dev/dmsdio0/0 /dev/dmsdio0/0p1
/dev/dmsdio0/0: PTTYPE="mbr" PTUUID="1a2b3c4d" PARTITIONS="1" BLOCK_SIZE="512" SIZE="15931539456"
/dev/dmsdio0/0p1: TYPE="vfat" VERSION="FAT32" LABEL="SDCARD" UUID="CAFE-BABE" SIZE="15930490880" MOUNTABLE="no"
/dev/dmsdio0/0p1: TYPE="vfat" VERSION="FAT32" LABEL="SDCARD" UUID="CAFE-BABE" SIZE="15930490880" MODULE="dmfatfs" MOUNTABLE="yes"
```

See [services/blkid/README.md](services/blkid/README.md).

### Automount

The `automount` service mounts every block node dmdevfs reports that holds
a mountable file system, at `/mnt/<volume label>` (or `/mnt/<node name>`
when there is no label or it is taken), and unmounts it when the node goes
away. It is a thin `main()` around [libautomount](libs/libautomount/README.md),
which does the work once for all instances. See
[services/automount/README.md](services/automount/README.md).

## API

| Function | Description |
Expand Down Expand Up @@ -172,8 +182,10 @@ dmblkid/
├── include/
│ └── dmblkid.h # Public API
├── libs/
│ └── dmpart/ # dmpart - MBR/GPT parser library (also used by dmdevfs)
│ ├── dmpart/ # dmpart - MBR/GPT parser library (also used by dmdevfs)
│ └── libautomount/ # libautomount - the automount logic, loaded once
├── services/
│ ├── automount/ # automount service (per node, around libautomount)
│ └── blkid/ # blkid command line application
├── src/
│ ├── dmblkid.c # dmblkid_probe(), result accessors
Expand All @@ -187,6 +199,7 @@ dmblkid/
├── tests/
│ ├── CMakeLists.txt
│ ├── dmblkid_test.c
│ ├── libautomount_test.c # libautomount mount logic
│ └── test_images.c # Image builders
├── CMakeLists.txt
├── Makefile
Expand Down
12 changes: 6 additions & 6 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,19 +123,19 @@ const char* dmblkid_get_uuid(const dmblkid_t* result);
const char* dmblkid_get_module(const dmblkid_t* result);
```

The DMOD filesystem module that handles the type: `"dmffs"` for dmffs.
`NULL` for types the ecosystem has no module for yet (FAT, exFAT), for
partition tables and for unknown contents.
The DMOD filesystem module that handles the type: `"dmfatfs"` for FAT and
exFAT, `"dmffs"` for dmffs. `NULL` for partition tables and for unknown
contents.

### `dmblkid_is_mountable`

```c
bool dmblkid_is_mountable(const dmblkid_t* result);
```

`true` only if `dmblkid_get_module()` can mount the node itself. `false` for
dmffs: its module mounts memory-mapped flash (`flash_addr`/`flash_size`), not
a node.
`true` only if `dmblkid_get_module()` can mount the node itself - FAT and
exFAT (`dmvfs_mount_fs("dmfatfs", <dir>, <node>)`). `false` for dmffs: its
module mounts memory-mapped flash (`flash_addr`/`flash_size`), not a node.

### `dmblkid_get_size`

Expand Down
4 changes: 2 additions & 2 deletions include/dmblkid.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ dmod_dmblkid_api(1.0, const char*, _get_label, ( const dmblkid_t* result ));
dmod_dmblkid_api(1.0, const char*, _get_uuid, ( const dmblkid_t* result ));

/**
* @return Name of the DMOD filesystem module that handles this format, or
* NULL if the ecosystem has none (FAT, exFAT today).
* @return Name of the DMOD filesystem module that handles this format
* ("dmfatfs" for FAT/exFAT, "dmffs"), or NULL if there is none.
*/
dmod_dmblkid_api(1.0, const char*, _get_module, ( const dmblkid_t* result ));

Expand Down
33 changes: 33 additions & 0 deletions libs/libautomount/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# =====================================================================
# libautomount - mounting block nodes as they appear
# =====================================================================
# Everything the automount service (services/automount) does, as a library:
# loaded once, while the service runs once per block node - see README.md.
# Built and released with dmblkid.
set(DMOD_MODULE_NAME libautomount)
set(DMOD_AUTHOR_NAME "Patryk Kubiak")
set(DMOD_STACK_SIZE 1024)
set(DMOD_DMR_PATH ${CMAKE_CURRENT_SOURCE_DIR}/libautomount.dmr)

dmod_add_library(${DMOD_MODULE_NAME} ${DMOD_MODULE_VERSION}
src/libautomount.c
src/libautomount_core.c
src/libautomount_os.c
)

target_include_directories(${DMOD_MODULE_NAME} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_CURRENT_SOURCE_DIR}/src
)

target_include_directories(${DMOD_MODULE_NAME}_if INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/include
)

# dmblkid - local sibling (repository root): headers from its _if target
target_link_libraries(${DMOD_MODULE_NAME} dmblkid_if)

dmod_link_modules(${DMOD_MODULE_NAME}
dmosi
libsystemd
)
56 changes: 56 additions & 0 deletions libs/libautomount/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# libautomount

Mounting block nodes as they appear - everything the
[automount](../../services/automount/README.md) service does, as a library.

A library module is loaded once, while the service runs once per block node
(one instance per partition of every card). Keeping the logic here means it
is in memory once, however many volumes are mounted: the service itself is
only a `main()` calling `libautomount_run()`.

## API

```c
#include "libautomount.h"

/* The whole service: mount, wait for a libsystemd stop, unmount. */
int libautomount_run(const char* node, const char* name, const char* base_dir);

/* The same without waiting. */
libautomount_t* libautomount_mount(const char* node, const char* name, const char* base_dir, int* error);
void libautomount_unmount(libautomount_t* mount);
const char* libautomount_get_dir(const libautomount_t* mount);
```

- `node` - device node, e.g. `/dev/dmsdio0/0p1`
- `name` - directory used when the volume label cannot be, e.g. `dmsdio0_0p1`
- `base_dir` - directory volumes are mounted in, `NULL` for `/mnt`

The node is probed with dmblkid. A file system that a module can mount from
a node (`dmblkid_is_mountable()`; FAT and exFAT through dmfatfs) is mounted
with that module at `<base_dir>/<label>`, or `<base_dir>/<name>` when the
volume has no label or that path is taken. In the label, characters that do
not belong in a path or a shell word become `_`. Anything else - a partition
table, unknown contents, dmffs - is left alone (`error` 0, nothing mounted).

Picking the path and mounting happen under one library-wide lock, so two
volumes never end up at the same path. Only `base_dir` is created: dmvfs
needs no directory under a mount point.

`libautomount_run()` returns 0 also when there is nothing to mount or the
volume cannot be mounted (logged), so a service restart policy never spins
on it; only `-ENOMEM` is an error. While mounted it sleeps on a semaphore
registered with `libsystemd_set_stop_semaphore()`; if the process is killed
instead of stopped, a process exit callback unmounts.

## Structure

| File | Contents |
|------|----------|
| `src/libautomount.c` | API, `libautomount_run()` (libsystemd stop, exit callback) |
| `src/libautomount_core.c` | Probe, label, path choice, mount/unmount |
| `src/libautomount_os.c` | dmvfs and file API access, the lock |

The host tests (`tests/libautomount_test.c` in the repository root) compile
`libautomount_core.c` with fakes of the `libautomount_os_*()` functions -
there is no dmvfs on the host loader.
56 changes: 56 additions & 0 deletions libs/libautomount/include/libautomount.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#ifndef LIBAUTOMOUNT_H
#define LIBAUTOMOUNT_H

#include <stdbool.h>
#include "dmod_types.h"
#include "libautomount_defs.h"

/**
* libautomount - mounting block nodes as they appear.
*
* Everything the automount service does, as a library: a library module is
* loaded once, while the service runs once per block node - so the service
* itself is only a main() calling libautomount_run().
*
* A node holding a file system that a module can mount from a node (FAT and
* exFAT through dmfatfs, as dmblkid reports it) is mounted at
* <base dir>/<volume label>, or <base dir>/<name> when the volume has no
* label or that path is taken. See README.md.
*/

typedef struct libautomount libautomount_t;

/**
* Mount @p node, wait until the calling process is asked to stop through
* libsystemd (libsystemd_stop_service(): node removed, "service stop"),
* unmount. If the process is killed instead, an exit callback unmounts.
*
* Meant to be the whole main() of a service started per node.
*
* @param node Device node, e.g. "/dev/dmsdio0/0p1"
* @param name Directory name used when the label cannot be, e.g. "dmsdio0_0p1"
* @param base_dir Directory the volumes are mounted in, NULL for "/mnt"
* @return 0 - also when there is nothing to mount, or it cannot be mounted
* (logged; a restart would not change that) - or -ENOMEM.
*/
dmod_libautomount_api(1.0, int, _run, ( const char* node, const char* name, const char* base_dir ));

/**
* Mount @p node if it holds a mountable file system (see above), without
* waiting for anything.
*
* @param base_dir NULL for "/mnt"
* @param error Receives 0 when there is nothing to mount, otherwise a
* negative errno value (probe error, -EBUSY: no free
* directory, mount error, -EINVAL, -ENOMEM). May be NULL.
* @return The mount, or NULL when nothing was mounted.
*/
dmod_libautomount_api(1.0, libautomount_t*, _mount, ( const char* node, const char* name, const char* base_dir, int* error ));

/** Unmount a mount made by libautomount_mount() and release it. Safe with NULL. */
dmod_libautomount_api(1.0, void, _unmount, ( libautomount_t* mount ));

/** @return The directory the volume is mounted at, e.g. "/mnt/BOOT"; NULL for an invalid mount. */
dmod_libautomount_api(1.0, const char*, _get_dir, ( const libautomount_t* mount ));

#endif // LIBAUTOMOUNT_H
24 changes: 24 additions & 0 deletions libs/libautomount/libautomount.dmr
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# DMOD Resource File for libautomount
# This file specifies where resources should be installed

# === Core Module ===
dmf=./${module}.dmf => ${destination}/${module}.dmf [origin=${dmf_dir}/${module}.dmf]
dmfc=./${module}.dmfc => ${destination}/${module}.dmfc [origin=${build_dir}/dmfc/${module}.dmfc]
dmd=./${module}.dmd => ${destination}/${module}.dmd [origin=${dmf_dir}/${module}.dmd]
version=./${module}_version.txt => ${destination}/${module}_version.txt [origin=${dmf_dir}/${module}_version.txt]

# === Documentation ===
# NOTE: ${repo_dir} is the top-level repo root (libautomount is built through
# add_subdirectory() from dmblkid's CMakeLists.txt), so paths are relative to
# the repo root, not to libs/libautomount.
readme=./README.md => ${destination}/${module}/README.md [origin=${repo_dir}/libs/libautomount/README.md]

# === Header Files ===
# Public header + generated <module>_defs.h
inc=./include => ${destination}/${module}/include [origin=${repo_dir}/libs/libautomount/include] [origin=${build_dir}/libs/libautomount/${module}_defs.h]

# === License ===
license=./LICENSE => ${destination}/${module}/LICENSE [origin=${repo_dir}/LICENSE]

# Adding itself
dmr=./${module}.dmr => ${destination}/${module}/${module}.dmr [origin=${repo_dir}/libs/libautomount/${module}.dmr]
Loading
Loading