Merge the CLI and Daemon portable interface

This commit is contained in:
Andrea Mazzoleni 2026-06-24 00:15:15 +02:00
parent 49d5e685c9
commit 00b4cbafd0
46 changed files with 4132 additions and 2050 deletions

View file

@ -11,7 +11,9 @@ endif
# Windows
if HOST_WINDOWS
snapraid_LDADD = resource_res.o
snapraid_LDADD = resource_res.o -ladvapi32 -luserenv
mktest_LDADD = -ladvapi32 -luserenv
mkstream_LDADD = -ladvapi32 -luserenv
resource_res.o: cmdline/resource.rc cmdline/resource.manifest
$(WINDRES) --include-dir=. --include-dir=$(srcdir) $< -o $@
@ -64,9 +66,9 @@ snapraid_SOURCES = \
cmdline/import.c \
cmdline/search.c \
cmdline/thermal.c \
cmdline/mingw.c \
os/mingw.c \
cmdline/mingwapp.c \
cmdline/unix.c \
os/unix.c \
cmdline/unixapp.c
noinst_HEADERS = \
@ -92,7 +94,6 @@ noinst_HEADERS = \
tommyds/tommyhashdyn.c \
tommyds/tommylist.c \
tommyds/tommytree.c \
cmdline/portable.h \
cmdline/snapraid.h \
cmdline/io.h \
cmdline/bw.h \
@ -115,24 +116,26 @@ noinst_HEADERS = \
cmdline/import.h \
cmdline/search.h \
cmdline/thermal.h \
cmdline/mingw.h \
cmdline/unix.h \
cmdline/os.h \
cmdline/app.h
cmdline/app.h \
os/os.h \
os/mingw.h \
os/unix.h \
os/os.h \
os/portable.h
mktest_SOURCES = \
cmdline/mktest.c \
cmdline/mingw.c \
os/mingw.c \
cmdline/mingwapp.c \
cmdline/unix.c \
os/unix.c \
cmdline/unixapp.c \
cmdline/support.c
mkstream_SOURCES = \
cmdline/mkstream.c \
cmdline/mingw.c \
os/mingw.c \
cmdline/mingwapp.c \
cmdline/unix.c \
os/unix.c \
cmdline/unixapp.c \
cmdline/support.c \
cmdline/util.c \

View file

