mirror of
https://github.com/amadvance/snapraid.git
synced 2026-10-26 02:49:02 -04:00
3062 lines
72 KiB
C
3062 lines
72 KiB
C
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
// Copyright (C) 2011 Andrea Mazzoleni
|
|
|
|
#include "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;
|
|
FILE* stdlog = 0;
|
|
int msg_line_prev = 0; /**< Previous line width on stdout */
|
|
int msg_line_curr = 0; /**< Current line width on stdout */
|
|
|
|
/*
|
|
* 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);
|
|
}
|
|
|
|
void log_fatal(int err, const char* format, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
lock_msg();
|
|
|
|
if (stdlog) {
|
|
if (is_hw(err))
|
|
fprintf(stdlog, "msg:fatal_hardware: ");
|
|
else
|
|
fprintf(stdlog, "msg:fatal: ");
|
|
|
|
va_start(ap, format);
|
|
vfprintf(stdlog, format, ap);
|
|
va_end(ap);
|
|
|
|
fflush(stdlog);
|
|
}
|
|
|
|
va_start(ap, format);
|
|
vmsg(stderr, format, ap);
|
|
va_end(ap);
|
|
|
|
fflush(stderr);
|
|
|
|
unlock_msg();
|
|
}
|
|
|
|
void log_error(int err, const char* format, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
lock_msg();
|
|
|
|
if (stdlog) {
|
|
if (is_hw(err))
|
|
fprintf(stdlog, "msg:error_hardware: ");
|
|
else
|
|
fprintf(stdlog, "msg:error: ");
|
|
|
|
va_start(ap, format);
|
|
vfprintf(stdlog, format, ap);
|
|
va_end(ap);
|
|
|
|
fflush(stdlog);
|
|
} else {
|
|
va_start(ap, format);
|
|
vmsg(stderr, format, ap);
|
|
va_end(ap);
|
|
|
|
fflush(stderr);
|
|
}
|
|
|
|
unlock_msg();
|
|
}
|
|
|
|
void log_expected(int err, const char* format, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
lock_msg();
|
|
|
|
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
|
|
|
|
char* strpolish(char* s)
|
|
{
|
|
char* i = s;
|
|
|
|
while (*i) {
|
|
if (isspace((unsigned char)*i) || !isprint((unsigned char)*i))
|
|
*i = ' ';
|
|
++i;
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
unsigned strsplit(char** split_map, unsigned split_max, char* str, const char* delimiters)
|
|
{
|
|
unsigned mac = 0;
|
|
|
|
/* skip initial delimiters */
|
|
str += strspn(str, delimiters);
|
|
|
|
while (*str != 0 && mac < split_max) {
|
|
/* start of the token */
|
|
split_map[mac] = str;
|
|
++mac;
|
|
|
|
/* find the first delimiter or the end of the string */
|
|
str += strcspn(str, delimiters);
|
|
|
|
/* put the final terminator if missing */
|
|
if (*str != 0)
|
|
*str++ = 0;
|
|
|
|
/* skip trailing delimiters */
|
|
str += strspn(str, delimiters);
|
|
}
|
|
|
|
return mac;
|
|
}
|
|
|
|
unsigned argsplit(const char** split_map, unsigned split_max, char* str)
|
|
{
|
|
unsigned mac = 0;
|
|
|
|
/* skip leading whitespace */
|
|
while (*str != 0 && isspace((unsigned char)*str))
|
|
++str;
|
|
|
|
while (*str != 0 && mac < split_max) {
|
|
char* dst = str;
|
|
split_map[mac++] = dst;
|
|
|
|
#ifdef _WIN32
|
|
/*
|
|
* cmd.exe rules:
|
|
* Unquoted: '^' escapes next char; '"' opens a quoted block.
|
|
* Quoted: '"' closes the block; '^' is an ordinary character.
|
|
*/
|
|
int quoted = 0;
|
|
|
|
while (*str != 0 && (quoted || !isspace((unsigned char)*str))) {
|
|
if (quoted) {
|
|
if (*str == '"') {
|
|
quoted = 0;
|
|
++str;
|
|
} else {
|
|
*dst++ = *str++;
|
|
}
|
|
} else {
|
|
if (*str == '^' && *(str + 1) != 0) {
|
|
++str;
|
|
*dst++ = *str++;
|
|
} else if (*str == '"') {
|
|
quoted = 1;
|
|
++str;
|
|
} else {
|
|
*dst++ = *str++;
|
|
}
|
|
}
|
|
}
|
|
|
|
#else
|
|
/*
|
|
* bash rules:
|
|
* Single-quoted block: everything literal, no exceptions.
|
|
* Double-quoted block: '\' only escapes $, `, ", '\', newline;
|
|
* other backslashes are copied verbatim.
|
|
* Unquoted '\': always escapes the immediately following char.
|
|
*/
|
|
|
|
/* set of characters that '\' can escape inside double quotes */
|
|
static const char dq_escapable[] = "$`\"\\\n";
|
|
|
|
while (*str != 0 && !isspace((unsigned char)*str)) {
|
|
|
|
if (*str == '\'') {
|
|
/* single-quoted block: truly everything is literal */
|
|
++str;
|
|
while (*str != 0 && *str != '\'')
|
|
*dst++ = *str++;
|
|
if (*str == '\'')
|
|
++str;
|
|
|
|
} else if (*str == '"') {
|
|
/* double-quoted block */
|
|
++str;
|
|
while (*str != 0 && *str != '"') {
|
|
if (*str == '\\' && strchr(dq_escapable, *(str + 1))) {
|
|
++str;
|
|
}
|
|
*dst++ = *str++;
|
|
}
|
|
if (*str == '"')
|
|
++str;
|
|
|
|
} else if (*str == '\\') {
|
|
/* unquoted backslash: next character always literal */
|
|
++str;
|
|
if (*str != 0)
|
|
*dst++ = *str++;
|
|
|
|
} else {
|
|
*dst++ = *str++;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (*str != 0)
|
|
++str; /* step past the delimiter */
|
|
|
|
/* skip whitespace before the next token */
|
|
while (*str != 0 && isspace((unsigned char)*str))
|
|
++str;
|
|
|
|
/* null-terminate after str has already moved on */
|
|
*dst = 0;
|
|
}
|
|
|
|
return mac;
|
|
}
|
|
|
|
char* strtrim(char* str)
|
|
{
|
|
char* begin;
|
|
char* end;
|
|
|
|
begin = str;
|
|
while (begin[0] && isspace((unsigned char)begin[0]))
|
|
++begin;
|
|
|
|
end = begin + strlen(begin);
|
|
while (end > begin && isspace((unsigned char)end[-1]))
|
|
--end;
|
|
|
|
end[0] = 0;
|
|
|
|
if (begin != end)
|
|
memmove(str, begin, end - begin + 1);
|
|
|
|
return str;
|
|
}
|
|
|
|
char* strlwr(char* str)
|
|
{
|
|
char* s = str;
|
|
|
|
while (*s) {
|
|
*s = tolower((unsigned char)*s);
|
|
++s;
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
char* worddigitstr(const char* haystack, const char* needle)
|
|
{
|
|
size_t len = strlen(needle);
|
|
const char* s;
|
|
|
|
if (len == 0)
|
|
return NULL;
|
|
|
|
for (s = haystack; (s = strstr(s, needle)) != NULL; ++s) {
|
|
|
|
/* left boundary */
|
|
if (s == haystack || isspace((unsigned char)s[-1]) || isdigit((unsigned char)s[-1])) {
|
|
/* right boundary */
|
|
if (s[len] == 0 || isspace((unsigned char)s[len]) || isdigit((unsigned char)s[len])) {
|
|
return (char*)s;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int strtoi(const char* nptr, char** endptr, int base)
|
|
{
|
|
long val;
|
|
int save_errno = errno;
|
|
|
|
errno = 0;
|
|
val = strtol(nptr, endptr, base);
|
|
|
|
if (errno == ERANGE) {
|
|
return val > 0 ? INT_MAX : INT_MIN;
|
|
}
|
|
|
|
if (val > INT_MAX) {
|
|
errno = ERANGE;
|
|
return INT_MAX;
|
|
}
|
|
|
|
if (val < INT_MIN) {
|
|
errno = ERANGE;
|
|
return INT_MIN;
|
|
}
|
|
|
|
if (errno == 0) {
|
|
errno = save_errno;
|
|
}
|
|
|
|
return (int)val;
|
|
}
|
|
|
|
unsigned strtou(const char* nptr, char** endptr, int base)
|
|
{
|
|
const char* p = nptr;
|
|
char* end;
|
|
unsigned long val;
|
|
int neg = 0;
|
|
int save_errno = errno;
|
|
|
|
while (isspace((unsigned char)*p)) {
|
|
++p;
|
|
}
|
|
|
|
if (*p == '-') {
|
|
neg = 1;
|
|
++p;
|
|
} else if (*p == '+') {
|
|
++p;
|
|
}
|
|
|
|
errno = 0;
|
|
val = strtoul(p, &end, base);
|
|
|
|
if (end == p) {
|
|
/* no conversion was performed */
|
|
if (endptr) {
|
|
*endptr = (char*)nptr;
|
|
}
|
|
errno = save_errno;
|
|
return 0;
|
|
}
|
|
|
|
if (endptr) {
|
|
*endptr = end;
|
|
}
|
|
|
|
if (errno == ERANGE || val > UINT_MAX) {
|
|
errno = ERANGE;
|
|
return UINT_MAX;
|
|
}
|
|
|
|
if (errno == 0) {
|
|
errno = save_errno;
|
|
}
|
|
|
|
if (neg) {
|
|
return (unsigned)-val;
|
|
}
|
|
|
|
return (unsigned)val;
|
|
}
|
|
|
|
/****************************************************************************/
|
|
/* 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 '/',
|
|
* and the Windows escaping char '^' to the fnmatch '\'
|
|
*/
|
|
while (*dst) {
|
|
switch (*dst) {
|
|
case '\\' :
|
|
*dst = '/';
|
|
break;
|
|
case '^' :
|
|
*dst = '\\';
|
|
break;
|
|
}
|
|
++dst;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void pathexport(char* dst, size_t size, const char* src)
|
|
{
|
|
pathcpy(dst, size, src);
|
|
|
|
#ifdef _WIN32
|
|
/* invert the import */
|
|
while (*dst) {
|
|
switch (*dst) {
|
|
case '/' :
|
|
*dst = '\\';
|
|
break;
|
|
case '\\' :
|
|
*dst = '^';
|
|
break;
|
|
}
|
|
++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;
|
|
}
|
|
|
|
/****************************************************************************/
|
|
/* 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;
|
|
}
|
|
|
|
void 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) {
|
|
/* start writing immediately */
|
|
sync_file_range(f, flush_offset, flush_size, SYNC_FILE_RANGE_WRITE);
|
|
}
|
|
#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;
|
|
/* LCOV_EXCL_STOP */
|
|
}
|
|
|
|
/* flush the data from the cache */
|
|
posix_fadvise_wrapper(f, discard_offset, discard_size, POSIX_FADV_DONTNEED);
|
|
}
|
|
#endif
|
|
|
|
advise->dirty_begin = 0;
|
|
advise->dirty_end = 0;
|
|
}
|
|
|
|
/****************************************************************************/
|
|
/* memory */
|
|
|
|
/**
|
|
* Cumulative amount of memory allocated.
|
|
*/
|
|
static size_t mcounter;
|
|
|
|
size_t malloc_counter_get(void)
|
|
{
|
|
size_t ret;
|
|
|
|
lock_memory();
|
|
|
|
ret = mcounter;
|
|
|
|
unlock_memory();
|
|
|
|
return ret;
|
|
}
|
|
|
|
void malloc_counter_inc(size_t inc)
|
|
{
|
|
lock_memory();
|
|
|
|
mcounter += inc;
|
|
|
|
unlock_memory();
|
|
}
|
|
|
|
/* LCOV_EXCL_START */
|
|
static ssize_t malloc_print(int f, const char* str)
|
|
{
|
|
ssize_t len = 0;
|
|
|
|
while (str[len])
|
|
++len;
|
|
return write(f, str, len);
|
|
}
|
|
/* LCOV_EXCL_STOP */
|
|
|
|
/* LCOV_EXCL_START */
|
|
static ssize_t malloc_printn(int f, size_t value)
|
|
{
|
|
char buf[32];
|
|
int i;
|
|
|
|
if (!value)
|
|
return write(f, "0", 1);
|
|
|
|
i = sizeof(buf);
|
|
while (value) {
|
|
buf[--i] = (value % 10) + '0';
|
|
value /= 10;
|
|
}
|
|
|
|
return write(f, buf + i, sizeof(buf) - i);
|
|
}
|
|
/* LCOV_EXCL_STOP */
|
|
|
|
/* LCOV_EXCL_START */
|
|
void malloc_fail(size_t size)
|
|
{
|
|
/* don't use printf to avoid any possible extra allocation */
|
|
int f = 2; /* stderr */
|
|
|
|
malloc_print(f, "Failed for Low Memory!\n");
|
|
malloc_print(f, "Allocating ");
|
|
malloc_printn(f, size);
|
|
malloc_print(f, " bytes.\n");
|
|
malloc_print(f, "Already allocated ");
|
|
malloc_printn(f, malloc_counter_get());
|
|
malloc_print(f, " bytes.\n");
|
|
if (sizeof(void*) == 4) {
|
|
malloc_print(f, "You are currently using a 32 bits executable.\n");
|
|
malloc_print(f, "If you have more than 4GB of memory, please upgrade to a 64 bits one.\n");
|
|
}
|
|
}
|
|
/* LCOV_EXCL_STOP */
|
|
|
|
void* malloc_nofail(size_t size)
|
|
{
|
|
void* ptr = malloc(size);
|
|
|
|
if (!ptr) {
|
|
/* LCOV_EXCL_START */
|
|
malloc_fail(size);
|
|
exit(EXIT_FAILURE);
|
|
/* LCOV_EXCL_STOP */
|
|
}
|
|
|
|
#ifndef CHECKER /* Don't preinitialize when running for valgrind */
|
|
/*
|
|
* Here we preinitialize the memory to ensure that the OS is really allocating it
|
|
* and not only reserving the addressable space.
|
|
* Otherwise we are risking that the OOM (Out Of Memory) killer in Linux will kill the process.
|
|
* Filling the memory doesn't ensure to disable OOM, but it increase a lot the chances to
|
|
* get a real error from malloc() instead than a process killed.
|
|
* Note that calloc() doesn't have the same effect.
|
|
*/
|
|
memset(ptr, 0xA5, size);
|
|
#endif
|
|
|
|
malloc_counter_inc(size);
|
|
|
|
return ptr;
|
|
}
|
|
|
|
void* calloc_nofail(size_t count, size_t size)
|
|
{
|
|
void* ptr;
|
|
|
|
size *= count;
|
|
|
|
/* see the note in malloc_nofail() of why we don't use calloc() */
|
|
ptr = malloc(size);
|
|
|
|
if (!ptr) {
|
|
/* LCOV_EXCL_START */
|
|
malloc_fail(size);
|
|
exit(EXIT_FAILURE);
|
|
/* LCOV_EXCL_STOP */
|
|
}
|
|
|
|
memset(ptr, 0, size);
|
|
|
|
malloc_counter_inc(size);
|
|
|
|
return ptr;
|
|
}
|
|
|
|
char* strdup_nofail(const char* str)
|
|
{
|
|
size_t size;
|
|
char* ptr;
|
|
|
|
size = strlen(str) + 1;
|
|
|
|
ptr = malloc(size);
|
|
|
|
if (!ptr) {
|
|
/* LCOV_EXCL_START */
|
|
malloc_fail(size);
|
|
exit(EXIT_FAILURE);
|
|
/* LCOV_EXCL_STOP */
|
|
}
|
|
|
|
memcpy(ptr, str, size);
|
|
|
|
malloc_counter_inc(size);
|
|
|
|
return ptr;
|
|
}
|
|
|
|
/****************************************************************************/
|
|
/* 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;
|
|
|
|
/* 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");
|
|
/* 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[128] = { 0 };
|
|
char type[64] = { 0 };
|
|
char updated[64] = { 0 };
|
|
char when_failed[64] = { 0 };
|
|
char norm[64] = { 0 };
|
|
char worst[64] = { 0 };
|
|
char thresh[64] = { 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(0);
|
|
#else
|
|
sched_yield();
|
|
#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);
|
|
}
|
|
|
|
/****************************************************************************/
|
|
/* wnmatch */
|
|
|
|
/**
|
|
* Helper function for case-insensitive character comparison
|
|
*/
|
|
static inline int char_match(char p, char t)
|
|
{
|
|
#ifdef WIN32
|
|
return tolower((unsigned char)p) == tolower((unsigned char)t);
|
|
#else
|
|
return p == t;
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Match character class [...]
|
|
* Return 0 if NOT matched
|
|
*/
|
|
static const char* match_class(const char* p, char t)
|
|
{
|
|
int negate = 0;
|
|
int matched = 0;
|
|
|
|
if (*p == '!' || *p == '^') {
|
|
negate = 1;
|
|
++p;
|
|
}
|
|
|
|
while (*p && *p != ']') {
|
|
if (p[1] == '-' && p[2] != ']' && p[2] != 0) {
|
|
/* range [a-z] */
|
|
char start = *p;
|
|
char end = p[2];
|
|
#ifdef WIN32
|
|
start = tolower((unsigned char)start);
|
|
end = tolower((unsigned char)end);
|
|
t = tolower((unsigned char)t);
|
|
#endif
|
|
if (t >= start && t <= end)
|
|
matched = 1;
|
|
p += 3;
|
|
} else {
|
|
/* single character */
|
|
if (char_match(*p, t))
|
|
matched = 1;
|
|
++p;
|
|
}
|
|
}
|
|
|
|
if (*p == ']')
|
|
++p;
|
|
|
|
if (negate)
|
|
matched = !matched;
|
|
if (!matched)
|
|
return 0;
|
|
|
|
return p;
|
|
}
|
|
|
|
int wnmatch_sub(const char* p, const char* t, int match_sub)
|
|
{
|
|
char p1 = 0; /* previous char */
|
|
while (*p) {
|
|
char p0 = *p;
|
|
switch (p0) {
|
|
case '?' :
|
|
/* ? matches any single character except / */
|
|
if (*t == 0 || *t == '/')
|
|
return 1;
|
|
++p;
|
|
++t;
|
|
break;
|
|
case '*' :
|
|
/* check for ** */
|
|
if (p[1] == '*') {
|
|
/* skip the ** */
|
|
p += 2;
|
|
|
|
/* munge all * */
|
|
while (*p == '*')
|
|
++p;
|
|
|
|
/* if its not near a slash, it's like a single * */
|
|
if (p1 == '/' || *p == '/') {
|
|
/* a ** at end matches everything */
|
|
if (*p == 0)
|
|
return 0;
|
|
|
|
/*
|
|
* In between slashes matches to nothing
|
|
*
|
|
* Check for /##/ or ^##/ (^ start of string)
|
|
*
|
|
* This is required for:
|
|
* "/##/file.txt" matching "/file.txt"
|
|
* "##/file.txt" matching "file.txt"
|
|
* "x##/file.txt" NOT matching "xfile.txt"
|
|
*/
|
|
if (*p == '/' && (p1 == 0 || p1 == '/')) {
|
|
/* try reducing to nothing */
|
|
if (wnmatch_sub(p + 1, t, match_sub) == 0)
|
|
return 0;
|
|
/* otherwise / should match in the text */
|
|
}
|
|
|
|
/* try matching with 0 or more characters */
|
|
while (*t) {
|
|
if (wnmatch_sub(p, t, match_sub) == 0)
|
|
return 0;
|
|
++t;
|
|
}
|
|
|
|
/* try matching at the end */
|
|
return wnmatch_sub(p, t, match_sub);
|
|
}
|
|
} else {
|
|
/* skip the * */
|
|
++p;
|
|
}
|
|
|
|
/* a * at end matches rest of segment */
|
|
if (*p == 0) {
|
|
while (*t && *t != '/')
|
|
++t;
|
|
return *t != 0;
|
|
}
|
|
|
|
/* try matching with 0 or more characters */
|
|
while (*t && *t != '/') {
|
|
if (wnmatch_sub(p, t, match_sub) == 0)
|
|
return 0;
|
|
++t;
|
|
}
|
|
|
|
/* try matching at the end */
|
|
return wnmatch_sub(p, t, match_sub);
|
|
case '[' :
|
|
/* character class */
|
|
if (*t == 0 || *t == '/')
|
|
return 1;
|
|
|
|
p = match_class(p + 1, *t);
|
|
if (!p)
|
|
return 1;
|
|
|
|
++t;
|
|
break;
|
|
default :
|
|
/* literal character */
|
|
if (*t == 0 || !char_match(*p, *t))
|
|
return 1;
|
|
++p;
|
|
++t;
|
|
break;
|
|
}
|
|
p1 = p0;
|
|
}
|
|
|
|
/* if we match sub directory */
|
|
if (match_sub) {
|
|
/* match successfully only if we are at a directory border */
|
|
return *t != '/';
|
|
} else {
|
|
/* match successfully if we've consumed all text */
|
|
return *t != 0;
|
|
}
|
|
}
|
|
|