serval-dna/os.c
Andrew Bettison 684735b11c Fix config file timestamp bug
Ensure that timestamp always advances by at least one second every time
the config file is modified, add test case

Refactor test defs: detecting started HTTPD is not specific to Rhizome

Refactor struct file_meta and associated functions into "io.h" and io.c

Add various strbuf helper functions for formatting struct timespec
and struct file_meta to assist debug logging
2014-04-29 15:04:20 +09:30

333 lines
11 KiB
C

/*
Serval Distributed Numbering Architecture (DNA)
Copyright (C) 2010 Paul Gardner-Stephen
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#define __SERVAL_DNA__OS_INLINE
#include "constants.h"
#include "os.h"
#include "mem.h"
#include "str.h"
#include "log.h"
#include "strbuf_helpers.h"
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <fcntl.h>
#include <alloca.h>
#include <dirent.h>
#include <time.h>
#include <string.h>
void log_info_mkdir(struct __sourceloc __whence, const char *path, mode_t mode)
{
INFOF("mkdir %s (mode %04o)", alloca_str_toprint(path), mode);
}
int _mkdirs(struct __sourceloc __whence, const char *path, mode_t mode, MKDIR_LOG_FUNC *logger)
{
return _mkdirsn(__whence, path, strlen(path), mode, logger);
}
int _emkdirs(struct __sourceloc __whence, const char *path, mode_t mode, MKDIR_LOG_FUNC *logger)
{
if (_mkdirs(__whence, path, mode, logger) == -1)
return WHYF_perror("mkdirs(%s,%o)", alloca_str_toprint(path), mode);
return 0;
}
int _emkdirsn(struct __sourceloc __whence, const char *path, size_t len, mode_t mode, MKDIR_LOG_FUNC *logger)
{
if (_mkdirsn(__whence, path, len, mode, logger) == -1)
return WHYF_perror("mkdirsn(%s,%lu,%o)", alloca_toprint(-1, path, len), (unsigned long)len, mode);
return 0;
}
/* This variant must not log anything itself, because it is called by the logging subsystem, and
* that would cause infinite recursion!
*
* The path need not be NUL terminated.
*
* The logger function pointer is usually NULL, for no logging, but may be any function the caller
* supplies (for example, log_info_mkdir).
*
* @author Andrew Bettison <andrew@servalproject.com>
*/
int _mkdirsn(struct __sourceloc whence, const char *path, size_t len, mode_t mode, MKDIR_LOG_FUNC *logger)
{
if (len == 0)
errno = EINVAL;
else {
char *pathfrag = alloca(len + 1);
strncpy(pathfrag, path, len)[len] = '\0';
if (mkdir(pathfrag, mode) != -1) {
if (logger)
logger(whence, pathfrag, mode);
return 0;
}
if (errno == EEXIST) {
DIR *d = opendir(pathfrag);
if (d) {
closedir(d);
return 0;
}
}
else if (errno == ENOENT) {
const char *lastsep = path + len - 1;
while (lastsep != path && *--lastsep != '/')
;
while (lastsep != path && *--lastsep == '/')
;
if (lastsep != path) {
if (_mkdirsn(whence, path, lastsep - path + 1, mode, logger) == -1)
return -1;
if (mkdir(pathfrag, mode) != -1) {
if (logger)
logger(whence, pathfrag, mode);
return 0;
}
}
}
}
return -1;
}
int urandombytes(unsigned char *buf, size_t len)
{
static int urandomfd = -1;
int tries = 0;
if (urandomfd == -1) {
for (tries = 0; tries < 4; ++tries) {
urandomfd = open("/dev/urandom",O_RDONLY);
if (urandomfd != -1) break;
sleep_ms(1000);
}
if (urandomfd == -1) {
WHY_perror("open(/dev/urandom)");
return -1;
}
}
tries = 0;
while (len > 0) {
ssize_t i = read(urandomfd, buf, (len < 1048576) ? len : 1048576);
if (i == -1) {
if (++tries > 4) {
WHY_perror("read(/dev/urandom)");
if (errno==EBADF) urandomfd=-1;
return -1;
}
} else {
tries = 0;
buf += i;
len -= i;
}
}
return 0;
}
time_ms_t gettime_ms()
{
struct timeval nowtv;
// If gettimeofday() fails or returns an invalid value, all else is lost!
if (gettimeofday(&nowtv, NULL) == -1)
FATAL_perror("gettimeofday");
if (nowtv.tv_sec < 0 || nowtv.tv_usec < 0 || nowtv.tv_usec >= 1000000)
FATALF("gettimeofday returned tv_sec=%ld tv_usec=%ld", (long)nowtv.tv_sec, (long)nowtv.tv_usec);
return nowtv.tv_sec * 1000LL + nowtv.tv_usec / 1000;
}
// Returns sleep time remaining.
time_ms_t sleep_ms(time_ms_t milliseconds)
{
if (milliseconds <= 0)
return 0;
struct timespec delay;
struct timespec remain;
delay.tv_sec = milliseconds / 1000;
delay.tv_nsec = (milliseconds % 1000) * 1000000;
if (nanosleep(&delay, &remain) == -1 && errno != EINTR)
FATALF_perror("nanosleep(tv_sec=%ld, tv_nsec=%ld)", delay.tv_sec, delay.tv_nsec);
return remain.tv_sec * 1000 + remain.tv_nsec / 1000000;
}
ssize_t read_symlink(const char *path, char *buf, size_t len)
{
if (len == 0) {
struct stat stat;
if (lstat(path, &stat) == -1)
return WHYF_perror("lstat(%s)", path);
return stat.st_size;
}
ssize_t nr = readlink(path, buf, len);
if (nr == -1)
return WHYF_perror("readlink(%s,%p,%zu)", path, buf, len);
if ((size_t)nr >= len)
return WHYF("buffer overrun from readlink(%s, len=%zu)", path, len);
buf[nr] = '\0';
return nr;
}
ssize_t read_whole_file(const char *path, unsigned char *buffer, size_t buffer_size)
{
assert(buffer != NULL);
assert(buffer_size != 0);
if (malloc_read_whole_file(path, &buffer, &buffer_size) == -1)
return -1;
return buffer_size;
}
int malloc_read_whole_file(const char *path, unsigned char **bufp, size_t *sizp)
{
int fd = open(path, O_RDONLY);
if (fd == -1)
return WHYF_perror("open(%d,%s,O_RDONLY)", fd, alloca_str_toprint(path));
ssize_t ret;
struct stat stat;
if (fstat(fd, &stat) == -1)
ret = WHYF_perror("fstat(%d)", fd);
else if (*bufp != NULL && (size_t)stat.st_size > *sizp)
ret = WHYF("file %s (size %zu) is larger than available buffer (%zu)", alloca_str_toprint(path), (size_t)stat.st_size, *sizp);
else if (*bufp == NULL && *sizp && (size_t)stat.st_size > *sizp)
ret = WHYF("file %s (size %zu) is larger than maximum buffer (%zu)", alloca_str_toprint(path), (size_t)stat.st_size, *sizp);
else {
*sizp = (size_t)stat.st_size;
if (*bufp == NULL && (*bufp = emalloc(*sizp)) == NULL)
ret = WHYF("file %s (size %zu) does not fit into memory", alloca_str_toprint(path), *sizp);
else {
assert(*bufp != NULL);
ret = read(fd, *bufp, *sizp);
if (ret == -1)
ret = WHYF_perror("read(%d,%s,%zu)", fd, alloca_str_toprint(path), *sizp);
}
}
if (close(fd) == -1)
ret = WHYF_perror("close(%d)", fd);
return ret;
}
int get_file_meta(const char *path, struct file_meta *metap)
{
struct stat st;
if (stat(path, &st) == -1) {
if (errno != ENOENT)
return WHYF_perror("stat(%s)", path);
*metap = FILE_META_NONEXIST;
} else {
metap->size = st.st_size;
metap->mtime = st.st_mtim;
// Truncate to whole seconds to ensure that this code will work on file systems that only have
// whole-second time stamp resolution.
metap->mtime.tv_nsec = 0;
}
return 0;
}
static int cmp_timespec(const struct timespec *a, const struct timespec *b)
{
return a->tv_sec < b->tv_sec ? -1 : a->tv_sec > b->tv_sec ? 1 : a->tv_nsec < b->tv_nsec ? -1 : a->tv_nsec > b->tv_nsec ? 1 : 0;
}
static void add_timespec(struct timespec *tv, long sec, long nsec)
{
const long NANO = 1000000000;
tv->tv_sec += sec;
// Bring nsec into range -NANO < nsec < NANO.
if (nsec >= NANO) {
sec = nsec / NANO;
tv->tv_sec += sec;
nsec -= sec * NANO;
} else if (nsec <= -NANO) {
// The C standard does not define whether negative integer division truncates towards negative
// infinity or rounds towards zero. So we have to use positive integer division, which always
// truncates towards zero.
sec = -nsec / NANO;
tv->tv_sec -= sec;
nsec += sec * NANO;
}
assert(nsec > -NANO);
assert(nsec < NANO);
tv->tv_nsec += nsec;
// Bring tv_nsec into range 0 <= tv_nsec < NANO.
if (tv->tv_nsec >= NANO) {
sec = tv->tv_nsec / NANO;
tv->tv_sec += sec;
tv->tv_nsec -= sec * NANO;
} else if (tv->tv_nsec < 0) {
sec = (-tv->tv_nsec + NANO - 1) / NANO;
tv->tv_sec -= sec;
tv->tv_nsec += sec * NANO;
}
assert(tv->tv_nsec >= 0);
assert(tv->tv_nsec < NANO);
}
int cmp_file_meta(const struct file_meta *a, const struct file_meta *b)
{
int c = cmp_timespec(&a->mtime, &b->mtime);
return c ? c : a->size < b->size ? -1 : a->size > b->size ? 1 : 0;
}
/* Post-update file meta adjustment.
*
* If a file's meta information is used to detect changes to the file by polling at regular
* intervals, then every update to the file must guarantee to never produce the same meta
* information as any prior update. The typical case is several updates in rapid succession during
* one second that do not change the size of the file. The second and subsequent of these will not
* change the file's meta information (size or last-modified time stamp) on file systems that only
* have one-second timestamp resolution.
*
* This function can be called immediately after updating such a file, supplying the meta
* information from just prior to the update. It will alter the file's meta information (last
* modified time stamp) to ensure that it differs from the prior meta information. This typically
* involves advancing the file's last-modification time stamp.
*
* Returns -1 if an error occurs, 1 if it alters the file's meta information, 0 if the current meta
* information is already different and did not need alteration.
*
* @author Andrew Bettison <andrew@servalproject.com>
*/
int alter_file_meta(const char *path, const struct file_meta *origp, struct file_meta *metap)
{
long nsec = 1;
long sec = 0;
// If the file's current last-modified timestamp is not greater than its original, try bumping the
// original timestamp by one nanosecond, and if that does not alter the timestamp, the file system
// does not support nanosecond timestamps, so try bumping it by one second.
while (sec <= 1) {
struct file_meta meta;
if (get_file_meta(path, &meta) == -1)
return -1;
if (metap)
*metap = meta;
if (is_file_meta_nonexist(&meta) || cmp_timespec(&origp->mtime, &meta.mtime) < 0)
return 0;
meta.mtime = origp->mtime;
add_timespec(&meta.mtime, sec, nsec);
struct timespec times[2];
times[0].tv_sec = 0;
times[0].tv_nsec = UTIME_OMIT;
times[1] = meta.mtime;
if (utimensat(AT_FDCWD, path, times, 0) == -1)
return WHYF_perror("utimensat(AT_FDCWD,%s,[UTIME_OMIT,%s],0)", alloca_str_toprint(path), alloca_timespec(&times[1]));
nsec = 0;
++sec;
}
return 1;
}