@ -4,11 +4,22 @@
#ifndef __APP_H
#define __APP_H
#include "os/os.h"
/**
* Include list support to have tommy_node.
*/
#include "tommyds/tommylist.h"
#define EINTERNAL EFAULT /**< Internal assertion failed. */
#define EDATA EIO /**< Silent data corruption. */
#define ESOFT EINVAL /**< Software error, like permission denied. */
#define EUSER EINVAL /**< Invalid value specified by the user. */
#define EEXTERNAL EINVAL /**< Invalid external interface behaviour. */
#define ECONTENT EINVAL /**< Invalid content file. */
#define EENVIRONMENT EINVAL /**< Invalid physical environment, like temperature too high. */
#define ESYSLOG EINVAL /**< Syslog error. */
/**
* Basic block position type.
*/
@ -34,34 +45,6 @@ static inline int hardlink(const char* a, const char* b)
*/
int devuuid(uint64_t device_id, const char* device_path, char* uuid, size_t size);
/**
* Physical offset not yet read.
*/
#define FILEPHY_UNREAD_OFFSET 0
/**
* Special value returned when the file-system doesn't report any offset for unknown reason.
*/
#define FILEPHY_UNREPORTED_OFFSET 1
/**
* Special value returned when the file doesn't have a real offset.
* For example, because it's stored in the NTFS MFT.
*/
#define FILEPHY_WITHOUT_OFFSET 2
/**
* Value indicating real offsets. All offsets greater or equal at this one are real.
*/
#define FILEPHY_REAL_OFFSET 3
/**
* Get the physical address of the specified file.
* This is expected to be just a hint and not necessarily correct or unique.
* Return 0 on success.
*/
int filephy(const char* path, uint64_t size, uint64_t* physical);
/**
* Check if the underline file-system support persistent inodes.
* Return -1 on error, 0 on success.
@ -393,17 +376,6 @@ int ambient_temperature(void);
*/
#define WINDOWS_SPACEHOLDER_SIZE (256 * 1024 * 1024)
/**
* Generic errors
*/
#define EINTERNAL EFAULT /**< Internal assertion failed. */
#define EDATA EIO /**< Silent data corruption. */
#define ESOFT EINVAL /**< Software error, like permission denied. */
#define EUSER EINVAL /**< Invalid value specified by the user. */
#define EEXTERNAL EINVAL /**< Invalid external interface behaviour. */
#define ECONTENT EINVAL /**< Invalid content file. */
#define EENVIRONMENT EINVAL /**< Invalid physical environment, like temperature too high. */
/****************************************************************************/
/* app */
@ -427,10 +399,5 @@ void app_default_conf(char* dst, size_t dst_size, const char* argv0);
*/
void app_signal_handler(int signum);
/**
* Global variable to identify if Ctrl+C is pressed.
*/
int app_global_interrupt(void);
#endif

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2016 Andrea Mazzoleni
#include "portable.h"
#include "os/portable.h"
#include "bw.h"

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2011 Andrea Mazzoleni
#include "portable.h"
#include "os/portable.h"
#include "support.h"
#include "util.h"

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2015 Andrea Mazzoleni
#include "portable.h"
#include "os/portable.h"
#include "support.h"
#include "state.h"

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2011 Andrea Mazzoleni
#include "portable.h"
#include "os/portable.h"
#include "util.h"
#include "elem.h"

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2011 Andrea Mazzoleni
#include "portable.h"
#include "os/portable.h"
#include "support.h"
#include "util.h"

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2011 Andrea Mazzoleni
#include "portable.h"
#include "os/portable.h"
#include "elem.h"
#include "support.h"

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2011 Andrea Mazzoleni
#include "portable.h"
#include "os/portable.h"
#include "elem.h"
#include "support.h"

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2013 Andrea Mazzoleni
#include "portable.h"
#include "os/portable.h"
#include "support.h"
#include "import.h"

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2016 Andrea Mazzoleni
#include "portable.h"
#include "os/portable.h"
#include "io.h"

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2013 Andrea Mazzoleni
#include "portable.h"
#include "os/portable.h"
#include "util.h"
#include "elem.h"

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2026 Andrea Mazzoleni
#include "portable.h"
#include "os/portable.h"
#include "elem.h"
#include "parity.h"

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2025 Andrea Mazzoleni
#include "portable.h"
#include "os/portable.h"
#include "snapraid.h"

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2011 Andrea Mazzoleni
#include "portable.h"
#include "os/portable.h"
#ifdef __MINGW32__ /* Only for MingW */
@ -22,7 +22,7 @@ int exit_sync_needed = 2;
volatile int global_interrupt = 0;
int app_global_interrupt(void)
int os_signal_interrupt(void)
{
return global_interrupt;
}
@ -33,6 +33,598 @@ void app_signal_handler(int signum)
global_interrupt = signum;
}
/****************************************************************************/
/* fs */
int fsinfo(const char* path, int* has_persistent_inode, int* has_syncronized_hardlinks, uint64_t* total_space, uint64_t* free_space, char* fstype, size_t fstype_size, char* fslabel, size_t fslabel_size)
{
wchar_t conv_buf[CONV_MAX];
/* all FAT/exFAT/NTFS when managed from Windows have persistent inodes */
if (has_persistent_inode)
*has_persistent_inode = 1;
/* NTFS doesn't synchronize hardlinks metadata */
if (has_syncronized_hardlinks)
*has_syncronized_hardlinks = 0;
if (free_space || total_space) {
ULARGE_INTEGER total_bytes;
ULARGE_INTEGER total_free_bytes;
DWORD attr;
char dir[PATH_MAX];
if (strlen(path) + 1 > sizeof(dir)) {
windows_errno(ERROR_BUFFER_OVERFLOW);
return -1;
}
strcpy(dir, path);
/* get the file attributes */
attr = GetFileAttributesW(convert(conv_buf, dir));
if (attr == INVALID_FILE_ATTRIBUTES) {
DWORD error = GetLastError();
if (error != ERROR_FILE_NOT_FOUND) {
windows_errno(error);
return -1;
}
/*
* If it doesn't exist, we assume a file
* and we check for the containing dir
*/
attr = 0;
}
/* if it's not a directory, truncate the file name */
if ((attr & FILE_ATTRIBUTE_DIRECTORY) == 0) {
char* slash = strrchr(dir, '/');
/**
* Cut the file name, but leave the last slash.
*
* This is done because a MSDN comment about using of UNC paths.
*
* MSDN 'GetDiskFreeSpaceEx function'
* http://msdn.microsoft.com/en-us/library/windows/desktop/aa364937%28v=vs.85%29.aspx
* If this parameter is a UNC name, it must include a trailing backslash,
* for example, "\\MyServer\MyShare\".
*/
if (slash)
slash[1] = 0;
}
/*
* Get the free space of the directory
* note that it must be a directory
*/
if (!GetDiskFreeSpaceExW(convert(conv_buf, dir), 0, &total_bytes, &total_free_bytes)) {
windows_errno(GetLastError());
return -1;
}
if (total_space)
*total_space = total_bytes.QuadPart;
if (free_space)
*free_space = total_free_bytes.QuadPart;
}
if (fstype && fslabel) {
wchar_t volume_root[PATH_MAX];
wchar_t fs_name[PATH_MAX];
wchar_t vol_name[PATH_MAX];
fstype[0] = 0;
fslabel[0] = 0;
if (GetVolumePathNameW(convert(conv_buf, path), volume_root, PATH_MAX)) {
if (GetVolumeInformationW(volume_root, vol_name, PATH_MAX, 0, 0, 0, fs_name, PATH_MAX)) {
char u8[CONV_MAX];
size_t len;
char* ret;
ret = u16tou8_mayfail(u8, sizeof(u8), fs_name, wcslen(fs_name), &len);
if (ret != 0 && len + 1 <= fstype_size) {
memcpy(fstype, u8, len);
fstype[len] = 0;
}
ret = u16tou8_mayfail(u8, sizeof(u8), vol_name, wcslen(vol_name), &len);
if (ret != 0 && len + 1 <= fslabel_size) {
memcpy(fslabel, u8, len);
fslabel[len] = 0;
}
}
}
}
return 0;
}
/****************************************************************************/
/* snapshot */
size_t windows_direct_size(void)
{
SYSTEM_INFO si;
GetSystemInfo(&si);
/*
* MSDN 'File Buffering'
* https://msdn.microsoft.com/en-us/library/windows/desktop/cc644950%28v=vs.85%29.aspx
*
* "Therefore, in most situations, page-aligned memory will also be sector-aligned,"
* "because the case where the sector size is larger than the page size is rare."
*/
return si.dwPageSize;
}
#define WINDOWS_NTFS_MAGIC 0x5346544E /* 'N','T','F','S' */
#define PS_CMD_MAX 4096
#define SNAPSHOT_GUID ".guid"
/*
* PowerShell helper
*
* Runs a PowerShell one-liner via _popen() and captures the first line
* of stdout into `out` as UTF-8.
* `out` may be NULL when output is not needed.
*
* The caller builds `command` as a string using snprintf().
* Any embedded path literals must use single quotes so they survive
* the outer double-quote wrapping passed to cmd.exe.
*
* Returns 0 on success, -1 on failure.
*/
static int windows_ps(const char* ps_command, char* out, size_t out_size)
{
char cmd[PS_CMD_MAX];
FILE* fp;
int ret;
ret = snprintf(cmd, sizeof(cmd), "powershell.exe -NoProfile -NonInteractive -Command \"%s\" 2>nul", ps_command);
if (ret < 0 || ret >= (int)sizeof(cmd)) {
errno = EINVAL;
return -1;
}
fp = _popen(cmd, "r");
if (!fp) {
windows_errno(GetLastError());
log_error(errno, "Failed to run PowerShell command '%s' (from popen).\n", cmd);
return -1;
}
if (out && out_size > 0) {
/* get the first line */
if (!fgets(out, (int)out_size, fp)) {
out[0] = 0;
} else {
/* trim spaces and newlines */
strtrim(out);
}
}
ret = _pclose(fp);
if (ret == -1) {
errno = EINVAL;
log_error(errno, "Failed to run PowerShell command '%s' (from pclose).\n", cmd);
return -1;
}
if (ret != 0) {
errno = EINVAL;
log_error(errno, "PowerShell command '%s' failed with '%d' (from pclose).\n", cmd, ret);
return -1;
}
return 0;
}
/**
* Validates a GUID string of the form:
* xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
* Does NOT accept braced forms like {xxxxxxxx-...}.
*
* Returns true if valid, false otherwise.
*/
int windows_guid_is_valid(const char* guid, int len)
{
if (!guid || len != 38)
return -1;
if (guid[0] != '{' || guid[37] != '}')
return -1;
const char* inner = guid + 1;
for (int i = 0; i < 36; ++i) {
if (i == 8 || i == 13 || i == 18 || i == 23) {
if (inner[i] != '-')
return -1;
} else {
char c = inner[i];
int is_digit = (c >= '0' && c <= '9');
int is_upper_hex = (c >= 'A' && c <= 'F');
int is_lower_hex = (c >= 'a' && c <= 'f');
if (!is_digit && !is_upper_hex && !is_lower_hex)
return -1;
}
}
return 0;
}
static int windows_read_guid(const char* guid_link, char* guid, size_t guid_size)
{
int len = windows_readlink(guid_link, guid, guid_size);
if (len < 0 && errno == ENOENT) {
guid[0] = 0;
return 0;
}
if (len < 0) {
log_error(errno, "Error readlink '%s'. %s.\n", guid_link, strerror(errno));
return -1;
}
if ((size_t)len >= guid_size) {
errno = ENAMETOOLONG;
log_error(errno, "GUID too long in '%s'.\n", guid_link);
return -1;
}
guid[len] = 0;
/* validate that the GUID contains only safe characters: [A-Za-z0-9-{}] */
if (windows_guid_is_valid(guid, len) != 0)
return -1;
return 0;
}
static int windows_rebuild_link(const struct fssnapshot_struct* fss, const char* name)
{
char cmd[PS_CMD_MAX];
char device_path[PATH_MAX];
char target_path[PATH_MAX];
char link_path[PATH_MAX];
char guid_link[PATH_MAX];
char guid[PATH_MAX];
pathcpy(guid_link, sizeof(guid_link), fss->snapshot_dir);
pathcat(guid_link, sizeof(guid_link), name);
pathcat(guid_link, sizeof(guid_link), SNAPSHOT_GUID);
pathcpy(link_path, sizeof(link_path), fss->snapshot_dir);
pathcat(link_path, sizeof(link_path), name);
int ret = windows_read_guid(guid_link, guid, sizeof(guid));
if (ret != 0) {
return -1;
}
/* if no GUID, assume it absent */
if (guid[0] == 0) {
/* remove stale links */
windows_rmdir(link_path);
return 0;
}
snprintf(cmd, sizeof(cmd),
"(Get-WmiObject Win32_ShadowCopy | Where-Object {$_.ID -eq '%s'}).DeviceObject",
guid);
if (windows_ps(cmd, device_path, sizeof(device_path)) != 0) {
log_error(errno, "Error getting DeviceObject from GUID '%s'. %s.\n", guid, strerror(errno));
return -1;
}
/* if no device path, assume snapshot absent */
if (device_path[0] == 0) {
/* remove stale links */
windows_rmdir(guid_link);
windows_rmdir(link_path);
return 0;
}
pathimport(target_path, sizeof(target_path), device_path);
pathslash(target_path, sizeof(target_path));
/* remove stale link if present */
windows_rmdir(link_path);
if (windows_symlink_directory(target_path, link_path) != 0) {
log_error(errno, "Error creating symlink '%s'. %s.\n", link_path, strerror(errno));
return -1;
}
return 0;
}
static int windows_delete_link(const struct fssnapshot_struct* fss, const char* name)
{
char link_path[PATH_MAX];
pathcpy(link_path, sizeof(link_path), fss->snapshot_dir);
pathcat(link_path, sizeof(link_path), name);
/* remove link if present */
windows_rmdir(link_path);
return 0;
}
int fssnapshot_mount(const char* dir, struct fssnapshot_struct* fss)
{
wchar_t conv_buf_vol[CONV_MAX];
char conv_buf_root[CONV_MAX];
wchar_t volume_root[PATH_MAX];
wchar_t volume_name[PATH_MAX];
wchar_t fs_name[32];
uint32_t magic;
/*
* GetVolumePathNameW() accepts any path: file, directory, or deep
* subdirectory, and resolves it to the volume root (e.g. L"C:\").
* The result always carries a trailing backslash.
*
* Use convert_if_required() to avoid the automatic addition of \\?\
* made by convert() that is propagated in the resulting volume_root
*/
if (!GetVolumePathNameW(convert_if_required(conv_buf_vol, dir), volume_root, PATH_MAX)) {
windows_errno(GetLastError());
log_error(errno, "Error getting VolumeRoot from '%s'. %s.\n", dir, strerror(errno));
return -1;
}
if (!GetVolumeInformationW(volume_root, 0, 0, 0, 0, 0, fs_name, 32)) {
windows_errno(GetLastError());
log_error(errno, "Error getting information of VolumeRoot '%s'. %s.\n", u16tou8(conv_buf_root, volume_root), strerror(errno));
return -1;
}
if (wcscmp(fs_name, L"NTFS") == 0)
magic = WINDOWS_NTFS_MAGIC;
else
return -1; /* support only NTFS */
/*
* Obtain the canonical volume name: \\?\Volume{GUID}\
*
* GetVolumePathNameW() may return a drive letter path ("C:\") or a
* directory mount point ("D:\Mount\"). Neither is guaranteed to be
* accepted by Win32_ShadowCopy.Create() on volumes without a drive
* letter. GetVolumeNameForVolumeMountPointW() always returns the
* stable kernel-form GUID path that VSS requires, regardless of how
* many (or how few) mount points the volume has.
*
* We store it in fss->dataset.
*/
if (!GetVolumeNameForVolumeMountPointW(volume_root, volume_name, PATH_MAX)) {
windows_errno(GetLastError());
log_error(errno, "Error getting VolumeName of VolumeRoot '%s'. %s.\n", u16tou8(conv_buf_root, volume_root), strerror(errno));
return -1;
}
/* don't use pathimport for dataset to keep backslashes */
pathcpy(fss->dataset, sizeof(fss->dataset), u16tou8(conv_buf_root, volume_name));
/* use pathimport to convert backslashes to slashes */
pathimport(fss->root_dir, sizeof(fss->root_dir), u16tou8(conv_buf_root, volume_root));
/* the returned root_dir should match the start of the passed dir */
if (pathncmp(fss->root_dir, dir, strlen(fss->root_dir)) != 0) {
errno = EINVAL;
log_error(errno, "Not matching VolumeRoot '%s' for '%s'.\n", fss->root_dir, dir);
return -1;
}
pathcpy(fss->snapshot_dir, sizeof(fss->snapshot_dir), fss->root_dir);
pathcat(fss->snapshot_dir, sizeof(fss->snapshot_dir), SNAPSHOT_CONTAINER "/");
if (windows_mkdir(fss->snapshot_dir) != 0 && errno != EEXIST) {
log_error(errno, "Error creating directory '%s'. %s.\n", fss->snapshot_dir, strerror(errno));
return -1;
}
/* refresh the links, after reboots they gets invalidated */
if (windows_rebuild_link(fss, SNAPSHOT_PENDING) != 0) {
return -1;
}
if (windows_rebuild_link(fss, SNAPSHOT_STABLE) != 0) {
return -1;
}
fss->magic = magic;
return 0;
}
int fssnapshot_stat(struct fssnapshot_struct* fss, const char* name, struct stat* st)
{
char link_path[PATH_MAX];
pathcpy(link_path, sizeof(link_path), fss->snapshot_dir);
pathcat(link_path, sizeof(link_path), name);
pathcat(link_path, sizeof(link_path), SNAPSHOT_GUID); /* we check the GUID as it's the one that matter */
/* use lstat because we want to check the link */
if (windows_lstat(link_path, st) != 0)
return -1;
/* it must be a link to a directory */
if (!S_ISLNKDIR(st->st_mode))
return -1;
return 0;
}
int fssnapshot_create(const struct fssnapshot_struct* fss, const char* name)
{
char cmd[PS_CMD_MAX];
char device_path[PATH_MAX];
char target_path[PATH_MAX];
char link_path[PATH_MAX];
char guid_link[PATH_MAX];
char out[PATH_MAX];
/* create VSS shadow copy and capture the system-assigned GUID */
snprintf(cmd, sizeof(cmd),
"$r = (Get-WmiObject -List Win32_ShadowCopy).Create('%s', 'ClientAccessible'); "
"if ($r.ReturnValue -eq 0) { "
"Write-Output ('ID_' + $r.ShadowID); "
"} else { "
"Write-Output ('0x{0:X8}' -f $r.ReturnValue); "
"}",
fss->dataset);
if (windows_ps(cmd, out, sizeof(out)) != 0) {
log_error(errno, "Error creating snapshot of VolumeName '%s'. %s.\n", fss->dataset, strerror(errno));
return -1;
}
if (strncmp(out, "ID_", 3) != 0) {
errno = ENODATA;
log_error(errno, "VSS snapshot creation of '%s' failed with error %s.\n", fss->dataset, out);
return -1;
}
const char* guid = out + 3;
/* resolve the shadow copy's device object path */
snprintf(cmd, sizeof(cmd), "(Get-WmiObject Win32_ShadowCopy | Where-Object {$_.ID -eq '%s'}).DeviceObject", guid);
if (windows_ps(cmd, device_path, sizeof(device_path)) != 0) {
log_error(errno, "Error getting the snapshot DeviceObject from GUID '%s'. %s.\n", guid, strerror(errno));
goto bail_and_delete;
}
if (device_path[0] == 0) {
errno = ENODATA;
log_error(errno, "Empty snapshot DeviceObject of GUID '%s'. %s.\n", guid, strerror(errno));
goto bail_and_delete;
}
/* use pathimport to convert backslashes to slashes */
pathimport(target_path, sizeof(target_path), device_path);
/* the ending slash is required, otherwise the link won't work */
pathslash(target_path, sizeof(target_path));
pathcpy(link_path, sizeof(link_path), fss->snapshot_dir);
pathcat(link_path, sizeof(link_path), name);
/* remove potential stale link */
windows_rmdir(link_path);
int ret = windows_symlink_directory(target_path, link_path);
if (ret != 0) {
log_error(errno, "Error creating symlink '%s'. %s.\n", link_path, strerror(errno));
goto bail_and_delete;
}
pathcpy(guid_link, sizeof(guid_link), fss->snapshot_dir);
pathcat(guid_link, sizeof(guid_link), name);
pathcat(guid_link, sizeof(guid_link), SNAPSHOT_GUID);
/* store GUID persistently as symlink target */
ret = windows_symlink_directory(guid, guid_link);
if (ret != 0) {
log_error(errno, "Error creating GUID symlink '%s'. %s.\n", guid_link, strerror(errno));
windows_rmdir(link_path);
goto bail_and_delete;
}
return 0;
bail_and_delete:
/* destroy the shadow copy we just created */
snprintf(cmd, sizeof(cmd), "Get-WmiObject Win32_ShadowCopy | Where-Object {$_.ID -eq '%s'} | Remove-WmiObject", guid);
windows_ps(cmd, 0, 0);
return -1;
}
int fssnapshot_delete(const struct fssnapshot_struct* fss, const char* name)
{
char guid[PATH_MAX];
char cmd[PS_CMD_MAX];
char guid_link[PATH_MAX];
pathcpy(guid_link, sizeof(guid_link), fss->snapshot_dir);
pathcat(guid_link, sizeof(guid_link), name);
pathcat(guid_link, sizeof(guid_link), SNAPSHOT_GUID);
if (windows_read_guid(guid_link, guid, sizeof(guid)) != 0)
return -1;
/* if the GUID link is gone, assume everything is gone */
if (guid[0] == 0) {
/* remove as not valid anymore */
windows_delete_link(fss, name);
return 0;
}
/* destroy the VSS shadow copy */
snprintf(cmd, sizeof(cmd), "Get-WmiObject Win32_ShadowCopy | Where-Object {$_.ID -eq '%s'} | Remove-WmiObject", guid);
int ret = windows_ps(cmd, 0, 0);
if (ret != 0) {
log_error(errno, "Error destroy the snapshot from GUID '%s'. %s.\n", guid, strerror(errno));
/* remove as not valid anymore */
windows_delete_link(fss, name);
return -1;
}
/* remove live symlink if present */
windows_delete_link(fss, name);
/* remove persistent GUID symlink */
if (windows_rmdir(guid_link) != 0) {
log_error(errno, "Error rmdir '%s'. %s.\n", guid_link, strerror(errno));
return -1;
}
return 0;
}
int fssnapshot_rename(const struct fssnapshot_struct* fss, const char* old_name, const char* new_name)
{
char old_guid[PATH_MAX];
char new_guid[PATH_MAX];
pathcpy(old_guid, sizeof(old_guid), fss->snapshot_dir);
pathcat(old_guid, sizeof(old_guid), old_name);
pathcat(old_guid, sizeof(old_guid), SNAPSHOT_GUID);
pathcpy(new_guid, sizeof(new_guid), fss->snapshot_dir);
pathcat(new_guid, sizeof(new_guid), new_name);
pathcat(new_guid, sizeof(new_guid), SNAPSHOT_GUID);
if (windows_rename(old_guid, new_guid) != 0) {
log_error(errno, "Error renaming '%s' to '%s'. %s.\n", old_guid, new_guid, strerror(errno));
return -1;
}
/* the rename is the latest operation, and then we don't care about the links, we just remove both */
windows_delete_link(fss, old_name);
windows_delete_link(fss, new_name);
return 0;
}
void fssnapshot_unmount(const struct fssnapshot_struct* fss)
{
/*
* Remove symlinks on exit to prevent "Dangling Links."
* Since \Device\HarddiskVolumeShadowCopyN paths are reassigned by the kernel
* at boot, a stale link would either point to a non-existent device or,
* worse, a different disk's snapshot.
*
* Deleting the link ensures system hygiene and prevents users from seeing
* "Location not available" errors or incorrect data after a reboot.
*/
windows_delete_link(fss, SNAPSHOT_PENDING);
windows_delete_link(fss, SNAPSHOT_STABLE);
}
/****************************************************************************/
/* resolve */
@ -425,15 +1017,15 @@ static void devattr_property(HANDLE h, char* serial, char* model, char* inter)
case BusTypeiScsi : type = "iSCSI"; break;
case BusTypeSas : type = "SAS"; break;
case BusTypeSata : type = "SATA"; break;
case BusTypeSd : type = "SD"; break;
case BusTypeMmc : type = "MMC"; break;
case BusTypeSd : type = "SD"; break; /* Secure Digital cards */
case BusTypeMmc : type = "MMC"; break; /* NAND Embedded MultiMediaCards (eMMC) */
case BusTypeVirtual : type = "Virtual"; break;
case BusTypeFileBackedVirtual : type = "Virtual"; break;
case BusTypeSpaces : type = "Storage Spaces"; break;
case BusTypeNvme : type = "NVMe"; break;
case BusTypeSCM : type = "SCM"; break;
case BusTypeUfs : type = "UFS"; break;
case BusTypeNvmeof : type = "NVMe"; break;
case BusTypeNvme : type = "NVMe"; break; /* NAND */
case BusTypeSCM : type = "SCM"; break; /* NAND Storage Class Memory */
case BusTypeUfs : type = "UFS"; break; /* NAND Universal Flash Storage */
case BusTypeNvmeof : type = "NVMe"; break; /* NAND NVMe over Fabrics */
default :
}
if (inter && type) /* always override interface as more detailed than smartctl */

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2015 Andrea Mazzoleni
#include "portable.h"
#include "os/portable.h"
#include "stream.h"
#include "support.h"

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2011 Andrea Mazzoleni
#include "portable.h"
#include "os/portable.h"
#include "support.h"

