AP_Filesystem: add the @MAV_LOG virtual directory

MAVFTP defines @MAV_LOG as the flight-stack-independent location for log
files, so that a GCS can find them without being told where a particular
board keeps them. QGroundControl asks for it before anything else, and
falls back to guessing per-firmware paths when it is not there.

It is an alias rather than a filesystem of its own: the backend table
gains a root, and where a row carries one the resolver rewrites a path
under that prefix to sit under that directory before handing it to the
filesystem which serves it. @MAV_LOG points at the local filesystem, under
whatever directory this board logs to, following a custom log directory
the same way AP_Logger_File does.

The rewritten path has to hold the root as well as the path, so the buffer
is sized for the longest path an FTP listing stats - the longest path a
request can carry, a separator and a 255 byte name - underneath the longest
log directory a board has, and a static_assert holds boards to that. A
path too long to rewrite is refused with ENAMETOOLONG before any filesystem
sees it, rather than being truncated into the name of some other file.

That buffer is not on the stack, where every path-based call would pay for
it. It is allocated the first time an alias is used and kept, and a
semaphore is held around each backend call which uses it. rename, the one
call with two paths alive at once, allocates a second buffer for its new
path and frees it afterwards. Paths which are not aliases take neither the
semaphore nor a buffer.

An alias can't sit on LittleFS for now, and @MAV_LOG is not built where
LittleFS is the local filesystem; forcing it on is a build error. LittleFS
holds its lock from opendir() to closedir(), so the semaphore, held across
each call, would be released before that lock, which ChibiOS mutexes do not
allow, and a thread listing an alias directory would wait for the semaphore
while holding the lock another thread holding the semaphore could be
waiting for.

rename now compares the filesystems which serve its two paths rather than
their table rows, since an alias shares its filesystem with other paths,
and sets EXDEV when they differ.

A prefix now has to be the whole of a path's first component, so that
"@MAV_LOG_backup" is not served as "_backup" under the log directory. This
is not particular to the alias: "@SYSfoo" used to be served by @SYS as
"foo", and is now the local file of that name. Nothing in the tree relied
on the old behaviour.

@MAV_LOG, and the alias support with it, is only built where the logs go
to a filesystem at all.
This commit is contained in:
Peter Barker
2026-09-22 11:10:46 +10:00
committed by Andrew Tridgell
parent 645c39f65b
commit bc4a6f05a6
5 changed files with 633 additions and 31 deletions
File diff suppressed because it is too large Load Diff
+54
View File
@@ -24,6 +24,10 @@
#include "AP_Filesystem_config.h"
#if AP_FILESYSTEM_ALIAS_ENABLED
#include <AP_HAL/Semaphores.h>
#endif // AP_FILESYSTEM_ALIAS_ENABLED
#ifndef MAX_NAME_LEN
#define MAX_NAME_LEN 255
#endif
@@ -180,9 +184,14 @@ public:
static AP_Filesystem *get_singleton(void);
private:
friend class AP_Filesystem_Test;
struct Backend {
const char *prefix;
AP_Filesystem_Backend &fs;
// if set, prefix is an alias for this directory in fs; a function as
// the directory may only be known at runtime
const char *(*root)(void);
};
static const struct Backend backends[];
@@ -191,6 +200,51 @@ private:
*/
const Backend &backend_by_path(const char *&path) const;
// a path resolved to its backend; an alias's rewritten path lives only as long as this does
class ResolvedPath {
public:
// SHARED uses alias_path under alias_sem; OWN allocates, for a second path in one call
enum class Buffer : uint8_t {
SHARED,
OWN,
};
ResolvedPath(AP_Filesystem &filesystem, const char *path, Buffer buffer=Buffer::SHARED);
~ResolvedPath();
CLASS_NO_COPY(ResolvedPath);
// false (with errno set) if an alias path could not be built; the caller must fail
bool valid(void) const { return _path != nullptr; }
const Backend &backend(void) const { return *_backend; }
const char *path(void) const { return _path; }
private:
const Backend *_backend;
const char *_path;
#if AP_FILESYSTEM_ALIAS_ENABLED
AP_Filesystem &_filesystem;
char *_own_buffer;
bool _holds_shared_buffer;
#endif // AP_FILESYSTEM_ALIAS_ENABLED
};
#if AP_FILESYSTEM_ALIAS_ENABLED
// allocated on first alias use and kept
char *alias_path = nullptr;
/*
held across the whole backend call using alias_path.
WARNING: where it is waited for, IO which never returns leaves it held,
and every later alias call blocks forever
*/
HAL_Semaphore alias_sem;
// alias_sem is recursive; this stops its holder overwriting alias_path in use
bool alias_path_in_use = false;
#endif // AP_FILESYSTEM_ALIAS_ENABLED
/*
find backend by open fd
*/
@@ -56,3 +56,27 @@
#include <AP_Mission/AP_Mission_config.h>
#define AP_FILESYSTEM_MISSION_ENABLED AP_MISSION_ENABLED
#endif
// true when AP_Filesystem.cpp picks LittleFS as the local filesystem. an
// alias can't sit on it; see AP_Filesystem.cpp
#define AP_FILESYSTEM_LOCAL_IS_LITTLEFS (AP_FILESYSTEM_LITTLEFS_ENABLED && !AP_FILESYSTEM_FATFS_ENABLED && !AP_FILESYSTEM_ESP32_ENABLED)
// @MAV_LOG is an alias for the directory this board writes its logs to,
// where those logs are written to a filesystem
#ifndef AP_FILESYSTEM_MAVLOG_ENABLED
#define AP_FILESYSTEM_MAVLOG_ENABLED (AP_FILESYSTEM_FILE_WRITING_ENABLED && !AP_FILESYSTEM_LOCAL_IS_LITTLEFS && defined(HAL_BOARD_LOG_DIRECTORY) && HAL_LOGGING_FILESYSTEM_ENABLED)
#endif // AP_FILESYSTEM_MAVLOG_ENABLED
// only @MAV_LOG uses the alias support
#ifndef AP_FILESYSTEM_ALIAS_ENABLED
#define AP_FILESYSTEM_ALIAS_ENABLED AP_FILESYSTEM_MAVLOG_ENABLED
#endif // AP_FILESYSTEM_ALIAS_ENABLED
// longest alias path: root + '/' + 238 byte FTP request + '/' + 255 byte name
#ifndef AP_FILESYSTEM_ALIAS_PATH_MAX
#define AP_FILESYSTEM_ALIAS_PATH_MAX 544
#endif // AP_FILESYSTEM_ALIAS_PATH_MAX
// last, for HAL_LOGGING_FILESYSTEM_ENABLED: AP_Logger_config.h includes this
// header, so nothing above may test that value, only name it
#include <AP_Logger/AP_Logger_config.h>
File diff suppressed because it is too large Load Diff
+6
View File
@@ -0,0 +1,6 @@
#!/usr/bin/env python3
def build(bld):
bld.ap_find_tests(
use='ap',
)