snapraid/cmdline/support.c
Andrea Mazzoleni c6951393e8
Some checks are pending
Build / build-mac (push) Waiting to run
Build / build-linux (push) Waiting to run
Build / build-arm32 (push) Waiting to run
Build / build-win (push) Waiting to run
Build / build-freebsd (push) Waiting to run
Build / build-musl (push) Waiting to run
CodeQL Advanced / Analyze (c-cpp) (push) Waiting to run
CodeQL Advanced / Analyze (actions) (push) Waiting to run
Exit code and logging invariants for log_fatal and hardware log_error
2026-09-21 17:27:07 +02:00

2656 lines
65 KiB
C

// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2011 Andrea Mazzoleni
#include "os/portable.h"
#include "support.h"
/****************************************************************************/
/* esc */
struct esc_thread_pool {
char pool[ESC_POOL_MAX][ESC_MAX];
unsigned idx;
};
#if HAVE_THREAD
static thread_key_t esc_key;
static void esc_pool_free(void* ptr)
{
free(ptr);
}
#else
static struct esc_thread_pool esc_single_pool;
#endif
char* esc_buf(void)
{
struct esc_thread_pool* tp;
#if HAVE_THREAD
tp = thread_getspecific(esc_key);
if (!tp) {
tp = calloc_nofail(1, sizeof(*tp));
thread_setspecific(esc_key, tp);
}
#else
tp = &esc_single_pool;
#endif
char* buffer = tp->pool[tp->idx];
tp->idx = (tp->idx + 1) % ESC_POOL_MAX;
return buffer;
}
/****************************************************************************/
/* lock */
/**
* Locks used externally.
*/
#if HAVE_THREAD
static thread_mutex_t msg_lock;
static thread_mutex_t memory_lock;
static thread_mutex_t random_lock;
#endif
void lock_msg(void)
{
#if HAVE_THREAD
thread_mutex_lock(&msg_lock);
#endif
}
void unlock_msg(void)
{
#if HAVE_THREAD
thread_mutex_unlock(&msg_lock);
#endif
}
void lock_memory(void)
{
#if HAVE_THREAD
thread_mutex_lock(&memory_lock);
#endif
}
void unlock_memory(void)
{
#if HAVE_THREAD
thread_mutex_unlock(&memory_lock);
#endif
}
void lock_random(void)
{
#if HAVE_THREAD
thread_mutex_lock(&random_lock);
#endif
}
void unlock_random(void)
{
#if HAVE_THREAD
thread_mutex_unlock(&random_lock);
#endif
}
void lock_init(void)
{
#if HAVE_THREAD
/* initialize the locks as first operation as log_fatal depends on them */
thread_mutex_init(&msg_lock);
thread_mutex_init(&memory_lock);
thread_mutex_init(&random_lock);
thread_key_create(&esc_key, esc_pool_free);
#endif
}
void lock_done(void)
{
#if HAVE_THREAD
void* tp = thread_getspecific(esc_key);
if (tp) {
esc_pool_free(tp);
thread_setspecific(esc_key, NULL);
}
thread_key_delete(esc_key);
thread_mutex_destroy(&msg_lock);
thread_mutex_destroy(&memory_lock);
thread_mutex_destroy(&random_lock);
#endif
}
/****************************************************************************/
/* random */
static uint64_t random_state = 1;
void random_seed(uint64_t seed)
{
lock_random();
random_state = seed;
unlock_random();
}
void random_reseed(void)
{
uint64_t seed = os_tick();
lock_random();
random_state = seed;
unlock_random();
}
unsigned char random_u8(void)
{
return random_u64() & 0xFF;
}
/*
* SplitMix64
*
* - Zero-safe: Any 64-bit seed is valid (even 0).
* - Full period: Guaranteed to visit every 2^64 value.
* - Logic: Uses a Weyl sequence + MurmurHash3-style finalizer.
*
* See: https://rosettacode.org/wiki/Pseudo-random_numbers/Splitmix64
*/
uint64_t random_u64(void)
{
uint64_t z;
lock_random();
z = random_state += 0x9E3779B97F4A7C15ULL;
unlock_random();
z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9ULL;
z = (z ^ (z >> 27)) * 0x94D049BB133111EBULL;
return z ^ (z >> 31);
}
/****************************************************************************/
/* error */
int is_hw(int err)
{
/* LCOV_EXCL_START */
if (err == EIO)
return 1;
#ifdef EFSCORRUPTED
if (err == EFSCORRUPTED) /* used by XFS inside the kernel - never expected in user mode as converted to EUCLEAN */
return 1;
#endif
#ifdef EUCLEAN
if (err == EUCLEAN) /* used by XFS at user level instead of EFSCORRUPTED */
return 1;
#endif
#ifdef EBADCRC
if (err == EBADCRC) /* used by BTRFS - never expected in user mode as converted to EIO */
return 1;
#endif
#ifdef EBADMSG
if (err == EBADMSG) /* used by BTRFS - never expected in user mode as converted to EIO */
return 1;
#endif
#ifdef ENOMEDIUM
if (err == ENOMEDIUM) /* medium removed */
return 1;
#endif
#ifdef EHWPOISON
if (err == EHWPOISON) /* hardware memory error */
return 1;
#endif
/* LCOV_EXCL_STOP */
return 0;
}
void log_fatal_errno(int err, const char* name)
{
/* LCOV_EXCL_START */
if (err == EIO) {
log_fatal(err, "DANGER! Unexpected input/output error in disk %s. It isn't possible to continue.\n", name);
#ifdef EFSCORRUPTED
} else if (err == EFSCORRUPTED) {
log_fatal(err, "DANGER! Unexpected corrupted error in disk %s. It isn't possible to continue.\n", name);
#endif
#ifdef EUCLEAN
} else if (err == EUCLEAN) {
log_fatal(err, "DANGER! Unexpected corrupted error in disk %s. It isn't possible to continue.\n", name);
#endif
#ifdef EBADCRC
} else if (err == EBADCRC) {
log_fatal(err, "DANGER! Unexpected bad crc error in disk %s. It isn't possible to continue.\n", name);
#endif
#ifdef EBADMSG
} else if (err == EBADMSG) {
log_fatal(err, "DANGER! Unexpected bad crc error in disk %s. It isn't possible to continue.\n", name);
#endif
#ifdef ENOMEDIUM
} else if (err == ENOMEDIUM) {
log_fatal(err, "DANGER! Unexpected medium removed in disk %s. It isn't possible to continue.\n", name);
#endif
#ifdef EHWPOISON
} else if (err == EHWPOISON) {
log_fatal(err, "DANGER! Unexpected memory error in disk %s. It isn't possible to continue.\n", name);
#endif
} else if (err == EACCES) {
log_fatal(err, "WARNING! Grant permission in the disk %s. It isn't possible to continue.\n", name);
} else if (err == ENOSPC) {
log_fatal(err, "WARNING! Ensure there is free space on the disk %s. It isn't possible to continue.\n", name);
} else {
log_fatal(err, "WARNING! Without a working %s disk, it isn't possible to continue.\n", name);
}
/* LCOV_EXCL_STOP */
}
void log_error_errno(int err, const char* name)
{
/* LCOV_EXCL_START */
if (err == EIO) {
log_fatal(err, "DANGER! Unexpected input/output error in disk %s.\n", name);
#ifdef EFSCORRUPTED
} else if (err == EFSCORRUPTED) {
log_fatal(err, "DANGER! Unexpected corrupted error in disk %s.\n", name);
#endif
#ifdef EUCLEAN
} else if (err == EUCLEAN) {
log_fatal(err, "DANGER! Unexpected corrupted error in disk %s.\n", name);
#endif
#ifdef EBADCRC
} else if (err == EBADCRC) {
log_fatal(err, "DANGER! Unexpected bad crc error in disk %s.\n", name);
#endif
#ifdef EBADMSG
} else if (err == EBADMSG) {
log_fatal(err, "DANGER! Unexpected bad crc error in disk %s.\n", name);
#endif
#ifdef ENOMEDIUM
} else if (err == ENOMEDIUM) {
log_fatal(err, "DANGER! Unexpected medium removed in disk %s.\n", name);
#endif
#ifdef EHWPOISON
} else if (err == EHWPOISON) {
log_fatal(err, "DANGER! Unexpected memory error in disk %s.\n", name);
#endif
/* LCOV_EXCL_STOP */
} else if (err == EACCES) {
log_error(err, "WARNING! Grant permission in the disk %s.\n", name);
} else if (err == ENOSPC) {
log_error(err, "WARNING! Ensure there is free space on the disk %s.\n", name);
} else if (err == ENOENT) {
log_error(err, "WARNING! You cannot modify files while running.\n");
}
}
/****************************************************************************/
/* print */
int msg_level = 0;
int msg_fatal_stderr = 1;
FILE* stdlog = 0;
int msg_line_prev = 0; /**< Previous line width on stdout */
int msg_line_curr = 0; /**< Current line width on stdout */
static unsigned log_hardware_error_count = 0;
unsigned log_hardware_errors(void)
{
unsigned count;
lock_msg();
count = log_hardware_error_count;
unlock_msg();
return count;
}
/*
* Note that in the following functions we always flush both
* stdout and stderr, because we want to ensure that they mixes
* well when redirected to files
*
* The buffering is similar at the "line buffered" one, that
* is not available on Windows, so we emulate it in this way.
*
* For stdlog flushing is limited. To ensure flushing the
* caller should use log_flush().
*/
static void vmsg(FILE* out, const char* format, va_list ap)
{
char* dup = strdup_nofail(format);
ssize_t len = strlen(dup);
int written = 0;
char control = 0;
if (len > 0) {
if (dup[len - 1] == '\n') {
dup[len - 1] = 0;
control = '\n';
} else if (dup[len - 1] == '\r') {
dup[len - 1] = 0;
control = '\r';
}
}
if (dup[0]) {
written = vfprintf(out, dup, ap);
if (written < 0) /* ignore errors */
written = 0;
}
switch (control) {
case 0 :
msg_line_curr += written;
break;
case '\n' :
msg_line_curr += written;
/* writes spaces to overwrite any previous char */
while (msg_line_curr < msg_line_prev) {
fprintf(out, " ");
--msg_line_prev;
}
msg_line_prev = 0;
msg_line_curr = 0;
fprintf(out, "\n");
break;
case '\r' :
msg_line_curr += written;
/* writes spaces to overwrite any previous char */
while (msg_line_curr < msg_line_prev) {
fprintf(out, " ");
--msg_line_prev;
}
msg_line_prev = msg_line_curr;
msg_line_curr = 0;
fprintf(out, "\r");
}
free(dup);
}
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)
{
int log_failed = 0;
lock_msg();
if (is_hw(err))
++log_hardware_error_count;
if (stdlog) {
if (is_hw(err))
log_failed = fprintf(stdlog, "msg:fatal_hardware: ") < 0;
else
log_failed = fprintf(stdlog, "msg:fatal: ") < 0;
va_list ap_copy;
va_copy(ap_copy, ap);
if (vfprintf(stdlog, format, ap_copy) < 0)
log_failed = 1;
va_end(ap_copy);
if (post && fprintf(stdlog, "%s", post) < 0)
log_failed = 1;
if (fflush(stdlog) != 0)
log_failed = 1;
}
/* A failed tagged write must retain stderr as the only usable fatal diagnostic. */
if (!stdlog || log_failed || msg_fatal_stderr) {
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_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 (is_hw(err))
++log_hardware_error_count;
if (stdlog) {
if (is_hw(err))
fprintf(stdlog, "msg:error_hardware: ");
else
fprintf(stdlog, "msg:error: ");
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_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);
}
unlock_msg();
}
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 (is_hw(err))
++log_hardware_error_count;
if (stdlog) {
if (is_hw(err))
fprintf(stdlog, "msg:error_hardware: "); /* we never expect an hardware error */
else
fprintf(stdlog, "msg:info: ");
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_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);
}
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;
lock_msg();
if (is_hw(err))
++log_hardware_error_count;
if (stdlog) {
if (is_hw(err))
fprintf(stdlog, "msg:error_hardware: "); /* we never expect an hardware error */
else
fprintf(stdlog, "msg:expected: ");
va_start(ap, format);
vfprintf(stdlog, format, ap);
va_end(ap);
fflush(stdlog);
}
unlock_msg();
}
void log_tag(const char* format, ...)
{
va_list ap;
lock_msg();
if (stdlog) {
va_start(ap, format);
vfprintf(stdlog, format, ap);
va_end(ap);
/* here we intentionally don't flush to make the output faster */
}
unlock_msg();
}
void log_flush(void)
{
lock_msg();
if (stdlog)
fflush(stdlog);
fflush(stdout);
fflush(stderr);
unlock_msg();
}
void msg_status(const char* format, ...)
{
va_list ap;
lock_msg();
if (stdlog) {
fprintf(stdlog, "msg:status: ");
va_start(ap, format);
vfprintf(stdlog, format, ap);
va_end(ap);
fflush(stdlog);
}
if (msg_level >= MSG_STATUS) {
va_start(ap, format);
vmsg(stdout, format, ap);
va_end(ap);
}
unlock_msg();
}
void msg_info(const char* format, ...)
{
va_list ap;
lock_msg();
/*
* Don't output in stdlog as these messages
* are always paired with a msg_tag() call
*/
if (msg_level >= MSG_INFO) {
va_start(ap, format);
vmsg(stdout, format, ap);
va_end(ap);
fflush(stdout);
}
unlock_msg();
}
void msg_progress(const char* format, ...)
{
va_list ap;
lock_msg();
if (stdlog) {
fprintf(stdlog, "msg:progress: ");
va_start(ap, format);
vfprintf(stdlog, format, ap);
va_end(ap);
fflush(stdlog);
}
if (msg_level >= MSG_PROGRESS) {
va_start(ap, format);
vmsg(stdout, format, ap);
va_end(ap);
fflush(stdout);
}
unlock_msg();
}
void msg_bar(const char* format, ...)
{
va_list ap;
lock_msg();
/*
* Don't output in stdlog as these messages
* are intended for screen only
* also don't flush stdout as they are intended to be partial messages
*/
if (msg_level >= MSG_BAR) {
va_start(ap, format);
vmsg(stdout, format, ap);
va_end(ap);
}
unlock_msg();
}
void msg_verbose(const char* format, ...)
{
va_list ap;
lock_msg();
if (stdlog) {
fprintf(stdlog, "msg:verbose: ");
va_start(ap, format);
vfprintf(stdlog, format, ap);
va_end(ap);
fflush(stdlog);
}
if (msg_level >= MSG_VERBOSE) {
va_start(ap, format);
vmsg(stdout, format, ap);
va_end(ap);
fflush(stdout);
}
unlock_msg();
}
void msg_flush(void)
{
lock_msg();
fflush(stdout);
fflush(stderr);
unlock_msg();
}
void printc(char c, size_t pad)
{
while (pad) {
/* group writes in long pieces */
char buf[128];
size_t len = pad;
if (len >= sizeof(buf))
len = sizeof(buf) - 1;
memset(buf, c, len);
buf[len] = 0;
fputs(buf, stdout);
pad -= len;
}
}
void printr(const char* str, size_t pad)
{
size_t len;
len = strlen(str);
if (len < pad)
printc(' ', pad - len);
fputs(str, stdout);
}
void printl(const char* str, size_t pad)
{
size_t len;
fputs(str, stdout);
len = strlen(str);
if (len < pad)
printc(' ', pad - len);
}
void printp(double v, size_t pad)
{
char buf[64];
const char* s = "%";
if (v > 0.1)
snprintf(buf, sizeof(buf), "%5.2f%s", v, s);
else if (v > 0.01)
snprintf(buf, sizeof(buf), "%6.3f%s", v, s);
else if (v > 0.001)
snprintf(buf, sizeof(buf), "%7.4f%s", v, s);
else if (v > 0.0001)
snprintf(buf, sizeof(buf), "%8.5f%s", v, s);
else if (v > 0.00001)
snprintf(buf, sizeof(buf), "%9.6f%s", v, s);
else if (v > 0.000001)
snprintf(buf, sizeof(buf), "%10.7f%s", v, s);
else if (v > 0.0000001)
snprintf(buf, sizeof(buf), "%11.8f%s", v, s);
else if (v > 0.00000001)
snprintf(buf, sizeof(buf), "%12.9f%s", v, s);
else if (v > 0.000000001)
snprintf(buf, sizeof(buf), "%13.10f%s", v, s);
else if (v > 0.0000000001)
snprintf(buf, sizeof(buf), "%14.11f%s", v, s);
else if (v > 0.00000000001)
snprintf(buf, sizeof(buf), "%15.12f%s", v, s);
else if (v > 0.000000000001)
snprintf(buf, sizeof(buf), "%16.13f%s", v, s);
else
snprintf(buf, sizeof(buf), "%17.14f%s", v, s);
printl(buf, pad);
}
#define charcat(c) \
do { \
if (p == end) { \
goto bail; \
} \
*p++ = (c); \
} while (0)
const char* esc_tag(const char* str)
{
char* buffer = esc_buf();
char* begin = buffer;
char* end = begin + ESC_MAX;
char* p = begin;
/* copy string with escaping */
while (*str) {
char c = *str++;
switch (c) {
case '\n' :
charcat('\\');
charcat('n');
break;
case '\r' :
charcat('\\');
charcat('r');
break;
case ':' :
charcat('\\');
charcat('d');
break;
case '\\' :
charcat('\\');
charcat('\\');
break;
default :
charcat(c);
break;
}
}
/* put final 0 */
if (p == end)
goto bail;
*p = 0;
return begin;
bail:
/* LCOV_EXCL_START */
log_fatal(EINTERNAL, "Escape for log is too long\n");
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
#ifdef _WIN32
static int needs_quote(const char* arg)
{
while (*arg) {
char c = *arg;
if (c == ' ' || c == '\t' || c == '\n' || c == '\r' ||
c == '&' || c == '|' || c == '(' || c == ')' ||
c == '<' || c == '>' || c == '^' || c == '"' ||
c == '%' || c == '!' || c == '=' || c == ';' ||
(unsigned char)c < 32 || c == 127)
return 1;
++arg;
}
return 0;
}
#endif
struct stream {
const char* str;
unsigned idx;
const char** map;
unsigned max;
};
static inline char ssget(struct stream* ss)
{
/* get the next char */
char c = *ss->str++;
/* if one string is finished, go to the next */
while (c == 0 && ss->idx + 1 < ss->max) {
ss->str = ss->map[++ss->idx];
c = *ss->str++;
}
return c;
}
#ifdef _WIN32
const char* esc_shell_multi(const char** str_map, unsigned str_max, char* buffer)
{
char* begin = buffer;
char* end = begin + ESC_MAX;
char* p = begin;
struct stream ss;
char c;
unsigned i;
int has_quote;
has_quote = 0;
for (i = 0; i < str_max; ++i) {
if (needs_quote(str_map[i]))
has_quote = 1;
}
ss.idx = 0;
ss.str = str_map[0];
ss.map = str_map;
ss.max = str_max;
if (!has_quote) {
c = ssget(&ss);
while (c) {
charcat(c);
c = ssget(&ss);
}
} else {
/* starting quote */
charcat('"');
c = ssget(&ss);
while (c) {
int bl = 0;
while (c == '\\') {
++bl;
c = ssget(&ss);
}
if (c == 0) {
/* double backslashes before closing quote */
bl = bl * 2;
while (bl--)
charcat('\\');
break;
} else if (c == '"') {
/* double backslashes + escape the quote */
bl = bl * 2 + 1;
while (bl--)
charcat('\\');
charcat('"');
} else {
/* normal backslashes */
while (bl--)
charcat('\\');
charcat(c);
}
c = ssget(&ss);
}
/* ending quote */
charcat('"');
}
/* put final 0 */
charcat(0);
return begin;
bail:
/* LCOV_EXCL_START */
log_fatal(EINTERNAL, "Escape for shell is too long\n");
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
#else
const char* esc_shell_multi(const char** str_map, unsigned str_max, char* buffer)
{
char* begin = buffer;
char* end = begin + ESC_MAX;
char* p = begin;
struct stream ss;
char c;
ss.idx = 0;
ss.str = str_map[0];
ss.map = str_map;
ss.max = str_max;
c = ssget(&ss);
while (c) {
switch (c) {
/* special chars that need to be quoted */
case ' ' : /* space */
case '\t' : /* tab */
case '\n' : /* newline */
case '\r' : /* carriage return */
case '~' : /* home */
case '`' : /* command */
case '#' : /* comment */
case '$' : /* variable */
case '&' : /* background job */
case '*' : /* wildcard */
case '(' : /* shell */
case ')' : /* shell */
case '\\' : /* quote */
case '|' : /* pipe */
case '[' : /* wildcard */
case ']' : /* wildcard */
case '{' : /* code */
case '}' : /* code */
case ';' : /* separator */
case '\'' : /* quote */
case '"' : /* quote */
case '<' : /* redirect */
case '>' : /* redirect */
case '?' : /* wildcard */
case '=' : /* assignment */
case '!' : /* history expansion */
charcat('\\');
charcat(c);
break;
default :
/* check for control characters */
if ((unsigned char)c < 32 || c == 127) {
charcat('\\');
charcat(c);
} else {
charcat(c);
}
break;
}
c = ssget(&ss);
}
/* put final 0 */
charcat(0);
return begin;
bail:
/* LCOV_EXCL_START */
log_fatal(EINTERNAL, "Escape for shell is too long\n");
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
#endif
/****************************************************************************/
/* path */
void pathcpy(char* dst, size_t size, const char* src)
{
size_t len = strlen(src);
if (len + 1 > size) {
/* LCOV_EXCL_START */
log_fatal(EINTERNAL, "Path too long '%s'\n", src);
os_abort();
/* LCOV_EXCL_STOP */
}
memcpy(dst, src, len + 1);
}
void pathcat(char* dst, size_t size, const char* src)
{
size_t dst_len = strlen(dst);
size_t src_len = strlen(src);
if (dst_len + src_len + 1 > size) {
/* LCOV_EXCL_START */
log_fatal(EINTERNAL, "Path too long '%s%s'\n", dst, src);
os_abort();
/* LCOV_EXCL_STOP */
}
memcpy(dst + dst_len, src, src_len + 1);
}
void pathcatl(char* dst, size_t dst_len, size_t size, const char* src)
{
size_t src_len = strlen(src);
if (dst_len + src_len + 1 > size) {
/* LCOV_EXCL_START */
log_fatal(EINTERNAL, "Path too long '%s%s'\n", dst, src);
os_abort();
/* LCOV_EXCL_STOP */
}
memcpy(dst + dst_len, src, src_len + 1);
}
void pathcatc(char* dst, size_t size, char c)
{
size_t dst_len = strlen(dst);
if (dst_len + 2 > size) {
/* LCOV_EXCL_START */
log_fatal(EINTERNAL, "Path too long '%s%c'\n", dst, c);
os_abort();
/* LCOV_EXCL_STOP */
}
dst[dst_len] = c;
dst[dst_len + 1] = 0;
}
void pathimport(char* dst, size_t size, const char* src)
{
pathcpy(dst, size, src);
#ifdef _WIN32
/* Convert the Windows dir separator '\' to C '/' */
while (*dst) {
if (*dst == '\\')
*dst = '/';
++dst;
}
#endif
}
void pathexport(char* dst, size_t size, const char* src)
{
pathcpy(dst, size, src);
#ifdef _WIN32
/* Convert C '/' to Windows dir separator '\' */
while (*dst) {
if (*dst == '/')
*dst = '\\';
++dst;
}
#endif
}
void pathprint(char* dst, size_t size, const char* format, ...)
{
size_t len;
va_list ap;
va_start(ap, format);
len = vsnprintf(dst, size, format, ap);
va_end(ap);
if (len >= size) {
/* LCOV_EXCL_START */
if (size > 0) {
dst[size - 1] = 0;
log_fatal(EINTERNAL, "Path too long '%s...'\n", dst);
} else {
log_fatal(EINTERNAL, "Path too long for empty size'\n");
}
os_abort();
/* LCOV_EXCL_STOP */
}
}
void pathslash(char* dst, size_t size)
{
size_t len = strlen(dst);
if (len > 0 && dst[len - 1] != '/') {
if (len + 2 > size) {
/* LCOV_EXCL_START */
log_fatal(EINTERNAL, "Path too long '%s/'\n", dst);
os_abort();
/* LCOV_EXCL_STOP */
}
dst[len] = '/';
dst[len + 1] = 0;
}
}
void pathcut(char* dst)
{
char* slash = strrchr(dst, '/');
if (slash)
slash[1] = 0;
else
dst[0] = 0;
}
void pathup(char* dst)
{
size_t len = strlen(dst);
if (len == 0)
return;
/* if ending with slash, remove it */
if (dst[len - 1] == '/')
--len;
/* search previous / */
while (len > 0 && dst[len - 1] != '/')
--len;
/* cut everything after the slash (if there are no more slash, return the empty string) */
dst[len] = 0;
}
int pathcmp(const char* a, const char* b)
{
#ifdef _WIN32
while (1) {
char ca = *a++;
char cb = *b++;
if (ca == '/')
ca = '\\';
if (cb == '/')
cb = '\\';
int la = tolower((unsigned char)ca);
int lb = tolower((unsigned char)cb);
if (la != lb)
return la - lb;
if (la == 0)
return 0;
}
#else
return strcmp(a, b);
#endif
}
int pathncmp(const char* a, const char* b, size_t n)
{
#ifdef _WIN32
while (n > 0) {
char ca = *a++;
char cb = *b++;
if (ca == '/')
ca = '\\';
if (cb == '/')
cb = '\\';
int la = tolower((unsigned char)ca);
int lb = tolower((unsigned char)cb);
if (la != lb)
return la - lb;
if (la == 0)
return 0;
--n;
}
return 0;
#else
return strncmp(a, b, n);
#endif
}
int path_is_root_of(const char* root, const char* path)
{
while (*root && *path) {
#ifdef _WIN32
/* case insensitive compare in Windows */
char r = tolower((unsigned char)*root);
char p = tolower((unsigned char)*path);
if (r == '\\')
r = '/';
if (p == '\\')
p = '/';
if (r != p)
return 0;
#else
if (*root != *path)
return 0;
#endif
++root;
++path;
}
return *root == 0 && *path != 0;
}
int path_is_sub(const char* sub)
{
const char* p;
size_t len;
#ifdef _WIN32
/* cannot start with a Windows drive letter like C: */
if (isalpha((unsigned char)sub[0]) && sub[1] == ':')
return 0;
#endif
len = 0;
p = sub;
while (1) {
char c = *p;
if (c == '/' || c == 0) {
/* empty component (leading, consecutive, trailing slash, or empty string) */
if (len == 0)
return 0;
/* dot component */
if (len == 1 && *(p - 1) == '.')
return 0;
/* dot-dot component */
if (len == 2 && *(p - 2) == '.' && *(p - 1) == '.')
return 0;
if (c == 0)
break;
len = 0;
#ifdef _WIN32
} else if (c == '\\') {
/* cannot contain backslash in Windows */
return 0;
#endif
} else {
++len;
}
++p;
}
return 1;
}
/****************************************************************************/
/* file-system */
int mkancestor(const char* file)
{
char dir[PATH_MAX];
struct stat st;
char* c;
pathcpy(dir, sizeof(dir), file);
c = strrchr(dir, '/');
if (!c) {
/* no ancestor */
return 0;
}
/* clear the file */
*c = 0;
/* if it's the root dir */
if (*dir == 0) {
/* nothing more to do */
return 0;
}
#ifdef _WIN32
/* if it's a drive specification like "C:" */
if (isalpha((unsigned char)dir[0]) && dir[1] == ':' && dir[2] == 0) {
/* nothing more to do */
return 0;
}
#endif
/*
* Check if the dir already exists using lstat().
*
* Note that in Windows when dealing with read-only media
* you cannot try to create the directory, and expecting
* the EEXIST error because the call will fail with ERROR_WRITE_PROTECTED.
*
* Also in Windows it's better to use lstat() than stat() because it
* doesn't need to open the dir with CreateFile().
*/
if (lstat(dir, &st) == 0) {
/* it already exists */
return 0;
}
/* recursively create them all */
if (mkancestor(dir) != 0) {
/* LCOV_EXCL_START */
return -1;
/* LCOV_EXCL_STOP */
}
/* create it */
int ret = mkdir(dir, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
if (ret != 0 && errno != EEXIST) {
/* LCOV_EXCL_START */
log_fatal(errno, "Error creating directory '%s'. %s.\n", dir, strerror(errno));
return -1;
/* LCOV_EXCL_STOP */
}
return 0;
}
int fmtime(int f, int64_t mtime_sec, int mtime_nsec)
{
#if HAVE_FUTIMENS
struct timespec tv[2];
#else
struct timeval tv[2];
#endif
int ret;
#if HAVE_FUTIMENS /* futimens() is preferred because it gives nanosecond precision */
tv[0].tv_sec = mtime_sec;
if (mtime_nsec != STAT_NSEC_INVALID)
tv[0].tv_nsec = mtime_nsec;
else
tv[0].tv_nsec = 0;
tv[1].tv_sec = tv[0].tv_sec;
tv[1].tv_nsec = tv[0].tv_nsec;
ret = futimens(f, tv);
#elif HAVE_FUTIMES /* fallback to futimes() if nanosecond precision is not available */
tv[0].tv_sec = mtime_sec;
if (mtime_nsec != STAT_NSEC_INVALID)
tv[0].tv_usec = mtime_nsec / 1000;
else
tv[0].tv_usec = 0;
tv[1].tv_sec = tv[0].tv_sec;
tv[1].tv_usec = tv[0].tv_usec;
ret = futimes(f, tv);
#elif HAVE_FUTIMESAT /* fallback to futimesat() for Solaris, it only has futimesat() */
tv[0].tv_sec = mtime_sec;
if (mtime_nsec != STAT_NSEC_INVALID)
tv[0].tv_usec = mtime_nsec / 1000;
else
tv[0].tv_usec = 0;
tv[1].tv_sec = tv[0].tv_sec;
tv[1].tv_usec = tv[0].tv_usec;
ret = futimesat(f, 0, tv);
#else
#error No function available to set file timestamps with sub-second precision
#endif
return ret;
}
int lmtime(const char* path, int64_t mtime_sec, int mtime_nsec)
{
#if HAVE_UTIMENSAT
struct timespec tv[2];
#else
struct timeval tv[2];
#endif
int ret;
#if HAVE_UTIMENSAT /* utimensat() is preferred because it gives nanosecond precision */
tv[0].tv_sec = mtime_sec;
if (mtime_nsec != STAT_NSEC_INVALID)
tv[0].tv_nsec = mtime_nsec;
else
tv[0].tv_nsec = 0;
tv[1].tv_sec = tv[0].tv_sec;
tv[1].tv_nsec = tv[0].tv_nsec;
ret = utimensat(AT_FDCWD, path, tv, AT_SYMLINK_NOFOLLOW);
#elif HAVE_LUTIMES /* fallback to lutimes() if nanosecond precision is not available */
tv[0].tv_sec = mtime_sec;
if (mtime_nsec != STAT_NSEC_INVALID)
tv[0].tv_usec = mtime_nsec / 1000;
else
tv[0].tv_usec = 0;
tv[1].tv_sec = tv[0].tv_sec;
tv[1].tv_usec = tv[0].tv_usec;
ret = lutimes(path, tv);
#elif HAVE_FUTIMESAT /* fallback to futimesat() for Solaris, it only has futimesat() */
tv[0].tv_sec = mtime_sec;
if (mtime_nsec != STAT_NSEC_INVALID)
tv[0].tv_usec = mtime_nsec / 1000;
else
tv[0].tv_usec = 0;
tv[1].tv_sec = tv[0].tv_sec;
tv[1].tv_usec = tv[0].tv_usec;
ret = futimesat(AT_FDCWD, path, tv);
#else
#error No function available to set file timestamps with sub-second precision
#endif
return ret;
}
/****************************************************************************/
/* advise */
void advise_init(struct advise_struct* advise, int mode)
{
advise->mode = mode;
advise->dirty_begin = 0;
advise->dirty_end = 0;
}
int advise_flags(struct advise_struct* advise)
{
int flags = 0;
if (advise->mode == ADVISE_SEQUENTIAL
|| advise->mode == ADVISE_FLUSH
|| advise->mode == ADVISE_FLUSH_WINDOW
|| advise->mode == ADVISE_DISCARD
|| advise->mode == ADVISE_DISCARD_WINDOW
)
flags |= O_SEQUENTIAL;
#if HAVE_DIRECT_IO
if (advise->mode == ADVISE_DIRECT)
flags |= O_DIRECT;
#endif
return flags;
}
int advise_open(struct advise_struct* advise, int f)
{
(void)advise;
(void)f;
#if HAVE_POSIX_FADVISE
if (advise->mode == ADVISE_SEQUENTIAL
|| advise->mode == ADVISE_FLUSH
|| advise->mode == ADVISE_FLUSH_WINDOW
|| advise->mode == ADVISE_DISCARD
|| advise->mode == ADVISE_DISCARD_WINDOW
) {
int ret;
/*
* Advise noreuse access, this avoids to pollute the page cache
* supported from Linux Kernel 6.3 with this commit: https://github.com/torvalds/linux/commit/17e810229cb3068b692fa078bd9b3a6527e0866a
*/
ret = posix_fadvise_wrapper(f, 0, 0, POSIX_FADV_NOREUSE);
if (ret == ENOSYS) {
/* call is not supported */
ret = 0;
}
if (ret != 0) {
/* LCOV_EXCL_START */
errno = ret; /* posix_fadvise return the error code */
return -1;
/* LCOV_EXCL_STOP */
}
/* advise sequential access, this doubles the read-ahead window size */
ret = posix_fadvise_wrapper(f, 0, 0, POSIX_FADV_SEQUENTIAL);
if (ret == ENOSYS) {
/* call is not supported, like in armhf, see posix_fadvise manpage */
ret = 0;
}
if (ret != 0) {
/* LCOV_EXCL_START */
errno = ret; /* posix_fadvise return the error code */
return -1;
/* LCOV_EXCL_STOP */
}
}
#endif
return 0;
}
int advise_write(struct advise_struct* advise, int f, data_off_t offset, data_off_t size)
{
data_off_t flush_offset;
data_off_t flush_size;
data_off_t discard_offset;
data_off_t discard_size;
(void)f;
(void)flush_offset;
(void)flush_size;
(void)discard_offset;
(void)discard_size;
flush_offset = 0;
flush_size = 0;
discard_offset = 0;
discard_size = 0;
/*
* Follow Linus recommendations about fast writes.
*
* Linus "Unexpected splice "always copy" behavior observed"
* http://thread.gmane.org/gmane.linux.kernel/987247/focus=988070
* ---
* I have had _very_ good experiences with even a rather trivial
* file writer that basically used (iirc) 8MB windows, and the logic was very
* trivial:
*
* - before writing a new 8M window, do "start writeback"
* (SYNC_FILE_RANGE_WRITE) on the previous window, and do
* a wait (SYNC_FILE_RANGE_WAIT_AFTER) on the window before that.
*
* in fact, in its simplest form, you can do it like this (this is from my
* "overwrite disk images" program that I use on old disks):
*
* for (index = 0; index < max_index ;index++) {
* if (write(fd, buffer, BUFSIZE) != BUFSIZE)
* break;
* // This won't block, but will start writeout asynchronously
* sync_file_range(fd, index*BUFSIZE, BUFSIZE, SYNC_FILE_RANGE_WRITE);
* // This does a blocking write-and-wait on any old ranges
* if (index)
* sync_file_range(fd, (index-1)*BUFSIZE, BUFSIZE, SYNC_FILE_RANGE_WAIT_BEFORE|SYNC_FILE_RANGE_WRITE|SYNC_FILE_RANGE_WAIT_AFTER);
* }
*
* and even if you don't actually do a discard (maybe we should add a
* SYNC_FILE_RANGE_DISCARD bit, right now you'd need to do a separate
* fadvise(FADV_DONTNEED) to throw it out) the system behavior is pretty
* nice, because the heavy writer gets good IO performance _and_ leaves only
* easy-to-free pages around after itself.
* ---
*
* Linus "Unexpected splice "always copy" behavior observed"
* http://thread.gmane.org/gmane.linux.kernel/987247/focus=988176
* ---
* The behavior for dirty page writeback is _not_ well defined, and
* if you do POSIX_FADV_DONTNEED, I would suggest you do it as part of that
* writeback logic, ie you do it only on ranges that you have just waited on.
*
* IOW, in my example, you'd couple the
*
* sync_file_range(fd, (index-1)*BUFSIZE, BUFSIZE, SYNC_FILE_RANGE_WAIT_BEFORE|SYNC_FILE_RANGE_WRITE|SYNC_FILE_RANGE_WAIT_AFTER);
*
* with a
*
* posix_fadvise(fd, (index-1)*BUFSIZE, BUFSIZE, POSIX_FADV_DONTNEED);
*
* afterwards to throw out the pages that you just waited for.
* ---
*/
switch (advise->mode) {
case ADVISE_FLUSH :
flush_offset = offset;
flush_size = size;
break;
case ADVISE_DISCARD :
discard_offset = offset;
discard_size = size;
break;
case ADVISE_FLUSH_WINDOW :
/* if the dirty range can be extended */
if (advise->dirty_end == offset) {
/* extent the dirty range */
advise->dirty_end += size;
/* if we reached the window size */
if (advise->dirty_end - advise->dirty_begin >= ADVISE_WINDOW_SIZE) {
/* flush the window */
flush_offset = advise->dirty_begin;
flush_size = ADVISE_WINDOW_SIZE;
/* remove it from the dirty range */
advise->dirty_begin += ADVISE_WINDOW_SIZE;
}
} else {
/* otherwise flush the existing dirty */
flush_offset = advise->dirty_begin;
flush_size = advise->dirty_end - advise->dirty_begin;
/* and set the new range as dirty */
advise->dirty_begin = offset;
advise->dirty_end = offset + size;
}
break;
case ADVISE_DISCARD_WINDOW :
/* if the dirty range can be extended */
if (advise->dirty_end == offset) {
/* extent the dirty range */
advise->dirty_end += size;
/* if we reached the double window size */
if (advise->dirty_end - advise->dirty_begin >= 2 * ADVISE_WINDOW_SIZE) {
/* discard the first window */
discard_offset = advise->dirty_begin;
discard_size = ADVISE_WINDOW_SIZE;
/* remove it from the dirty range */
advise->dirty_begin += ADVISE_WINDOW_SIZE;
/* flush the second window */
flush_offset = advise->dirty_begin;
flush_size = ADVISE_WINDOW_SIZE;
}
} else {
/* otherwise discard the existing dirty */
discard_offset = advise->dirty_begin;
discard_size = advise->dirty_end - advise->dirty_begin;
/* and set the new range as dirty */
advise->dirty_begin = offset;
advise->dirty_end = offset + size;
}
break;
}
#if HAVE_SYNC_FILE_RANGE
if (flush_size != 0) {
int ret;
/* start writing immediately */
ret = sync_file_range(f, flush_offset, flush_size, SYNC_FILE_RANGE_WRITE);
if (ret != 0) {
/* LCOV_EXCL_START */
return -1;
/* LCOV_EXCL_STOP */
}
}
#endif
#if HAVE_SYNC_FILE_RANGE && HAVE_POSIX_FADVISE
if (discard_size != 0) {
int ret;
/* send the data to the disk and wait until it's written */
ret = sync_file_range(f, discard_offset, discard_size, SYNC_FILE_RANGE_WAIT_BEFORE | SYNC_FILE_RANGE_WRITE | SYNC_FILE_RANGE_WAIT_AFTER);
if (ret != 0) {
/* LCOV_EXCL_START */
return -1;
/* LCOV_EXCL_STOP */
}
/* flush the data from the cache */
ret = posix_fadvise_wrapper(f, discard_offset, discard_size, POSIX_FADV_DONTNEED);
if (ret == ENOSYS) {
/* call is not supported */
ret = 0;
}
if (ret != 0) {
/* LCOV_EXCL_START */
errno = ret; /* posix_fadvise return the error code */
return -1;
/* LCOV_EXCL_STOP */
}
}
#endif
return 0;
}
int advise_read(struct advise_struct* advise, int f, data_off_t offset, data_off_t size)
{
(void)advise;
(void)f;
(void)offset;
(void)size;
#if HAVE_POSIX_FADVISE
if (advise->mode == ADVISE_DISCARD
|| advise->mode == ADVISE_DISCARD_WINDOW
) {
int ret;
/* flush the data from the cache */
ret = posix_fadvise_wrapper(f, offset, size, POSIX_FADV_DONTNEED);
if (ret == ENOSYS) {
/* call is not supported */
ret = 0;
}
if (ret != 0) {
/* LCOV_EXCL_START */
errno = ret; /* posix_fadvise return the error code */
return -1;
/* LCOV_EXCL_STOP */
}
}
#endif
/*
* Here we cannot call posix_fadvise(..., POSIX_FADV_WILLNEED) for the next block
* because it may be blocking.
*
* Ted Ts'o "posix_fadvise(POSIX_FADV_WILLNEED) waits before returning?"
* https://lkml.org/lkml/2010/12/6/122
* ---
* readahead and posix_fadvise(POSIX_FADV_WILLNEED) work exactly the same
* way, and in fact share mostly the same code path (see
* force_page_cache_readahead() in mm/readahead.c).
*
* They are asynchronous in that there is no guarantee the pages will be
* in the page cache by the time they return. But at the same time, they
* are not guaranteed to be non-blocking. That is, the work of doing the
* readahead does not take place in a kernel thread. So if you try to
* request I/O than will fit in the request queue, the system call will
* block until some I/O is completed so that more I/O requested cam be
* loaded onto the request queue.
*
* The only way to fix this would be to either put the work on a kernel
* thread (i.e., some kind of workqueue) or in a userspace thread. For
* ion programmer wondering what to do today, I'd suggest the
* latter since it will be more portable across various kernel versions.
*
* This does leave the question about whether we should change the kernel
* to allow readahead() and posix_fadvise(POSIX_FADV_WILLNEED) to be
* non-blocking and do this work in a workqueue (or via some kind of
* callback/continuation scheme). My worry is just doing this if a user
* application does something crazy, like request gigabytes and gigabytes
* of readahead, and then repented of their craziness, there should be a
* way of cancelling the readahead request. Today, the user can just
* kill the application. But if we simply shove the work to a kernel
* thread, it becomes a lot harder to cancel the readahead request. We'd
* have to invent a new API, and then have a way to know whether the user
* has access to kill a particular readahead request, etc.
* ---
*/
return 0;
}
int advise_close(struct advise_struct* advise, int f)
{
data_off_t flush_offset;
data_off_t flush_size;
data_off_t discard_offset;
data_off_t discard_size;
(void)advise;
(void)f;
(void)flush_offset;
(void)flush_size;
(void)discard_offset;
(void)discard_size;
flush_offset = 0;
flush_size = 0;
discard_offset = 0;
discard_size = 0;
/* assume it's a write if advise->dirty_begin != advise->dirty_end */
switch (advise->mode) {
case ADVISE_FLUSH_WINDOW :
flush_offset = advise->dirty_begin;
flush_size = advise->dirty_end - advise->dirty_begin;
break;
case ADVISE_DISCARD_WINDOW :
discard_offset = advise->dirty_begin;
discard_size = advise->dirty_end - advise->dirty_begin;
break;
}
#if HAVE_SYNC_FILE_RANGE
if (flush_size != 0) {
int ret;
/* start writing immediately */
ret = sync_file_range(f, flush_offset, flush_size, SYNC_FILE_RANGE_WRITE);
if (ret != 0) {
/* LCOV_EXCL_START */
return -1;
/* LCOV_EXCL_STOP */
}
}
#endif
#if HAVE_SYNC_FILE_RANGE && HAVE_POSIX_FADVISE
if (discard_size != 0) {
int ret;
/* send the data to the disk and wait until it's written */
ret = sync_file_range(f, discard_offset, discard_size, SYNC_FILE_RANGE_WAIT_BEFORE | SYNC_FILE_RANGE_WRITE | SYNC_FILE_RANGE_WAIT_AFTER);
if (ret != 0) {
/* LCOV_EXCL_START */
return -1;
/* LCOV_EXCL_STOP */
}
/* flush the data from the cache */
ret = posix_fadvise_wrapper(f, discard_offset, discard_size, POSIX_FADV_DONTNEED);
if (ret == ENOSYS) {
/* call is not supported */
ret = 0;
}
if (ret != 0) {
/* LCOV_EXCL_START */
errno = ret; /* posix_fadvise return the error code */
return -1;
/* LCOV_EXCL_STOP */
}
}
#endif
advise->dirty_begin = 0;
advise->dirty_end = 0;
return 0;
}
/****************************************************************************/
/* smartctl */
/**
* Match a string with the specified pattern.
* Like sscanf() a space match any sequence of spaces.
* Return 0 if it matches.
*/
static int smatch(const char* str, const char* pattern)
{
while (*pattern) {
if (isspace((unsigned char)*pattern)) {
++pattern;
while (isspace((unsigned char)*str))
++str;
} else if (*pattern == *str) {
++pattern;
++str;
} else
return -1;
}
return 0;
}
int is_sep(char c)
{
return c == '.'
|| c == ','
|| c == '\''
|| c == ' ' /* 0xa0 converted by space for no code page */
|| c == (char)0xa0; /* french */
}
/*
* snumber()
*
* Matches a literal prefix (the second argument), then parses the following
* unsigned 64-bit integer, allowing thousand separators.
*
* Whitespace is flexible around the prefix and before the number.
* Commas are ignored during number parsing but basic validation is applied.
*
* Returns:
* 1 - successfully parsed a valid uint64_t
* 0 - prefix mismatch, no digits found, invalid format or overflow
*/
int snumber(const char* str, const char* prefix, uint64_t* value)
{
const char* s = str;
const char* f = prefix;
/* match literal prefix, tolerating whitespace differences */
while (*f) {
/* skip whitespace in both strings */
while (isspace((unsigned char)*f))
++f;
while (isspace((unsigned char)*s))
++s;
if (*f == 0)
break;
if (*s != *f)
return 0; /* prefix mismatch */
++s;
++f;
}
/* skip whitespace before the number starts */
while (isspace((unsigned char)*s))
++s;
uint64_t num = 0;
int digits_seen = 0;
int sep_seen = 0;
/* parse digits, ignoring commas */
while (*s) {
if (*s >= '0' && *s <= '9') {
unsigned digit = (unsigned)(*s - '0');
/* check for uint64_t overflow before multiplying */
if (num > (UINT64_MAX - digit) / 10ULL)
return 0;
num = num * 10ULL + digit;
++digits_seen;
sep_seen = 0;
} else if (is_sep(*s)) {
/* disallow leading separators */
if (digits_seen == 0)
return 0;
/* if two consecutive separators, stop parsing */
if (sep_seen)
break;
sep_seen = 1;
} else {
/* any other character ends the number */
break;
}
++s;
}
/* require at least one digit */
if (digits_seen == 0)
return 0;
*value = num;
return 1;
}
int smartctl_attribute(OS_FILE* f, const char* file, const char* name, struct smart_attr* smart, uint64_t* info, char* serial, char* family, char* model, char* inter)
{
unsigned i;
int inside;
struct smart_attr dummy_smart[SMART_COUNT];
uint64_t dummy_info[INFO_COUNT];
char dummy_serial[SMART_MAX];
char dummy_family[SMART_MAX];
char dummy_model[SMART_MAX];
char dummy_interface[SMART_MAX];
/* dummy attributes */
if (!smart)
smart = dummy_smart;
if (!info)
info = dummy_info;
if (!serial)
serial = dummy_serial;
if (!family)
family = dummy_family;
if (!model)
model = dummy_model;
if (!inter)
inter = dummy_interface;
/* preclear attributes */
*serial = 0;
*family = 0;
*model = 0;
*inter = 0;
memset(smart, 0, sizeof(struct smart_attr) * SMART_COUNT);
for (i = 0; i < SMART_COUNT; ++i) {
smart[i].raw = SMART_UNASSIGNED;
smart[i].norm = SMART_UNASSIGNED;
smart[i].worst = SMART_UNASSIGNED;
smart[i].thresh = SMART_UNASSIGNED;
}
for (i = 0; i < INFO_COUNT; ++i)
info[i] = SMART_UNASSIGNED;
pathcpy(smart[SMART_ERROR_PROTOCOL].name, sizeof(smart[SMART_ERROR_PROTOCOL].name), "error_protocol");
pathcpy(smart[SMART_ERROR_MEDIUM].name, sizeof(smart[SMART_ERROR_MEDIUM].name), "error_medium");
pathcpy(smart[SMART_WEAR_LEVEL].name, sizeof(smart[SMART_WEAR_LEVEL].name), "wear_level");
/* read the file */
inside = 0;
while (1) {
char buf[256];
unsigned id;
uint64_t raw;
char* s;
s = os_fgets(buf, sizeof(buf), f);
if (s == 0)
break;
/* remove extraneous chars */
s = strpolish(buf);
log_tag("smartctl:%s:%s:out: %s\n", file, name, s);
/* skip initial spaces */
while (isspace((unsigned char)*s))
++s;
if (*s == 0) {
inside = 0;
/* common */
} else if (smatch(s, "Rotation Rate: Solid State") == 0) {
info[INFO_ROTATION_RATE] = 0;
} else if (sscanf(s, "Rotation Rate: %" SCNu64, &info[INFO_ROTATION_RATE]) == 1) {
} else if (snumber(s, "User Capacity:", &raw) == 1) {
info[INFO_SIZE] = raw;
} else if (sscanf(s, "Model Family: %63[^\n]", family) == 1) {
strtrim(family);
} else if (sscanf(s, "Device Model: %63[^\n]", model) == 1) {
strtrim(model);
} else if (sscanf(s, "Serial number: %63s", serial) == 1) { /* SCSI */
strtrim(serial);
} else if (sscanf(s, "Serial Number: %63s", serial) == 1) { /*ATA / NVMe */
strtrim(serial);
/* SCSI */
/*
Vendor: WD
Product: WD4001FYYG-01SL3
Revision: VR08
Compliance: SPC-4
User Capacity: 4,000,787,030,016 bytes [4.00 TB]
Logical block size: 512 bytes
Rotation Rate: 7200 rpm
Form Factor: 3.5 inches
Logical Unit id: 0x50000c0f01f55dd0
Serial number: WMC1F0D41KD5
Device type: disk
Transport protocol: SAS (SPL-3)
Local Time is: Fri Jan 28 14:14:51 2022 CET
SMART support is: Available - device has SMART capability.
SMART support is: Enabled
Temperature Warning: Enabled
Current Drive Temperature: 38 C
Drive Trip Temperature: 65 C
Manufactured in week 25 of year 2012
Specified cycle count over device lifetime: 10000
Accumulated start-stop cycles: 103
Specified load-unload count over device lifetime: 300000
Accumulated load-unload cycles: 103
Elements in grown defect list: 5
Vendor (Seagate) cache information
Blocks sent to initiator = 569127595
Blocks received from initiator = 2633690060
Blocks read from cache and sent to initiator = 1881027254
Number of read and write commands whose size <= segment size = 181284019
Number of read and write commands whose size > segment size = 0
Vendor (Seagate/Hitachi) factory information
number of hours powered up = 25024.80
number of minutes until next internal SMART test = 42
*/
} else if (sscanf(s, "Transport protocol: %63[^\n]", inter) == 1) {
if (strcmp(inter, "Fibre channel (FCP-4)") == 0)
strcpy(inter, "Fibre");
else if (strcmp(inter, "SSA") == 0)
strcpy(inter, "SSA");
else if (strcmp(inter, "IEEE 1394 (SBP-3)") == 0)
strcpy(inter, "FireWire");
else if (strcmp(inter, "RDMA (SRP)") == 0)
strcpy(inter, "SCSI");
else if (strcmp(inter, "iSCSI") == 0)
strcpy(inter, "iSCSI");
else if (strcmp(inter, "SAS (SPL-4)") == 0)
strcpy(inter, "SAS");
else if (strcmp(inter, "ADT") == 0)
strcpy(inter, "SCSI");
else if (strcmp(inter, "ATA (ACS-2)") == 0)
strcpy(inter, "SATA");
else if (strcmp(inter, "UAS") == 0)
strcpy(inter, "USB");
else if (strcmp(inter, "SOP") == 0)
strcpy(inter, "NVMe");
else if (strcmp(inter, "PCIe") == 0)
strcpy(inter, "PCIe");
else
strcpy(inter, "SCSI");
/* map generic attributes to SMART attr */
} else if (sscanf(s, "Elements in grown defect list: %" SCNu64, &smart[SMART_REALLOCATED_SECTOR_COUNT].raw) == 1) {
pathcpy(smart[SMART_REALLOCATED_SECTOR_COUNT].name, sizeof(smart[SMART_REALLOCATED_SECTOR_COUNT].name), "Elements_In_Grown_Defect_List");
} else if (sscanf(s, "Current Drive Temperature: %" SCNu64, &smart[SMART_TEMPERATURE_CELSIUS].raw) == 1) {
pathcpy(smart[SMART_TEMPERATURE_CELSIUS].name, sizeof(smart[SMART_TEMPERATURE_CELSIUS].name), "Current_Drive_Temperature");
} else if (sscanf(s, "Drive Trip Temperature: %" SCNu64, &smart[SMART_AIRFLOW_TEMPERATURE_CELSIUS].raw) == 1) {
pathcpy(smart[SMART_AIRFLOW_TEMPERATURE_CELSIUS].name, sizeof(smart[SMART_AIRFLOW_TEMPERATURE_CELSIUS].name), "Drive_Trip_Temperature");
} else if (sscanf(s, "Accumulated start-stop cycles: %" SCNu64, &smart[SMART_START_STOP_COUNT].raw) == 1) {
pathcpy(smart[SMART_START_STOP_COUNT].name, sizeof(smart[SMART_START_STOP_COUNT].name), "Accumulated_Start-Stop_Cycles");
} else if (sscanf(s, "Accumulated load-unload cycles: %" SCNu64, &smart[SMART_LOAD_CYCLE_COUNT].raw) == 1) {
pathcpy(smart[SMART_LOAD_CYCLE_COUNT].name, sizeof(smart[SMART_LOAD_CYCLE_COUNT].name), "Accumulated_Load-Unload_Cycles");
} else if (sscanf(s, " number of hours powered up = %" SCNu64, &smart[SMART_POWER_ON_HOURS].raw) == 1) { /* note "n" of "number" lower case */
pathcpy(smart[SMART_POWER_ON_HOURS].name, sizeof(smart[SMART_POWER_ON_HOURS].name), "Number_Of_Hours_Powered_Up");
/* special entries */
} else if (sscanf(s, "Non-medium error count: %" SCNu64, &raw) == 1) {
smart[SMART_ERROR_PROTOCOL].raw = raw;
/* ATA */
/*
ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_FAILED RAW_VALUE
1 Raw_Read_Error_Rate 0x002f 200 200 051 Pre-fail Always - 0
3 Spin_Up_Time 0x0027 172 169 021 Pre-fail Always - 8383
4 Start_Stop_Count 0x0032 100 100 000 Old_age Always - 24
5 Reallocated_Sector_Ct 0x0033 200 200 140 Pre-fail Always - 0
7 Seek_Error_Rate 0x002e 200 200 000 Old_age Always - 0
9 Power_On_Hours 0x0032 083 083 000 Old_age Always - 12471
10 Spin_Retry_Count 0x0032 100 253 000 Old_age Always - 0
11 Calibration_Retry_Count 0x0032 100 253 000 Old_age Always - 0
12 Power_Cycle_Count 0x0032 100 100 000 Old_age Always - 24
192 Power-Off_Retract_Count 0x0032 200 200 000 Old_age Always - 9
193 Load_Cycle_Count 0x0032 157 157 000 Old_age Always - 131565
194 Temperature_Celsius 0x0022 127 119 000 Old_age Always - 25
196 Reallocated_Event_Count 0x0032 200 200 000 Old_age Always - 0
197 Current_Pending_Sector 0x0032 200 200 000 Old_age Always - 0
198 Offline_Uncorrectable 0x0030 100 253 000 Old_age Offline - 0
199 UDMA_CRC_Error_Count 0x0032 200 200 000 Old_age Always - 0
200 Multi_Zone_Error_Rate 0x0008 200 200 000 Old_age Offline - 0
*/
} else if (smatch(s, "ATA Version is:") == 0) {
strcpy(inter, "ATA");
} else if (smatch(s, "SATA Version is:") == 0) {
strcpy(inter, "SATA");
} else if (smatch(s, "Transport Type: PCIe") == 0) {
strcpy(inter, "PCIe");
} else if (smatch(s, "Transport Type: Parallel") == 0) {
strcpy(inter, "PATA");
/* special entries */
} else if (smatch(s, "No Errors Logged") == 0) {
smart[SMART_ERROR_PROTOCOL].raw = 0;
} else if (sscanf(s, "ATA Error Count: %" SCNu64, &raw) == 1) {
smart[SMART_ERROR_PROTOCOL].raw = raw;
/* NVME */
/*
Critical Warning: 0x00
Temperature: 55 Celsius
Available Spare: 100%
Available Spare Threshold: 10%
Percentage Used: 8%
Data Units Read: 213,006,510 [109 TB]
Data Units Written: 549,370,112 [281 TB]
Host Read Commands: 11,210,192,197
Host Write Commands: 20,687,602,229
Controller Busy Time: 14,055
Power Cycles: 39
Power On Hours: 4,204
Unsafe Shutdowns: 9
Media and Data Integrity Errors: 0
Error Information Log Entries: 1,479,242
Warning Comp. Temperature Time: 0
Critical Comp. Temperature Time: 0
Temperature Sensor 2: 75 Celsius
Thermal Temp. 1 Total Time: 58745
*/
} else if (smatch(s, "NVMe Version:") == 0) {
strcpy(inter, "NVMe");
info[INFO_ROTATION_RATE] = 0;
/* special entries */
} else if (snumber(s, "Media and Data Integrity Errors:", &raw) == 1) {
smart[SMART_ERROR_MEDIUM].raw = raw;
} else if (snumber(s, "Error Information Log Entries:", &raw) == 1) {
smart[SMART_ERROR_PROTOCOL].raw = raw;
} else if (sscanf(s, "Percentage Used: %" SCNu64 "%%", &raw) == 1) {
smart[SMART_WEAR_LEVEL].raw = raw; /* take care that it can be greather than 100%, meaning that it's used over the expected lifetime */
/* map generic attributes to SMART attr */
} else if (sscanf(s, "Temperature: %" SCNu64, &smart[SMART_TEMPERATURE_CELSIUS].raw) == 1) {
pathcpy(smart[SMART_TEMPERATURE_CELSIUS].name, sizeof(smart[SMART_TEMPERATURE_CELSIUS].name), "Temperature");
} else if (snumber(s, "Power On Hours:", &smart[SMART_POWER_ON_HOURS].raw) == 1) {
pathcpy(smart[SMART_POWER_ON_HOURS].name, sizeof(smart[SMART_POWER_ON_HOURS].name), "Power_On_Hours");
} else if (snumber(s, "Power Cycles:", &smart[SMART_POWER_CYCLE_COUNT].raw) == 1) {
pathcpy(smart[SMART_POWER_CYCLE_COUNT].name, sizeof(smart[SMART_POWER_CYCLE_COUNT].name), "Power_Cycles");
/* map specific attributes to UNUSED SMART attr */
} else if (sscanf(s, "Critical Warning: %" SCNx64, &smart[SMART_NVME_CRITICAL_WARNING].raw) == 1) {
pathcpy(smart[SMART_NVME_CRITICAL_WARNING].name, sizeof(smart[SMART_NVME_CRITICAL_WARNING].name), "Critical_Warning");
} else if (sscanf(s, "Available Spare: %" SCNu64 "%%", &smart[SMART_NVME_AVAILABLE_SPARE].raw) == 1) {
pathcpy(smart[SMART_NVME_AVAILABLE_SPARE].name, sizeof(smart[SMART_NVME_AVAILABLE_SPARE].name), "Available_Spare");
} else if (snumber(s, "Data Units Read:", &smart[SMART_NVME_DATA_UNITS_READ].raw) == 1) {
pathcpy(smart[SMART_NVME_DATA_UNITS_READ].name, sizeof(smart[SMART_NVME_DATA_UNITS_READ].name), "Data_Units_Read");
} else if (snumber(s, "Data Units Written:", &smart[SMART_NVME_DATA_UNITS_WRITTEN].raw) == 1) {
pathcpy(smart[SMART_NVME_DATA_UNITS_WRITTEN].name, sizeof(smart[SMART_NVME_DATA_UNITS_WRITTEN].name), "Data_Units_Written");
} else if (snumber(s, "Host Read Commands:", &smart[SMART_NVME_HOST_READ_COMMANDS].raw) == 1) {
pathcpy(smart[SMART_NVME_HOST_READ_COMMANDS].name, sizeof(smart[SMART_NVME_HOST_READ_COMMANDS].name), "Host_Read_Commands");
} else if (snumber(s, "Host Write Commands:", &smart[SMART_NVME_HOST_WRITE_COMMANDS].raw) == 1) {
pathcpy(smart[SMART_NVME_HOST_WRITE_COMMANDS].name, sizeof(smart[SMART_NVME_HOST_WRITE_COMMANDS].name), "Host_Write_Commands");
} else if (snumber(s, "Controller Busy Time:", &smart[SMART_NVME_CONTROLLER_BUSY_TIME].raw) == 1) {
pathcpy(smart[SMART_NVME_CONTROLLER_BUSY_TIME].name, sizeof(smart[SMART_NVME_CONTROLLER_BUSY_TIME].name), "Controller_Busy_Time");
} else if (snumber(s, "Unsafe Shutdowns:", &smart[SMART_NVME_UNSAFE_SHUTDOWNS].raw) == 1) {
pathcpy(smart[SMART_NVME_UNSAFE_SHUTDOWNS].name, sizeof(smart[SMART_NVME_UNSAFE_SHUTDOWNS].name), "Unsafe_Shutdowns");
/* smartctl doesn't print this with command, but %d */
} else if (snumber(s, "Warning Comp. Temperature Time:", &smart[SMART_NVME_WARNING_COMP_TEMPERATURE_TIME].raw) == 1) {
pathcpy(smart[SMART_NVME_WARNING_COMP_TEMPERATURE_TIME].name, sizeof(smart[SMART_NVME_WARNING_COMP_TEMPERATURE_TIME].name), "Warning_Comp_Temperature_Time");
/* smartctl doesn't print this with command, but %d */
} else if (snumber(s, "Critical Comp. Temperature Time:", &smart[SMART_NVME_CRITICAL_COMP_TEMPERATURE_TIME].raw) == 1) {
pathcpy(smart[SMART_NVME_CRITICAL_COMP_TEMPERATURE_TIME].name, sizeof(smart[SMART_NVME_CRITICAL_COMP_TEMPERATURE_TIME].name), "Critical_Comp_Temperature_Time");
/* ATA Attributes table */
} else if (smatch(s, "ID#") == 0) {
inside = 1;
} else if (inside) {
char id_name[SMART_MAX] = { 0 };
char type[SMART_MAX] = { 0 };
char updated[SMART_MAX] = { 0 };
char when_failed[SMART_MAX] = { 0 };
char norm[SMART_MAX] = { 0 };
char worst[SMART_MAX] = { 0 };
char thresh[SMART_MAX] = { 0 };
uint64_t min, max, avg;
int format_minmax = 0;
int format_avg = 0;
int flags;
unsigned long v;
char* e;
/* 194 Temperature_Celsius 0x0002 240 240 000 Old_age Always - 27 (Min/Max 16/60) */
if (sscanf(s, "%u %127s %*s %63s %63s %63s %63s %63s %63s %" SCNu64 " (Min/Max %" SCNu64 "/%" SCNu64 ")", &id, id_name, norm, worst, thresh, type, updated, when_failed, &raw, &min, &max) == 11) {
format_minmax = 1;
/* 3 Spin_Up_Time 0x0007 149 149 024 Pre-fail Always - 442 (Average 441) */
} else if (sscanf(s, "%u %127s %*s %63s %63s %63s %63s %63s %63s %" SCNu64 " (Average %" SCNu64 ")", &id, id_name, norm, worst, thresh, type, updated, when_failed, &raw, &avg) == 10) {
format_avg = 1;
} else if (sscanf(s, "%u %127s %*s %63s %63s %63s %63s %63s %63s %" SCNu64, &id, id_name, norm, worst, thresh, type, updated, when_failed, &raw) == 9) {
} else {
log_fatal(EEXTERNAL, "Invalid smartctl line '%s'.\n", s);
return -1;
}
if (id >= 256) {
/* LCOV_EXCL_START */
log_fatal(EEXTERNAL, "Invalid SMART id '%u'.\n", id);
return -1;
/* LCOV_EXCL_STOP */
}
flags = 0;
if (strcmp(type, "Pre-fail") == 0)
flags |= SMART_ATTR_TYPE_PREFAIL;
else if (strcmp(type, "Old_age") == 0)
flags |= SMART_ATTR_TYPE_OLDAGE;
if (strcmp(updated, "Always") == 0)
flags |= SMART_ATTR_UPDATE_ALWAYS;
else if (strcmp(updated, "Offline") == 0)
flags |= SMART_ATTR_UPDATE_OFFLINE;
if (strcmp(when_failed, "FAILING_NOW") == 0)
flags |= SMART_ATTR_WHEN_FAILED_NOW;
else if (strcmp(when_failed, "In_the_past") == 0)
flags |= SMART_ATTR_WHEN_FAILED_PAST;
else if (strcmp(when_failed, "-") == 0)
flags |= SMART_ATTR_WHEN_FAILED_NEVER;
/* revert the min/max decoding done by smartctl */
if (format_minmax
&& raw <= 0xFFFFUL
&& min <= 0xFFFFUL
&& max <= 0xFFFFUL) {
raw |= min << 16;
raw |= max << 32;
}
/* revert the avg decoding done by smartctl */
if (format_avg
&& raw <= 0xFFFFUL
&& avg <= 0xFFFFUL) {
raw |= avg << 16;
}
smart[id].raw = raw;
v = strtoul(norm, &e, 10);
if (e != norm || *e == 0) {
smart[id].norm = v;
} else {
smart[id].norm = SMART_UNASSIGNED;
}
v = strtoul(worst, &e, 10);
if (e != worst || *e == 0) {
smart[id].worst = v;
} else {
smart[id].worst = SMART_UNASSIGNED;
}
v = strtoul(thresh, &e, 10);
if (e != thresh || *e == 0) {
smart[id].thresh = v;
} else {
smart[id].thresh = SMART_UNASSIGNED;
}
smart[id].flags = flags;
pathcpy(smart[id].name, sizeof(smart[id].name), id_name);
/*
* Map normalized health percentage to our unified wear level for SSDs
* 177: Wear_Leveling_Count (Samsung/Crucial)
* 231: SSD_Life_Left (Kingston/WD)
* 233: Media_Wearout_Indicator (Intel)
*/
if (smart[id].norm != SMART_UNASSIGNED && smart[id].norm <= 100) {
if (strcmp(id_name, "Wear_Leveling_Count") == 0
|| strcmp(id_name, "SSD_Life_Left") == 0
|| strcmp(id_name, "Media_Wearout_Indicator") == 0) {
smart[SMART_WEAR_LEVEL].raw = 100 - smart[id].norm;
}
}
}
}
return 0;
}
int smartctl_flush(OS_FILE* f, const char* file, const char* name)
{
/* read the file */
while (1) {
char buf[256];
char* s;
s = os_fgets(buf, sizeof(buf), f);
if (s == 0)
break;
/* remove extraneous chars */
s = strpolish(buf);
log_tag("smartctl:%s:%s:out: %s\n", file, name, s);
}
return 0;
}
int smart_temp(devinfo_t* devinfo)
{
uint64_t t = devinfo->smart[SMART_TEMPERATURE_CELSIUS].raw;
if (t == SMART_UNASSIGNED)
t = devinfo->smart[SMART_AIRFLOW_TEMPERATURE_CELSIUS].raw;
if (t == SMART_UNASSIGNED)
return -1;
/* mask out min/max values */
t &= 0xFFFFUL;
if (t == 0)
return -1;
if (t > 100)
return -1;
return (int)t;
}
/****************************************************************************/
/* thread */
#if HAVE_THREAD
void thread_mutex_init(thread_mutex_t* mutex)
{
if (pthread_mutex_init(mutex, 0) != 0) {
/* LCOV_EXCL_START */
fprintf(stderr, "Failed call to pthread_mutex_init().\n"); /* cannot use log_fatal() because it needs locks */
os_abort();
/* LCOV_EXCL_STOP */
}
}
void thread_mutex_destroy(thread_mutex_t* mutex)
{
if (pthread_mutex_destroy(mutex) != 0) {
/* LCOV_EXCL_START */
fprintf(stderr, "Failed call to thread_mutex_destroy().\n"); /* cannot use log_fatal() because it needs locks */
os_abort();
/* LCOV_EXCL_STOP */
}
}
void thread_mutex_lock(thread_mutex_t* mutex)
{
if (pthread_mutex_lock(mutex) != 0) {
/* LCOV_EXCL_START */
fprintf(stderr, "Failed call to pthread_mutex_lock().\n"); /* cannot use log_fatal() because it needs locks */
os_abort();
/* LCOV_EXCL_STOP */
}
}
void thread_mutex_unlock(thread_mutex_t* mutex)
{
if (pthread_mutex_unlock(mutex) != 0) {
/* LCOV_EXCL_START */
fprintf(stderr, "Failed call to pthread_mutex_unlock().\n"); /* cannot use log_fatal() because it needs locks */
os_abort();
/* LCOV_EXCL_STOP */
}
}
void thread_cond_init(thread_cond_t* cond)
{
if (pthread_cond_init(cond, 0) != 0) {
/* LCOV_EXCL_START */
fprintf(stderr, "Failed call to pthread_cond_init().\n"); /* cannot use log_fatal() because it needs locks */
os_abort();
/* LCOV_EXCL_STOP */
}
}
void thread_cond_destroy(thread_cond_t* cond)
{
if (pthread_cond_destroy(cond) != 0) {
/* LCOV_EXCL_START */
fprintf(stderr, "Failed call to pthread_cond_destroy().\n"); /* cannot use log_fatal() because it needs locks */
os_abort();
/* LCOV_EXCL_STOP */
}
}
void thread_cond_signal(thread_cond_t* cond)
{
if (pthread_cond_signal(cond) != 0) {
/* LCOV_EXCL_START */
fprintf(stderr, "Failed call to pthread_cond_signal().\n"); /* cannot use log_fatal() because it needs locks */
os_abort();
/* LCOV_EXCL_STOP */
}
}
void thread_cond_broadcast(thread_cond_t* cond)
{
if (pthread_cond_broadcast(cond) != 0) {
/* LCOV_EXCL_START */
fprintf(stderr, "Failed call to pthread_cond_broadcast().\n"); /* cannot use log_fatal() because it needs locks */
os_abort();
/* LCOV_EXCL_STOP */
}
}
void thread_cond_wait(thread_cond_t* cond, thread_mutex_t* mutex)
{
if (pthread_cond_wait(cond, mutex) != 0) {
/* LCOV_EXCL_START */
fprintf(stderr, "Failed call to pthread_cond_wait().\n"); /* cannot use log_fatal() because it needs locks */
os_abort();
/* LCOV_EXCL_STOP */
}
}
/**
* Implementation note about conditional variables.
*
* The conditional variables can be signaled inside or outside the mutex,
* what is better it's debatable but in general doing that outside the mutex,
* reduces the number of context switches.
*
* But when testing with helgrind and drd, this disallows such tools to
* to see the dependency between the signal and the wait.
*
* To avoid it we signal everything inside the mutex. And we do this in both
* test mode (with CHECKER defined) and release mode (CHECKER not defined),
* to be on the safe side and avoid any difference in behaviour between test and
* release.
*
* Here some interesting discussion:
*
* Condvars: signal with mutex locked or not?
* http://www.domaigne.com/blog/computing/condvars-signal-with-mutex-locked-or-not/
*
* Calling pthread_cond_signal without locking mutex
* http://stackoverflow.com/questions/4544234/calling-pthread-cond-signal-without-locking-mutex/4544494#4544494
*/
/**
* Control when to signal the condition variables.
*/
int thread_cond_signal_outside = 0;
void thread_cond_signal_and_unlock(thread_cond_t* cond, thread_mutex_t* mutex)
{
if (thread_cond_signal_outside) {
/*
* Without the thread checker unlock before signaling,
* this reduces the number of context switches
*/
thread_mutex_unlock(mutex);
}
thread_cond_signal(cond);
if (!thread_cond_signal_outside) {
/*
* With the thread checker unlock after signaling
* to make explicit the condition and mutex relation
*/
thread_mutex_unlock(mutex);
}
}
void thread_cond_broadcast_and_unlock(thread_cond_t* cond, thread_mutex_t* mutex)
{
if (thread_cond_signal_outside) {
/*
* Without the thread checker unlock before signaling,
* this reduces the number of context switches
*/
thread_mutex_unlock(mutex);
}
thread_cond_broadcast(cond);
if (!thread_cond_signal_outside) {
/*
* With the thread checker unlock after signaling
* to make explicit the condition and mutex relation
*/
thread_mutex_unlock(mutex);
}
}
void thread_create(thread_id_t* thread, void* (*func)(void*), void* arg)
{
if (pthread_create(thread, 0, func, arg) != 0) {
/* LCOV_EXCL_START */
log_fatal(EINTERNAL, "Failed call to pthread_create().\n");
os_abort();
/* LCOV_EXCL_STOP */
}
}
void thread_join(thread_id_t thread, void** retval)
{
if (pthread_join(thread, retval) != 0) {
/* LCOV_EXCL_START */
log_fatal(EINTERNAL, "Failed call to pthread_join().\n");
os_abort();
/* LCOV_EXCL_STOP */
}
}
void thread_yield(void)
{
#ifdef __MINGW32__
Sleep(1); /* avoid 100% cpu utilization */
#else
sched_yield();
usleep(1000); /* avoid 100% cpu utilization */
#endif
}
#endif
int thread_key_create(thread_key_t* key, void (*destructor)(void*))
{
return pthread_key_create(key, destructor);
}
int thread_key_delete(thread_key_t key)
{
return pthread_key_delete(key);
}
void* thread_getspecific(thread_key_t key)
{
return pthread_getspecific(key);
}
int thread_setspecific(thread_key_t key, void* value)
{
return pthread_setspecific(key, value);
}