View file

@ -1,160 +0,0 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2026 Andrea Mazzoleni
#ifndef __OS_H
#define __OS_H
/****************************************************************************/
/* signal */
/**
* Enable or disable signal handling.
* @param enable 1 to enable signals, 0 to disable
*/
void os_signal_set(int enable);
/**
* Initialize signal handling.
*/
void os_signal_init(void (*handler_term)(int sig), void (*handler_hup)(int sig));
/**
* Restore signal handlers after fork in child process.
* This resets signals to default handling.
*/
void os_signal_restore_after_fork(void);
/****************************************************************************/
/* os */
/**
* Initializes the system.
*/
void os_init(int opt);
/**
* Deinitializes the system.
*/
void os_done(void);
/**
* Get the os_tick counter value.
*
* Note that the frequency is unspecified, because the time measure
* is meant to be used to compare the ratio between usage times.
*/
uint64_t os_tick(void);
/**
* Get the os_tick counter value in millisecond.
*/
uint64_t os_tick_ms(void);
/**
* Abort the process with a stacktrace.
*/
void os_abort(void) __noreturn;
/**
* Clear the screen.
*/
void os_clear(void);
/**
* Opaque handle tracking a process execution stream and its process ID.
*
* This structure encapsulates a standard buffered I/O stream tied to the
* redirected standard output of a child process spawned via os_popen().
*/
#ifdef __MINGW32__
#define OS_FILE FILE
#else
typedef struct OS_FILE {
FILE* fp; /**< Standard I/O buffered stream wrapper. */
pid_t pid; /**< Process ID of the child process. */
} OS_FILE;
#endif
/**
* Spawns a child process and opens a buffered stream to read its stdout.
*
* This function acts as a safer alternative to standard popen(), accepting an
* explicit argument vector rather than evaluating a shell command string.
*
* The child process is completely isolated in its own process group (via setpgid)
* with its stdin and stderr redirected to /dev/null. The underlying pipe is
* created with O_CLOEXEC set to prevent leakage across concurrent forks.
*
* @param argv A NULL-terminated array of strings representing the argument vector.
* argv[0] must contain the absolute path to the verified executable.
* @return A pointer to an initialized OS_FILE structure on success, or NULL on failure.
* On failure, errno is set appropriately.
*/
OS_FILE* os_popen(const char** argv);
/**
* Reads a line from an OS execution stream.
*
* Reads characters from the specified stream into the buffer string until
* (size - 1) characters are read, a newline character is read and transferred,
* or an end-of-file (EOF) condition is encountered. A terminating null
* character is appended.
*
* @param s Pointer to an array of chars where the string read is stored.
* @param size Maximum number of characters to be read (including the null character).
* @param stream Pointer to the OS_FILE tracking context.
*
* @return On success, returns the buffer pointer @p s. If EOF is reached or a read
* error occurs before any characters are read, returns NULL.
*/
char* os_fgets(char* s, int size, OS_FILE* stream);
/**
* Closes an execution stream and reaps the associated child process.
*
* Closes the underlying buffered standard I/O stream, releases the allocated
* tracking context container, and blocks until the associated child process
* terminates to prevent the creation of zombie processes.
*
* @param stream Pointer to the active OS_FILE context to close and free.
*
* @return The termination status of the child process on success, or -1 on failure.
* On failure, errno is set appropriately.
*/
int os_pclose(OS_FILE* stream);
/*
* Fork and execute a verified executable, discarding all I/O.
*
* Spawns @argv[0] in a new process with stdin, stdout and stderr all
* redirected to /dev/null. Use this for fire-and-forget tasks where the
* child's output is not needed.
*
* The child is placed in its own process group (setpgid) to isolate it
* from signals sent to the daemon's process group.
*
* @argv NULL-terminated argument vector. argv[0] must be the absolute path
* to the executable.
*
* Returns the child execit status on success, or -1 on failure.
*/
int os_spawn_and_wait(const char** argv);
/**
* Get the optimal CPU for the speed test
*/
int os_get_optimal_cpu(void);
/**
* Fill memory with pseudo-random values
*/
int os_randomize(void* ptr, size_t size);
/**
* Validates a string for exec.
* Returns -1 if dangerous characters are detected, 0 otherwise.
*/
int os_validate_exec_input(const char* str);
#endif

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2011 Andrea Mazzoleni
#include "portable.h"
#include "os/portable.h"
#include "support.h"
#include "elem.h"

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2013 Andrea Mazzoleni
#include "portable.h"
#include "os/portable.h"
#include "support.h"
#include "elem.h"

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2013 Andrea Mazzoleni
#include "portable.h"
#include "os/portable.h"
#include "util.h"
#include "elem.h"

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2011 Andrea Mazzoleni
#include "portable.h"
#include "os/portable.h"
#include "support.h"
#include "elem.h"

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2013 Andrea Mazzoleni
#include "portable.h"
#include "os/portable.h"
#include "support.h"
#include "elem.h"

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2014 Andrea Mazzoleni
#include "portable.h"
#include "os/portable.h"
#include "support.h"
#include "search.h"

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2011 Andrea Mazzoleni
#include "portable.h"
#include "os/portable.h"
#include "snapraid.h"
#include "util.h"

View file

@ -1,9 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2011 Andrea Mazzoleni
#include "portable.h"
#include "os/portable.h"
#include "os.h"
#include "snapraid.h"
#include "support.h"
#include "elem.h"
@ -1413,7 +1412,7 @@ int snapraid_main(int argc, char* argv[])
}
}
os_init(opt.force_scan_winfind);
os_init(OS_INIT_OPT_AVOID_SLEEP | (opt.force_scan_winfind ? OS_INIT_OPT_WINFIND : 0));
app_init();
raid_init();
crc32c_init();
@ -2092,19 +2091,19 @@ int snapraid_main(int argc, char* argv[])
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
if (app_global_interrupt()) {
if (os_signal_interrupt()) {
/* LCOV_EXCL_START */
#ifdef _WIN32
exit(STATUS_CONTROL_C_EXIT);
#else
/* restore default handler */
signal(app_global_interrupt(), SIG_DFL);
signal(os_signal_interrupt(), SIG_DFL);
/* raise the signal again*/
raise(app_global_interrupt());
raise(os_signal_interrupt());
/* if raise() didn't terminate */
_exit(128 + app_global_interrupt());
_exit(128 + os_signal_interrupt());
#endif
/* LCOV_EXCL_STOP */
}

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2011 Andrea Mazzoleni
#include "portable.h"
#include "os/portable.h"
#include "snapraid.h"
#include "util.h"

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2011 Andrea Mazzoleni
#include "portable.h"
#include "os/portable.h"
#include "elem.h"
#include "import.h"
@ -5050,7 +5050,7 @@ int state_progress_begin(struct snapraid_state* state, block_off_t blockstart, b
return -1;
/* stop if requested */
if (app_global_interrupt()) {
if (os_signal_interrupt()) {
/* LCOV_EXCL_START */
if (!state->opt.gui) {
msg_status("Not starting due to interruption\n");
@ -5571,7 +5571,7 @@ int state_progress(struct snapraid_state* state, struct snapraid_io* io, block_o
}
/* stop if requested */
if (app_global_interrupt()) {
if (os_signal_interrupt()) {
/* LCOV_EXCL_START */
if (!state->opt.gui) {
msg_bar("\n");

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2013 Andrea Mazzoleni
#include "portable.h"
#include "os/portable.h"
#include "support.h"
#include "elem.h"

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2011 Andrea Mazzoleni
#include "portable.h"
#include "os/portable.h"
#include "support.h"
#include "util.h"

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2011 Andrea Mazzoleni
#include "portable.h"
#include "os/portable.h"
#include "support.h"
@ -362,10 +362,18 @@ static void vmsg(FILE* out, const char* format, va_list ap)
free(dup);
}
void log_fatal(int err, const char* format, ...)
static void msg(FILE* out, const char* format, ...) __attribute__((format(attribute_printf, 2, 3)));
static void msg(FILE* out, const char* format, ...)
{
va_list ap;
va_start(ap, format);
vmsg(out, format, ap);
va_end(ap);
}
void vlog_fatal(int err, const char* format, va_list ap, const char* post)
{
lock_msg();
if (stdlog) {
@ -374,26 +382,41 @@ void log_fatal(int err, const char* format, ...)
else
fprintf(stdlog, "msg:fatal: ");
va_start(ap, format);
vfprintf(stdlog, format, ap);
va_end(ap);
va_list ap_copy;
va_copy(ap_copy, ap);
vfprintf(stdlog, format, ap_copy);
va_end(ap_copy);
if (post)
fprintf(stdlog, "%s", post);
fflush(stdlog);
}
va_start(ap, format);
vmsg(stderr, format, ap);
va_end(ap);
va_list ap_copy2;
va_copy(ap_copy2, ap);
vmsg(stderr, format, ap_copy2);
va_end(ap_copy2);
if (post)
msg(stderr, "%s", post);
fflush(stderr);
unlock_msg();
}
void log_error(int err, const char* format, ...)
void log_fatal(int err, const char* format, ...)
{
va_list ap;
va_start(ap, format);
vlog_fatal(err, format, ap, 0);
va_end(ap);
}
void vlog_error(int err, const char* format, va_list ap, const char* post)
{
lock_msg();
if (stdlog) {
@ -402,15 +425,23 @@ void log_error(int err, const char* format, ...)
else
fprintf(stdlog, "msg:error: ");
va_start(ap, format);
vfprintf(stdlog, format, ap);
va_end(ap);
va_list ap_copy;
va_copy(ap_copy, ap);
vfprintf(stdlog, format, ap_copy);
va_end(ap_copy);
if (post)
fprintf(stdlog, "%s", post);
fflush(stdlog);
} else {
va_start(ap, format);
vmsg(stderr, format, ap);
va_end(ap);
va_list ap_copy;
va_copy(ap_copy, ap);
vmsg(stderr, format, ap_copy);
va_end(ap_copy);
if (post)
msg(stderr, "%s", post);
fflush(stderr);
}
@ -418,11 +449,17 @@ void log_error(int err, const char* format, ...)
unlock_msg();
}
void log_info(int err, const char* format, ...)
void log_error(int err, const char* format, ...)
{
va_list ap;
va_start(ap, format);
vlog_error(err, format, ap, 0);
va_end(ap);
}
void vlog_info(int err, const char* format, va_list ap, const char* post)
{
lock_msg();
if (stdlog) {
@ -431,15 +468,23 @@ void log_info(int err, const char* format, ...)
else
fprintf(stdlog, "msg:info: ");
va_start(ap, format);
vfprintf(stdlog, format, ap);
va_end(ap);
va_list ap_copy;
va_copy(ap_copy, ap);
vfprintf(stdlog, format, ap_copy);
va_end(ap_copy);
if (post)
fprintf(stdlog, "%s", post);
fflush(stdlog);
} else {
va_start(ap, format);
vmsg(stderr, format, ap);
va_end(ap);
va_list ap_copy;
va_copy(ap_copy, ap);
vmsg(stderr, format, ap_copy);
va_end(ap_copy);
if (post)
msg(stderr, "%s", post);
fflush(stderr);
}
@ -447,6 +492,36 @@ void log_info(int err, const char* format, ...)
unlock_msg();
}
void log_info(int err, const char* format, ...)
{
va_list ap;
va_start(ap, format);
vlog_info(err, format, ap, 0);
va_end(ap);
}
void os_syslog(int level, const char* format, ...)
{
va_list ap;
va_start(ap, format);
switch (level) {
case OS_LVL_CRITICAL :
vlog_fatal(ESYSLOG, format, ap, "\n");
break;
case OS_LVL_ERROR :
vlog_error(ESYSLOG, format, ap, "\n");
break;
case OS_LVL_WARNING :
case OS_LVL_INFO :
/* do not log */
break;
}
va_end(ap);
}
void log_expected(int err, const char* format, ...)
{
va_list ap;

View file

@ -4,7 +4,6 @@
#ifndef __SUPPORT_H
#define __SUPPORT_H
#include "os.h"
#include "app.h"
/****************************************************************************/
@ -93,6 +92,7 @@ typedef void log_ptr(int err, const char* format, ...) __attribute__((format(att
*
* These messages go in the log file and in stderr unconditionally.
*/
void vlog_fatal(int err, const char* format, va_list ap, const char* post);
void log_fatal(int err, const char* format, ...) __attribute__((format(attribute_printf, 2, 3)));
/**
@ -106,6 +106,7 @@ void log_fatal(int err, const char* format, ...) __attribute__((format(attribute
*
* These messages go in the log file if specified, otherwise they go in stderr.
*/
void vlog_error(int err, const char* format, va_list ap, const char* post);
void log_error(int err, const char* format, ...) __attribute__((format(attribute_printf, 2, 3)));
/**

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2011 Andrea Mazzoleni
#include "portable.h"
#include "os/portable.h"
#include "support.h"
#include "elem.h"

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2025 Andrea Mazzoleni
#include "portable.h"
#include "os/portable.h"
#include "thermal.h"
#include "state.h"
@ -328,7 +328,7 @@ void state_thermal_cooldown(struct snapraid_state* state)
sleep_time -= 30;
}
if (!app_global_interrupt()) { /* don't wake-up if we are interrupting */
if (!os_signal_interrupt()) { /* don't wake-up if we are interrupting */
log_tag("thermal:spinup\n");
/* spinup */

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2014 Andrea Mazzoleni
#include "portable.h"
#include "os/portable.h"
#include "support.h"
#include "elem.h"

View file

@ -1,832 +0,0 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2013 Andrea Mazzoleni
#include "portable.h"
#ifndef __MINGW32__ /* Only for Unix */
#include "os.h"
#include "support.h"
/****************************************************************************/
/* signal */
void os_signal_restore_after_fork(void)
{
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = SIG_DFL;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGTERM, &sa, 0);
sigaction(SIGINT, &sa, 0);
sigaction(SIGHUP, &sa, 0);
/* do not restore SIGPIPE */
/* ensure signals are unblocked */
sigset_t mask;
sigemptyset(&mask);
sigprocmask(SIG_SETMASK, &mask, NULL); /* cannot use pthread_sigmask after fork */
}
void os_signal_init(void (*handler_term)(int sig), void (*handler_hup)(int sig))
{
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = handler_term;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART; /* use the SA_RESTART to automatically restart interrupted system calls */
sigaction(SIGTERM, &sa, 0);
sigaction(SIGINT, &sa, 0);
sa.sa_handler = handler_hup;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART; /* use the SA_RESTART to automatically restart interrupted system calls */
sigaction(SIGHUP, &sa, 0);
sa.sa_handler = SIG_IGN; /* ignore the signal */
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGPIPE, &sa, 0);
}
/****************************************************************************/
/* unix */
#if !HAVE_EACCESS
int eaccess(const char* pathname, int mode)
{
return faccessat(AT_FDCWD, pathname, mode, AT_EACCESS);
}
#endif
#if HAVE_POSIX_FADVISE
int posix_fadvise_wrapper(int fd, off_t offset, off_t len, int advice)
{
int ret = posix_fadvise(fd, offset, len, advice);
if (ret == ENOENT)
return ENOSYS;
return ret;
}
#endif
int open_noatime(const char* file, int flags)
{
#ifdef O_NOATIME
int f = open(file, flags | O_NOATIME);
/* only root is allowed to use O_NOATIME, in case retry without it */
if (f == -1 && errno == EPERM)
f = open(file, flags);
return f;
#else
return open(file, flags);
#endif
}
int dirent_hidden(struct dirent* dd)
{
return dd->d_name[0] == '.';
}
size_t direct_size(void)
{
long size;
size = sysconf(_SC_PAGESIZE);
if (size == -1) {
/* LCOV_EXCL_START */
log_fatal(EEXTERNAL, "No page size\n");
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
return size;
}
const char* stat_desc(struct stat* st)
{
if (S_ISREG(st->st_mode))
return "regular";
if (S_ISDIR(st->st_mode))
return "directory";
if (S_ISCHR(st->st_mode))
return "character";
if (S_ISBLK(st->st_mode))
return "block-device";
if (S_ISFIFO(st->st_mode))
return "fifo";
if (S_ISLNK(st->st_mode))
return "symlink";
if (S_ISSOCK(st->st_mode))
return "socket";
return "unknown";
}
/****************************************************************************/
/* exec */
/*
* Securely verify and open an executable file.
*
* Performs a series of security checks on the file at @exec_path before
* returning an open file descriptor suitable for use with fexecve(2), or
* falling back to execve(2) on systems that lack it.
*
* Security model:
* - @exec_path must be absolute.
* - The path is resolved via realpath(3) to canonicalize it and eliminate
* symlinks before any further checks are performed.
* - The parent directory is opened with O_PATH | O_NOFOLLOW to pin its
* inode, and the file is opened with openat(2) relative to that pinned
* descriptor. This closes the TOCTOU window between path resolution
* and file open.
* - Both the parent directory and the file must be owned by root or the
* daemon's real/effective UID, must not be world-writable, and must not
* be group-writable by a group other than the daemon's real/effective GID.
* - The file must be a regular file with at least one execute bit set.
* - The setuid and setgid bits must not be set.
* - On systems with fexecve(2) support, the returned fd is opened without
* O_CLOEXEC if the file is a script (@is_script=1). This is necessary because
* fexecve(2) executes scripts by passing a descriptor path (e.g., /dev/fd/N)
* to the script interpreter, which requires the fd to remain open across
* the execve syscall. For compiled binaries, O_CLOEXEC is safely applied
* to prevent leaking the fd to the spawned process. On systems without
* fexecve(2), execve(2) is used with @resolved_path and O_CLOEXEC is
* always applied.
*
* @exec_path Absolute path to the executable to verify.
* @resolved_path Caller-allocated buffer of at least PATH_MAX bytes. On
* success, filled with the canonicalized path from realpath(3).
*
* Returns an open file descriptor (>= 0) on success. The caller is
* responsible for closing it. Returns -1 on any verification failure;
* the specific reason is emitted via log_error(...).
*/
static int verify_executable(const char* exec_path, char* resolved_path, int is_script)
{
struct stat st;
uid_t process_uid, process_euid;
gid_t process_gid, process_egid;
process_uid = getuid();
process_euid = geteuid();
process_gid = getgid();
process_egid = getegid();
/* verify path is absolute */
if (exec_path[0] != '/') {
log_error(EINVAL, "Path %s must be absolute", exec_path);
return -1;
}
/* resolve the path to prevent symlink attacks */
if (!realpath(exec_path, resolved_path)) {
log_error(errno, "Failed to resolve %s. %s.", exec_path, strerror(errno));
return -1;
}
char* last_slash = strrchr(resolved_path, '/');
if (last_slash == 0) {
log_error(EINVAL, "Relative execution of %s not allowed", resolved_path);
return -1;
}
if (last_slash == resolved_path) {
log_error(EINVAL, "Root dir execution of %s not allowed", resolved_path);
return -1;
}
const char* exec_name = last_slash + 1;
if (exec_name[0] == 0) {
log_error(EINVAL, "No executable name in %s", exec_path);
return -1;
}
char dir_path[PATH_MAX];
size_t dir_len = last_slash - resolved_path;
memcpy(dir_path, resolved_path, dir_len);
dir_path[dir_len] = 0;
int dir_fd = open(dir_path, O_PATH | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
if (dir_fd < 0) {
log_error(errno, "Failed to open directory %s. %s.", dir_path, strerror(errno));
return -1;
}
if (fstat(dir_fd, &st) != 0) {
log_error(errno, "Failed to stat directory %s. %s.", dir_path, strerror(errno));
close(dir_fd);
return -1;
}
/* directory must be owned by root or the daemon's real user */
if (st.st_uid != process_uid && st.st_uid != process_euid && st.st_uid != 0) {
log_error(EINVAL, "Directory %s owner must match the daemon owner or be root", dir_path);
close(dir_fd);
return -1;
}
/* directory must be not group-writable unless group matches daemon */
if ((st.st_mode & S_IWGRP) && st.st_gid != process_gid && st.st_gid != process_egid && st.st_gid != 0) {
log_error(EINVAL, "Directory %s must be not group-writable unless group matches daemon owner or root", dir_path);
close(dir_fd);
return -1;
}
/* directory must be not world-writable */
if (st.st_mode & S_IWOTH) {
log_error(EINVAL, "Directory %s must be not world-writable", dir_path);
close(dir_fd);
return -1;
}
/*
* Open the executable
*/
int flags = O_RDONLY | O_NOFOLLOW;
#if HAVE_FEXECVE
if (!is_script) {
flags |= O_CLOEXEC; /* with fexecve cannot use O_CLOEXEC (Close on Exec) for scripts */
}
#else
(void)is_script;
flags |= O_CLOEXEC;
#endif
int fd = openat(dir_fd, exec_name, flags);
if (fd < 0) {
log_error(errno, "Failed to open %s. %s.", resolved_path, strerror(errno));
close(dir_fd);
return -1;
}
close(dir_fd);
/* get the file handle (TOCTOU Protection) */
if (fstat(fd, &st) == -1) {
log_error(errno, "Failed to stat %s. %s.", resolved_path, strerror(errno));
close(fd);
return -1;
}
/* ensure it's a regular file */
if (!S_ISREG(st.st_mode)) {
log_error(EINVAL, "File %s is not a regular file", resolved_path);
close(fd);
return -1;
}
/* explicitly reject scripts if not allowed */
if (!is_script) {
char magic[2];
if (read(fd, magic, 2) == 2 && magic[0] == '#' && magic[1] == '!') {
log_error(EINVAL, "File %s is a script, which is not supported", resolved_path);
close(fd);
return -1;
}
if (lseek(fd, 0, SEEK_SET) == (off_t)-1) {
/* ignore lseek errors on block devices or pipes, though it's a regular file here */
}
}
/* ensure it has execute permissions */
if (!(st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) {
log_error(EINVAL, "File %s is not an executable", resolved_path);
close(fd);
return -1;
}
/* must be owned by root or the daemon's real user */
if (st.st_uid != process_uid && st.st_uid != process_euid && st.st_uid != 0) {
log_error(EINVAL, "File %s owner must match the daemon owner or be root", resolved_path);
close(fd);
return -1;
}
/* must be not group-writable unless group matches daemon */
if ((st.st_mode & S_IWGRP) && st.st_gid != process_gid && st.st_gid != process_egid && st.st_gid != 0) {
log_error(EINVAL, "File %s must be not group-writable unless group matches daemon owner or root", resolved_path);
close(fd);
return -1;
}
/* must be not world-writable */
if (st.st_mode & S_IWOTH) {
log_error(EINVAL, "File %s must be not world-writable", resolved_path);
close(fd);
return -1;
}
/* must be not setuid / setgid */
if (st.st_mode & (S_ISUID | S_ISGID)) {
log_error(EINVAL, "File %s has setuid/setgid bits set", resolved_path);
close(fd);
return -1;
}
return fd;
}
int os_validate_exec_input(const char* str)
{
/* reject paths trying to go up levels */
if (strstr(str, "..") != 0)
return -1;
/* reject inputs starting with '-' to prevent Flag Injection */
if (str[0] == '-')
return -1;
return 0;
}
/*
* Scrubbed environment
* Only provide the bare essentials.
*/
static char* const envp_scrubbed[] = {
"PATH="
#ifdef __APPLE__
"/opt/homebrew/bin:"
#endif
"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"TERM=dumb",
"LANG=C",
"IFS= \t\n",
NULL
};
static int pipe_cloexec(int pipefd[2])
{
#ifdef HAVE_PIPE2
return pipe2(pipefd, O_CLOEXEC);
#else
if (pipe(pipefd) < 0)
return -1;
for (int i = 0; i < 2; i++) {
int flags = fcntl(pipefd[i], F_GETFD);
if (flags < 0)
goto bail;
if (fcntl(pipefd[i], F_SETFD, flags | FD_CLOEXEC) < 0)
goto bail;
}
return 0;
bail:
close(pipefd[0]);
close(pipefd[1]);
return -1;
#endif
}
/*
* Fork and execute a verified executable, capturing stdout.
*
* Spawns @argv[0] in a new process with stdout connected to a pipe whose
* read end is returned in @stdout_fd. stdin and stderr are redirected to
* /dev/null. Use this when the child's error output needs to be read and
* processed by the daemon.
*
* The child is placed in its own process group (setpgid) to isolate it
* from signals sent to the daemon's process group. The daemon can terminate
* the child and all its descendants with kill(-pid, SIGTERM).
*
* The pipe is created with O_CLOEXEC on both ends. The write end is closed
* in the parent after fork. The read end's buffer is reduced to 4096 bytes
* to improve read responsiveness on low-volume output.
*
* @argv NULL-terminated argument vector. argv[0] must be the absolute
* path to the executable.
* @stdout_fd On success, set to the read end of the stdout pipe. The caller
* is responsible for closing it when done.
*
* Returns the child PID on success, or -1 on failure.
*/
static pid_t os_spawn_stdout(const char** argv, int* stdout_fd)
{
char resolved_path[PATH_MAX];
int out_pipe[2];
pid_t pid;
int fd = verify_executable(argv[0], resolved_path, 0);
if (fd < 0) {
return -1;
}
if (pipe_cloexec(out_pipe) < 0) {
close(fd);
return -1;
}
pid = fork();
if (pid < 0) {
close(out_pipe[0]);
close(out_pipe[1]);
close(fd);
return -1;
}
if (pid == 0) {
/* child process */
/*
* Create a new process group for the child.
* This isolates the child from signals sent to the daemon's process group
* and allows the daemon to kill this process and all its future children
* (the entire group) using kill(-pid, SIGTERM).
*/
setpgid(0, 0);
/* io sandboxing */
int null_fd = open("/dev/null", O_RDWR | O_CLOEXEC);
if (null_fd < 0)
_exit(126);
/* stdin -> /dev/null */
/* stdout -> pipe */
/* stderr -> /dev/null */
if (dup2(null_fd, STDIN_FILENO) < 0
|| dup2(out_pipe[1], STDOUT_FILENO) < 0
|| dup2(null_fd, STDERR_FILENO) < 0)
_exit(126);
close(out_pipe[0]);
close(out_pipe[1]);
/* if the fd we opened is not one of the standard ones, close it */
if (null_fd > STDERR_FILENO)
close(null_fd);
#if defined(CLOSE_RANGE_CLOEXEC) && defined(HAVE_CLOSE_RANGE)
/*
* Set all fd to be closed on exec as extra safety measure
*
* fallback: if it fails, we assume to be still safe, as all fds and
* sockets should be already created with CLOEXEC.
*/
close_range(3, ~0U, CLOSE_RANGE_CLOEXEC);
#endif
/* restore and unblock signals */
os_signal_restore_after_fork();
/* use the resolved path for execution */
argv[0] = resolved_path;
/*
* Direct Execution via File Descriptor
* The kernel uses the shebang in the FD to find the interpreter.
*/
#if HAVE_FEXECVE
fexecve(fd, (char**)argv, envp_scrubbed);
#else
/* fallback: unfortunately must use the path */
execve(resolved_path, (char**)argv, envp_scrubbed);
#endif
_exit(127);
}
/* parent */
close(fd);
#ifdef F_SETPIPE_SZ /* not available on macOS */
/* set the pipe buffer to the minimum to improve responsiveness */
if (fcntl(out_pipe[0], F_SETPIPE_SZ, 4096) == -1) {
log_error(errno, "Failed to set pipe size. %s.", strerror(errno));
/* log non-fatal error or ignore */
}
#endif
close(out_pipe[1]);
*stdout_fd = out_pipe[0];
return pid;
}
static int os_wait(pid_t pid, int* status)
{
int ret;
do {
ret = waitpid(pid, status, 0);
} while (ret == -1 && errno == EINTR);
return ret;
}
int os_spawn_and_wait(const char** argv)
{
char resolved_path[PATH_MAX];
pid_t pid;
int fd = verify_executable(argv[0], resolved_path, 0);
if (fd < 0)
return -1;
pid = fork();
if (pid < 0) {
log_error(errno, "Fork failed. %s.", strerror(errno));
close(fd);
return -1;
}
if (pid == 0) {
/* child process */
/*
* Create a new process group for the child.
* This isolates the child from signals sent to the daemon's process group
* and allows the daemon to kill this process and all its future children
* (the entire group) using kill(-pid, SIGTERM).
*/
setpgid(0, 0);
/* io sandboxing */
int null_fd = open("/dev/null", O_RDWR | O_CLOEXEC);
if (null_fd < 0)
_exit(126);
/* stdin -> /dev/null */
/* stdout -> /dev/null */
/* stderr -> /dev/null */
if (dup2(null_fd, STDIN_FILENO) < 0
|| dup2(null_fd, STDOUT_FILENO) < 0
|| dup2(null_fd, STDERR_FILENO) < 0)
_exit(126);
/* if the fd we opened is not one of the standard ones, close it */
if (null_fd > STDERR_FILENO)
close(null_fd);
#if defined(CLOSE_RANGE_CLOEXEC) && defined(HAVE_CLOSE_RANGE)
/*
* Set all fd to be closed on exec as extra safety measure
*
* fallback: if it fails, we assume to be still safe, as all fds and
* sockets should be already created with CLOEXEC.
*/
close_range(3, ~0U, CLOSE_RANGE_CLOEXEC);
#endif
/* restore and unblock signals */
os_signal_restore_after_fork();
/* use the resolved path for execution */
argv[0] = resolved_path;
/*
* Direct Execution via File Descriptor
* The kernel uses the shebang in the FD to find the interpreter.
*/
#if HAVE_FEXECVE
fexecve(fd, (char**)argv, envp_scrubbed);
#else
/* fallback: unfortunately must use the path */
execve(resolved_path, (char**)argv, envp_scrubbed);
#endif
_exit(127);
}
/* parent */
close(fd);
int status;
int ret = os_wait(pid, &status);
if (ret == -1) {
/* LCOV_EXCL_START */
log_error(errno, "Wait %s failed. %s.", resolved_path, strerror(errno));
return -1;
/* LCOV_EXCL_STOP */
}
if (WIFEXITED(status)) {
return WEXITSTATUS(status);
} else if (WIFSIGNALED(status)) {
return 128 + WTERMSIG(status);
}
return -1;
}
OS_FILE* os_popen(const char** argv)
{
int stdout_fd = -1;
pid_t pid = os_spawn_stdout(argv, &stdout_fd);
if (pid < 0) {
return 0;
}
FILE* fp = fdopen(stdout_fd, "r");
if (!fp) {
/* LCOV_EXCL_START */
int saved_errno = errno;
close(stdout_fd);
int status;
os_wait(pid, &status);
errno = saved_errno;
return 0;
/* LCOV_EXCL_STOP */
}
OS_FILE* os_file = malloc_nofail(sizeof(OS_FILE));
os_file->fp = fp;
os_file->pid = pid;
return os_file;
}
char* os_fgets(char* s, int size, OS_FILE* stream)
{
return fgets(s, size, stream->fp);
}
int os_pclose(OS_FILE* stream)
{
/* leep handles locally to free container block immediately */
FILE* fp = stream->fp;
pid_t pid = stream->pid;
free(stream);
/* close buffered stream, releasing the underlying fd */
fclose(fp);
int status = 0;
if (os_wait(pid, &status) < 0) {
return -1;
}
return status;
}
/****************************************************************************/
/* os */
uint64_t os_tick(void)
{
#if HAVE_MACH_ABSOLUTE_TIME
/* for Mac OS X */
return mach_absolute_time();
#elif HAVE_CLOCK_GETTIME && (defined(CLOCK_MONOTONIC) || defined(CLOCK_MONOTONIC_RAW))
/* for Linux */
struct timespec tv;
/* nanosecond precision with clock_gettime() */
#if defined(CLOCK_MONOTONIC_RAW)
if (clock_gettime(CLOCK_MONOTONIC_RAW, &tv) != 0) {
#else
if (clock_gettime(CLOCK_MONOTONIC, &tv) != 0) {
#endif
/* LCOV_EXCL_START */
return 0;
/* LCOV_EXCL_STOP */
}
return tv.tv_sec * 1000000000ULL + tv.tv_nsec;
#else
/* other platforms */
struct timeval tv;
/* microsecond precision with gettimeofday() */
if (gettimeofday(&tv, 0) != 0) {
/* LCOV_EXCL_START */
return 0;
/* LCOV_EXCL_STOP */
}
return tv.tv_sec * 1000000ULL + tv.tv_usec;
#endif
}
uint64_t os_tick_ms(void)
{
struct timeval tv;
if (gettimeofday(&tv, 0) != 0) {
/* LCOV_EXCL_START */
return 0;
/* LCOV_EXCL_STOP */
}
return tv.tv_sec * 1000ULL + tv.tv_usec / 1000;
}
int os_randomize(void* ptr, size_t size)
{
int f;
ssize_t ret;
f = open("/dev/urandom", O_RDONLY);
if (f == -1) {
/* LCOV_EXCL_START */
return -1;
/* LCOV_EXCL_STOP */
}
ret = read(f, ptr, size);
if (ret < 0 || (size_t)ret != size) {
/* LCOV_EXCL_START */
close(f);
return -1;
/* LCOV_EXCL_STOP */
}
if (close(f) != 0) {
/* LCOV_EXCL_START */
return -1;
/* LCOV_EXCL_STOP */
}
return 0;
}
void os_clear(void)
{
/* ANSI codes */
printf("\033[H"); /* cursor at topleft */
printf("\033[2J"); /* clear screen */
}
/* LCOV_EXCL_START */
void os_abort(void)
{
#if HAVE_BACKTRACE && HAVE_BACKTRACE_SYMBOLS
void* stack[32];
char** messages;
unsigned size;
unsigned i;
#endif
printf("Stacktrace of " PACKAGE " v" VERSION);
#ifdef _linux
printf(", linux");
#endif
#ifdef __GNUC__
printf(", gcc " __VERSION__);
#endif
printf(", %d-bit", (int)sizeof(void*) * 8);
printf(", PATH_MAX=%d", PATH_MAX);
printf("\n");
#if HAVE_BACKTRACE && HAVE_BACKTRACE_SYMBOLS
size = backtrace(stack, 32);
messages = backtrace_symbols(stack, size);
for (i = 1; i < size; ++i) {
const char* msg;
if (messages)
msg = messages[i];
else
msg = "<unknown>";
printf("[bt] %02u: %s\n", i, msg);
if (messages) {
int ret;
char addr2line[1024];
size_t j = 0;
while (msg[j] != '(' && msg[j] != ' ' && msg[j] != 0)
++j;
snprintf(addr2line, sizeof(addr2line), "addr2line %p -e %.*s", stack[i], (unsigned)j, msg);
ret = system(addr2line);
if (WIFEXITED(ret) && WEXITSTATUS(ret) != 0)
printf("exit:%d\n", WEXITSTATUS(ret));
if (WIFSIGNALED(ret))
printf("signal:%d\n", WTERMSIG(ret));
}
}
#endif
printf("Please report this error to the SnapRAID Issues:\n");
printf("https://github.com/amadvance/snapraid/issues\n");
abort();
}
/* LCOV_EXCL_STOP */
void os_init(int opt)
{
(void)opt;
}
void os_done(void)
{
}
#endif

View file

@ -1,11 +1,11 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2013 Andrea Mazzoleni
#include "portable.h"
#include "os/portable.h"
#ifndef __MINGW32__ /* Only for Unix */
#include "os.h"
#include "app.h"
#include "support.h"
/****************************************************************************/
@ -23,7 +23,7 @@ int exit_sync_needed = 2;
volatile int global_interrupt = 0;
int app_global_interrupt(void)
int os_signal_interrupt(void)
{
return global_interrupt;
}
@ -1295,7 +1295,6 @@ static int devuuid_blkid(uint64_t device, char* uuid, size_t uuid_size)
}
#endif
/**
* Get the LABEL using libblkid.
* It uses a cache to work without root permission, resulting in LABEL
@ -1594,118 +1593,6 @@ int devuuid(uint64_t device_id, const char* device_path, char* uuid, size_t uuid
/****************************************************************************/
/* fs */
int filephy(const char* path, uint64_t size, uint64_t* physical)
{
#if HAVE_LINUX_FIEMAP_H
/*
* In Linux get the real physical address of the file
* Note that FIEMAP doesn't require root permission
*/
int f;
struct fiemap* fiemap;
size_t fiemap_size;
unsigned int blknum;
f = open(path, O_RDONLY);
if (f == -1) {
/* LCOV_EXCL_START */
return -1;
/* LCOV_EXCL_STOP */
}
/*
* First try with FIEMAP
* if works for ext2, ext3, ext4, xfs, btrfs
*/
fiemap_size = sizeof(struct fiemap) + sizeof(struct fiemap_extent);
fiemap = malloc_nofail(fiemap_size);
memset(fiemap, 0, fiemap_size);
fiemap->fm_start = 0;
fiemap->fm_length = ~0ULL;
fiemap->fm_flags = FIEMAP_FLAG_SYNC; /* required to ensure that just created files report a valid address and not 0 */
fiemap->fm_extent_count = 1; /* we are interested only at the first block */
if (ioctl(f, FS_IOC_FIEMAP, fiemap) != -1) {
uint32_t flags = fiemap->fm_extents[0].fe_flags;
uint64_t offset = fiemap->fm_extents[0].fe_physical;
/* check some condition for validating the offset */
if (flags & FIEMAP_EXTENT_DATA_INLINE) {
/* if the data is inline, we don't have an offset to report */
*physical = FILEPHY_WITHOUT_OFFSET;
} else if (flags & FIEMAP_EXTENT_UNKNOWN) {
/* if the offset is unknown, we don't have an offset to report */
*physical = FILEPHY_WITHOUT_OFFSET;
} else if (offset == 0) {
/*
* 0 is the general fallback for file-systems when
* they don't have an offset to report
*/
*physical = FILEPHY_WITHOUT_OFFSET;
} else {
/* finally report the real offset */
*physical = offset + FILEPHY_REAL_OFFSET;
}
free(fiemap);
if (close(f) == -1)
return -1;
return 0;
}
free(fiemap);
/* if the file is empty, FIBMAP doesn't work, and we don't even try to use it */
if (size == 0) {
*physical = FILEPHY_WITHOUT_OFFSET;
if (close(f) == -1)
return -1;
return 0;
}
/*
* Then try with FIBMAP
* it works for jfs, reiserfs, ntfs-3g
* in exfat it always returns 0, that it's anyway better than the fake inodes
*/
blknum = 0; /* first block */
if (ioctl(f, FIBMAP, &blknum) != -1) {
*physical = blknum + FILEPHY_REAL_OFFSET;
if (close(f) == -1)
return -1;
return 0;
}
/*
* Otherwise don't use anything, and keep the directory traversal order
* at now this should happen only for vfat
* and it's surely better than using fake inodes
*/
*physical = FILEPHY_UNREPORTED_OFFSET;
if (close(f) == -1)
return -1;
#else
/*
* In a generic Unix use a dummy value for all the files
* We don't want to risk to use the inode without knowing
* if it really improves performance.
* In this way we keep them in the directory traversal order
* that at least keeps files in the same directory together.
* Note also that in newer file-system with snapshot, like ZFS,
* the inode doesn't represent even more the disk position, because files
* are not overwritten in place, but rewritten in another location
* of the disk.
*/
*physical = FILEPHY_UNREPORTED_OFFSET;
(void)path; /* not used here */
(void)size;
#endif
return 0;
}
/* from man statfs */
#define ADFS_SUPER_MAGIC 0xadf5
#define AFFS_SUPER_MAGIC 0xADFF
@ -1800,6 +1687,9 @@ int filephy(const char* path, uint64_t size, uint64_t* physical)
#define UFS_BYTESWAPPED_SUPER_MAGIC 0x54190100
#define VMHGFS_SUPER_MAGIC 0xBACBACBC
#define VZFS_SUPER_MAGIC 0x565A4653
#define BTRFS_SUPER_MAGIC 0x9123683E
#define BCACHEFS_SUPER_MAGIC 0xCA451A4E
#define ZFS_SUPER_MAGIC 0x2FC12FC1
struct filesystem_entry {
unsigned id;
@ -2035,6 +1925,9 @@ int fsinfo(const char* path, int* has_persistent_inode, int* has_syncronized_har
return 0;
}
/****************************************************************************/
/* snapshot */
#if HAVE_LINUX_DEVICE
static int fssnapshot_inode(const char* path, uint32_t magic, uint64_t root_inode, struct fssnapshot_struct* fss)
{

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2011 Andrea Mazzoleni
#include "portable.h"
#include "os/portable.h"
#include "support.h"
#include "util.h"

View file

@ -7,7 +7,7 @@ AC_CANONICAL_HOST
AC_CANONICAL_BUILD
AC_CANONICAL_TARGET
AM_INIT_AUTOMAKE([foreign no-dependencies subdir-objects])
AC_CONFIG_SRCDIR([cmdline/portable.h])
AC_CONFIG_SRCDIR([os/os.h])
AC_CONFIG_HEADERS([config.h])
dnl Enables as many system-specific extensions as reasonably possible
@ -72,7 +72,7 @@ AC_HEADER_DIRENT
AC_HEADER_SYS_WAIT
AC_CHECK_HEADERS([fcntl.h stddef.h stdint.h stdlib.h string.h limits.h time.h sys/time.h])
AC_CHECK_HEADERS([unistd.h getopt.h io.h inttypes.h byteswap.h])
AC_CHECK_HEADERS([pthread.h math.h])
AC_CHECK_HEADERS([pthread.h math.h syslog.h grp.h pwd.h])
AC_CHECK_HEADERS([sys/file.h sys/ioctl.h sys/sysmacros.h sys/mkdev.h sys/mount.h])
AC_CHECK_HEADERS([linux/fiemap.h linux/fs.h linux/btrfs.h execinfo.h])
AC_CHECK_HEADERS([mach/mach_time.h sys/sysctl.h])

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2011 Andrea Mazzoleni
// Copyright (C) 2026 Andrea Mazzoleni
#ifndef __PORTABLE_MINGW_H
#define __PORTABLE_MINGW_H
@ -8,6 +8,14 @@
#include <wchar.h>
#define SYSLOG "EventLog"
#define OS_LVL_CRITICAL 1
#define OS_LVL_ERROR 2
#define OS_LVL_WARNING 3
#define OS_LVL_INFO 4
#define OS_LVL_DEBUG 5
/* map Windows name to POSIX name */
#define strncasecmp _strnicmp
@ -39,13 +47,44 @@ wchar_t* convert_arg(wchar_t* conv_buf, const char* src, int only_if_required);
#define convert(buf, a) convert_arg(buf, a, 0)
#define convert_if_required(buf, a) convert_arg(buf, a, 1)
/**
* Convert a generic string from UTF16 to UTF8.
* \param conv_buf Destination UTF-8 buffer of size CONV_MAX.
* \param src Source UTF-16 null-terminated string.
* \return The converted string. It never fails.
*/
char* u16tou8(char* conv_buf, const wchar_t* src);
/**
* Convert a generic string from UTF8 to UTF16.
* \param conv_buf Destination UTF-16 buffer of size CONV_MAX.
* \param src Source UTF-8 null-terminated string.
* \return The converted string. It never fails.
*/
wchar_t* u8tou16(wchar_t* conv_buf, const char* src);
/**
* Convert a generic string from UTF16 to UTF8.
* \param conv_buf Destination UTF-8 buffer.
* \param number_of_char Size of the destination buffer in bytes.
* \param src Source UTF-16 string.
* \param number_of_wchar Number of wide characters to convert.
* \param result_length_without_terminator Optional pointer to receive the converted length.
* \return The converted string, or 0 on failure.
*/
char* u16tou8_mayfail(char* conv_buf, size_t number_of_char, const wchar_t* src, size_t number_of_wchar, size_t* result_length_without_terminator);
/**
* Convert a generic string from UTF8 to UTF16.
* \param conv_buf Destination UTF-16 buffer.
* \param conv_size Size of the destination buffer in wchar_t elements.
* \param src Source UTF-8 string.
* \param number_of_char Number of bytes to convert (or -1 for null-terminated string).
* \param result_length_without_terminator Optional pointer to receive the converted length.
* \return The converted string, or 0 on failure.
*/
wchar_t* u8tou16_mayfail(wchar_t* conv_buf, size_t number_of_wchar, const char* src, size_t number_of_char, size_t* result_length_without_terminator);
/****************************************************************************/
/* file */
@ -58,6 +97,8 @@ wchar_t* u8tou16(wchar_t* conv_buf, const char* src);
/* Remap functions and types */
#undef fopen
#define fopen windows_fopen
#define FOPEN_TEXT "t"
#define FOPEN_BINARY "b"
#undef open
#define open windows_open
#define open_noatime windows_open
@ -106,6 +147,7 @@ wchar_t* u8tou16(wchar_t* conv_buf, const char* src);
#undef utimensat
#define utimensat windows_utimensat
#define O_NOFOLLOW 0
#define O_CLOEXEC 0
#define dirent_hidden windows_dirent_hidden
#define HAVE_STRUCT_DIRENT_D_STAT 1
#undef HAVE_STRUCT_DIRENT_D_INO
@ -145,6 +187,12 @@ wchar_t* u8tou16(wchar_t* conv_buf, const char* src);
#define HAVE_DIRECT_IO 1
#define O_DIRECT 0x10000000
#define O_DSYNC 0x20000000
#undef gmtime_r
#define gmtime_r windows_gmtime_r
#undef localtime_r
#define localtime_r windows_localtime_r
#undef realpath
#define realpath windows_realpath
/**
* If nanoseconds are not supported, we report the special STAT_NSEC_INVALID value,
@ -155,6 +203,39 @@ wchar_t* u8tou16(wchar_t* conv_buf, const char* src);
/* We have nano second support */
#define STAT_NSEC(st) ((int)(st)->st_mtimensec)
static inline int WEXITSTATUS(DWORD status)
{
return status;
}
static inline int WTERMSIG(DWORD status)
{
/* identify most common crash reasons */
switch (status) {
case 0x40000015 : return SIGABRT; /* STATUS_FATAL_APP_EXIT */
case 0xC0000005 : return SIGSEGV; /* STATUS_ACCESS_VIOLATION */
case 0xC0000006 : return SIGSEGV; /* STATUS_IN_PAGE_ERROR */
case 0xC000001D : return SIGILL; /* STATUS_ILLEGAL_INSTRUCTION */
case 0xC00000FD : return SIGSEGV; /* STATUS_STACK_OVERFLOW */
case 0xC0000090 : return SIGFPE; /* STATUS_FLOAT_INVALID_OPERATION */
case 0xC0000094 : return SIGFPE; /* STATUS_INTEGER_DIVIDE_BY_ZERO */
case 0xC000013A : return SIGINT; /* STATUS_CONTROL_C_EXIT */
case 0xC0000409 : return SIGSEGV; /* STATUS_STACK_BUFFER_OVERRUN */
}
return 0;
}
static inline int WIFEXITED(DWORD status)
{
return status < 0xC0000000 && WTERMSIG(status) == 0;
}
static inline int WIFSIGNALED(DWORD status)
{
return WTERMSIG(status) != 0;
}
/**
* Generic stat information.
*/
@ -354,7 +435,7 @@ const char* windows_stat_desc(struct stat* st);
unsigned windows_sleep(unsigned seconds);
/**
* List usleep().
* Like usleep().
*/
void windows_usleep(uint64_t useconds);
@ -411,10 +492,25 @@ ssize_t windows_pread(int f, void* buffer, size_t size, off_t offset);
ssize_t windows_pwrite(int f, const void* buffer, size_t size, off_t offset);
/**
* List direct_size().
* Like direct_size().
*/
size_t windows_direct_size(void);
/**
* Like gmtime_r().
*/
struct tm* windows_gmtime_r(const time_t* timer, struct tm* result);
/**
* Like localtime_r().
*/
struct tm* windows_localtime_r(const time_t* timer, struct tm* result);
/**
* Like realpath().
*/
char* windows_realpath(const char* path, char* resolved_path);
/**
* Like GetFileAttributes()
*/
@ -448,17 +544,28 @@ int windows_is_wine(void);
#define HAVE_THREAD 1
typedef void* windows_thread_t;
typedef CRITICAL_SECTION windows_mutex_t;
typedef struct {
SRWLOCK srw;
BOOL exclusive;
} windows_rwlock_t;
typedef CONDITION_VARIABLE windows_cond_t;
typedef void* windows_key_t;
/* remap to pthread */
#define pid_t intptr_t
#define thread_id_t windows_thread_t
#define thread_mutex_t windows_mutex_t
#define thread_cond_t windows_cond_t
#define thread_rwlock_t windows_rwlock_t
#define thread_key_t windows_key_t
#define pthread_mutex_init windows_mutex_init
#define pthread_mutex_destroy windows_mutex_destroy
#define pthread_mutex_lock windows_mutex_lock
#define pthread_mutex_unlock windows_mutex_unlock
#define pthread_rwlock_init windows_rwlock_init
#define pthread_rwlock_destroy windows_rwlock_destroy
#define pthread_rwlock_rdlock windows_rwlock_rdlock
#define pthread_rwlock_wrlock windows_rwlock_wrlock
#define pthread_rwlock_unlock windows_rwlock_unlock
#define pthread_cond_init windows_cond_init
#define pthread_cond_destroy windows_cond_destroy
#define pthread_cond_signal windows_cond_signal
@ -478,6 +585,11 @@ int windows_mutex_init(windows_mutex_t* mutex, void* attr);
int windows_mutex_destroy(windows_mutex_t* mutex);
int windows_mutex_lock(windows_mutex_t* mutex);
int windows_mutex_unlock(windows_mutex_t* mutex);
int windows_rwlock_init(windows_rwlock_t* mutex, void* attr);
int windows_rwlock_destroy(windows_rwlock_t* mutex);
int windows_rwlock_rdlock(windows_rwlock_t* mutex);
int windows_rwlock_wrlock(windows_rwlock_t* mutex);
int windows_rwlock_unlock(windows_rwlock_t* mutex);
int windows_cond_init(windows_cond_t* cond, void* attr);
int windows_cond_destroy(windows_cond_t* cond);
int windows_cond_signal(windows_cond_t* cond);
@ -496,6 +608,23 @@ int windows_key_delete(windows_key_t key);
void* windows_getspecific(windows_key_t key);
int windows_setspecific(windows_key_t key, void* value);
/****************************************************************************/
/* service */
#if !HAVE_GETOPT_LONG
#error HAVE_GETOPT_LONG is required in Windows for the service options
#endif
/**
* Log into Eventlog
*/
void windows_eventlog(int level, const char* msg);
/**
* Signal that the service is starting and it needs more time.
*/
void windows_starting(void);
#endif
#endif

319
os/os.h Normal file
View file

@ -0,0 +1,319 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2026 Andrea Mazzoleni
#ifndef __OS_H
#define __OS_H
/****************************************************************************/
/* syslog */
/**
* Log an informational syslog message.
* The message should start with a lowercase letter and must not terminate with \n.
* \param level One of OS_LVL_*.
* \param format Printf-like format string.
*/
void os_syslog(int level, const char* format, ...) __attribute__((format(attribute_printf, 2, 3)));
/****************************************************************************/
/* signal */
/**
* Enable or disable signal handling.
* \param enable 1 to enable signals, 0 to disable.
*/
void os_signal_set(int enable);
/**
* Initialize signal handling.
* \param handler_term Signal handler callback for termination signals (SIGTERM, SIGINT).
* \param handler_hup Signal handler callback for hangup signal (SIGHUP).
*/
void os_signal_init(void (*handler_term)(int sig), void (*handler_hup)(int sig));
/**
* Restore signal handlers after fork in child process.
* This resets signals to default handling.
*/
void os_signal_restore_after_fork(void);
/**
* Get string representation of signal number.
* \param sig Signal number.
* \return Signal name string.
*/
const char* os_signal_name(int sig);
/**
* Global variable to identify if Ctrl+C is pressed.
* \return 1 if Ctrl+C was pressed/interrupted, 0 otherwise.
*/
int os_signal_interrupt(void);
/****************************************************************************/
/* exec */
/**
* Opaque handle tracking a process execution stream and its process ID.
*
* This structure encapsulates a standard buffered I/O stream tied to the
* redirected standard output of a child process spawned via os_popen().
*/
#ifdef __MINGW32__
#define OS_FILE FILE
#else
typedef struct OS_FILE {
FILE* fp; /**< Standard I/O buffered stream wrapper. */
pid_t pid; /**< Process ID of the child process. */
} OS_FILE;
#endif
/**
* Spawns a child process and opens a buffered stream to read its stdout.
*
* This function acts as a safer alternative to standard popen(), accepting an
* explicit argument vector rather than evaluating a shell command string.
*
* The child process is completely isolated in its own process group (via setpgid)
* with its stdin and stderr redirected to /dev/null. The underlying pipe is
* created with O_CLOEXEC set to prevent leakage across concurrent forks.
*
* \param argv A NULL-terminated array of strings representing the argument vector.
* argv[0] must contain the absolute path to the verified executable.
* \return A pointer to an initialized OS_FILE structure on success, or NULL on failure.
*/
OS_FILE* os_popen(const char** argv);
/**
* Reads a line from an OS execution stream.
*
* Reads characters from the specified stream into the buffer string until
* (size - 1) characters are read, a newline character is read and transferred,
* or an end-of-file (EOF) condition is encountered. A terminating null
* character is appended.
*
* \param s Pointer to an array of chars where the string read is stored.
* \param size Maximum number of characters to be read (including the null character).
* \param stream Pointer to the OS_FILE tracking context.
* \return On success, returns the buffer pointer s. If EOF is reached or a read
* error occurs before any characters are read, returns NULL.
*/
char* os_fgets(char* s, int size, OS_FILE* stream);
/**
* Closes an execution stream and reaps the associated child process.
*
* Closes the underlying buffered standard I/O stream, releases the allocated
* tracking context container, and blocks until the associated child process
* terminates to prevent the creation of zombie processes.
*
* \param stream Pointer to the active OS_FILE context to close and free.
* \return The termination status of the child process on success, or -1 on failure.
*/
int os_pclose(OS_FILE* stream);
/**
* Spawn a new process with the specified argument vector, optionally capturing stdout and/or stderr.
* \param argv Array of command line arguments.
* \param stdout_read_fd Pointer to store file descriptor for stdout, or NULL to redirect to /dev/null.
* \param stderr_read_fd Pointer to store file descriptor for stderr, or NULL to redirect to /dev/null.
* \param run_as_user User to run script as (NULL for current user).
* \return Process ID of spawned process, or -1 on failure.
*/
pid_t os_spawn(char** argv, int* stdout_read_fd, int* stderr_read_fd, const char* run_as_user);
/**
* Wait for the child process to terminate.
* \param pid Process ID of the child process.
* \param status Pointer to store the exit status.
* \return Child PID on success, -1 on failure.
*/
pid_t os_wait(pid_t pid, int* status);
/**
* Terminate gracefully a process.
* \param pid Process ID of the process to terminate.
* \return 0 on success, -1 on failure.
*/
int os_term(pid_t pid);
/**
* Fork and execute a verified executable, discarding all I/O.
*
* Spawns @argv[0] in a new process with stdin, stdout and stderr all
* redirected to /dev/null. Use this for fire-and-forget tasks where the
* child's output is not needed.
*
* The child is placed in its own process group (setpgid) to isolate it
* from signals sent to the daemon's process group.
*
* \param argv NULL-terminated argument vector. argv[0] must be the absolute path
* to the executable.
* \return The child exit status on success, or -1 on failure.
*/
int os_spawn_and_wait(const char** argv);
/**
* Execute a system command with optional user context and input.
* \param command Command to execute.
* \param run_as_user User to run command as (NULL for current user).
* \param stdin_text Text to provide as stdin (NULL for no input).
* \return Exit status of command.
*/
int os_command(const char* command, const char* run_as_user, const char* stdin_text);
/**
* Execute a script file with specified user context.
* \param argv Array of command line arguments.
* \param envp Environment variables (NULL-terminated list of strings).
* \param run_as_user User to run script as (NULL for current user).
* \return Exit status of script.
*/
int os_script(char** argv, char** envp, const char* run_as_user);
/**
* Validates a string for exec.
* \param str The input string to validate.
* \return -1 if dangerous characters are detected, 0 otherwise.
*/
int os_validate_exec_input(const char* str);
/****************************************************************************/
/* fs */
/**
* Physical offset not yet read.
*/
#define FILEPHY_UNREAD_OFFSET 0
/**
* Special value returned when the file-system doesn't report any offset for unknown reason.
*/
#define FILEPHY_UNREPORTED_OFFSET 1
/**
* Special value returned when the file doesn't have a real offset.
* For example, because it's stored in the NTFS MFT.
*/
#define FILEPHY_WITHOUT_OFFSET 2
/**
* Value indicating real offsets. All offsets greater or equal at this one are real.
*/
#define FILEPHY_REAL_OFFSET 3
/**
* Get the physical address of the specified file.
* This is expected to be just a hint and not necessarily correct or unique.
* \param path The path of the file.
* \param size The size of the file.
* \param physical Pointer to store the physical offset.
* \return 0 on success, -1 on failure.
*/
int filephy(const char* path, uint64_t size, uint64_t* physical);
/****************************************************************************/
/* privileges */
/**
* Bracketed Privileges System:
* These functions allow the daemon to run with dropped effective privileges
* by default (euid/egid set to an unprivileged user like "nobody"), while
* temporarily escalating to root privileges for specific operations that require
* administrative permissions (e.g., config changes, executing SnapRAID commands,
* managing log files).
*/
/**
* Temporarily acquire root privileges.
* Restores the effective user and group ID of the calling thread to root (0).
*/
void os_privileges_acquire(void);
/**
* Release root privileges.
* Reverts the effective user and group ID of the calling thread to the unprivileged credentials.
*/
void os_privileges_release(void);
/**
* Drop effective privileges permanently to an unprivileged user (e.g., "nobody").
* Called after startup/initialization is complete to transition the daemon into
* the Bracketed Privileges execution mode.
*/
void os_privileges_drop(void);
/****************************************************************************/
/* os */
#define OS_INIT_OPT_AVOID_SLEEP 1 /**< Prevent the system to go to sleep */
#define OS_INIT_OPT_WINFIND 2 /**< Use the legacy Windows FindFirstFile interface instead of the new stream interface */
/**
* Initializes the system.
* \param opt System initialization options.
*/
void os_init(unsigned opt);
/**
* Deinitializes the system.
*/
void os_done(void);
/**
* Get the os_tick counter value.
*
* Note that the frequency is unspecified, because the time measure
* is meant to be used to compare the ratio between usage times.
* \return Monotonic clock tick value.
*/
uint64_t os_tick(void);
/**
* Get the os_tick counter value in millisecond.
* \return Monotonic clock value in milliseconds.
*/
uint64_t os_tick_ms(void);
/**
* Get the os_tick counter value in seconds.
* \return Monotonic clock value in seconds.
*/
uint64_t os_tick_sec(void);
/**
* Abort the process with a stacktrace.
*/
void os_abort(void) __noreturn;
/**
* Exit the process with failure.
*/
void os_exit(void) __noreturn;
/**
* Shutdown the system.
* \return 0 on success, -1 on failure.
*/
int os_shutdown(void);
/**
* Clear the screen.
*/
void os_clear(void);
/**
* Fill memory with pseudo-random values
* \param ptr Pointer to the memory buffer.
* \param size Size of the memory buffer.
* \return 0 on success, -1 on failure.
*/
int os_randomize(void* ptr, size_t size);
/**
* Get the optimal CPU for the speed test
* \return Optimal CPU index.
*/
int os_get_optimal_cpu(void);
#endif

View file

@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2011 Andrea Mazzoleni
// Copyright (C) 2026 Andrea Mazzoleni
#ifndef __PORTABLE_H
#define __PORTABLE_H
@ -32,6 +32,11 @@
*/
#define _WIN32_WINNT 0x601
/**
* Enable the rand_s() function.
*/
#define _CRT_RAND_S
/**
* Undef as it clashes with windows.h declarations
*/
@ -46,7 +51,7 @@
#ifdef __MINGW32__
#if defined(__USE_MINGW_ANSI_STDIO) && __USE_MINGW_ANSI_STDIO == 1
#define attribute_printf gnu_printf /* GNU format */
#define printf __mingw_printf
#define printf(...) __mingw_printf(__VA_ARGS__) /* to support %z in printf */
#else
#define attribute_printf ms_printf /* MSVCRT format */
#endif
@ -65,7 +70,6 @@
#define __noreturn __attribute__((noreturn))
#endif
/**
* Architecture for inline assembly.
*/
@ -225,6 +229,38 @@
#include <execinfo.h>
#endif
#ifdef HAVE_STRINGS
#include <strings.h>
#endif
#if HAVE_SYSLOG_H
#include <syslog.h>
#endif
#if HAVE_GRP_H
#include <grp.h>
#endif
#if HAVE_PWD_H
#include <pwd.h>
#endif
#if HAVE_SYS_SYSINFO_H
#include <sys/sysinfo.h>
#endif
#if HAVE_SYS_UTSNAME_H
#include <sys/utsname.h>
#endif
#if HAVE_ZLIB
#include <zlib.h>
#endif
#if HAVE_ZSTD
#include <zstd.h>
#endif
#ifdef __APPLE__
#include <CoreFoundation/CoreFoundation.h>
#include <DiskArbitration/DiskArbitration.h>
@ -252,20 +288,6 @@
#include <linux/close_range.h>
#endif
/* implement close_range for glibc 2.33 or earlier */
#if defined(__linux__) && !defined(HAVE_CLOSE_RANGE)
#include <sys/syscall.h>
#ifndef __NR_close_range
#define __NR_close_range 436
#endif
#define close_range close_range_impl
static inline int close_range_impl(unsigned int first, unsigned int last, unsigned int flags)
{
return (int)syscall(__NR_close_range, first, last, flags);
}
#define HAVE_CLOSE_RANGE 1
#endif
/*
* Flags for open()
*/

1607
os/unix.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2011 Andrea Mazzoleni
// Copyright (C) 2026 Andrea Mazzoleni
#ifndef __UNIX_H
#define __UNIX_H
@ -9,6 +9,38 @@
#define HAVE_DIRECT_IO 1 /**< Support O_DIRECT in open(). */
#endif
#define SYSLOG "syslog"
#define OS_LVL_CRITICAL LOG_CRIT
#define OS_LVL_ERROR LOG_ERR
#define OS_LVL_WARNING LOG_WARNING
#define OS_LVL_INFO LOG_INFO
#define OS_LVL_DEBUG LOG_DEBUG
/* implement close_range for glibc 2.33 or earlier */
#if defined(__linux__) && !defined(HAVE_CLOSE_RANGE)
#include <sys/syscall.h>
#ifndef __NR_close_range
#define __NR_close_range 436
#endif
#define close_range close_range_impl
static inline int close_range_impl(unsigned int first, unsigned int last, unsigned int flags)
{
return (int)syscall(__NR_close_range, first, last, flags);
}
#define HAVE_CLOSE_RANGE 1
#endif
#ifndef WEXITSTATUS
#define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
#endif
#ifndef WIFEXITED
#define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
#endif
#define FOPEN_TEXT ""
#define FOPEN_BINARY ""
#define O_BINARY 0 /**< Not used in Unix. */
#define O_SEQUENTIAL 0 /**< In Unix posix_fadvise() shall be used. */
@ -119,6 +151,7 @@ typedef pthread_t thread_id_t;
typedef pthread_mutex_t thread_mutex_t;
typedef pthread_cond_t thread_cond_t;
typedef pthread_key_t thread_key_t;
typedef pthread_rwlock_t thread_rwlock_t;
#endif
#endif

View file

@ -6,7 +6,7 @@
#define tommy_calloc calloc_nofail
#define tommy_free free
#include "cmdline/portable.h"
#include "os/portable.h"
#include "cmdline/support.h" /* for malloc/calloc_nofail() */
#include "tommyhash.c"