Merge branch 'main' of git.zander.im:Zander671/rpi4b-temp-humidity
This commit is contained in:
commit
54c23fc67b
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,3 +3,4 @@ bin/
|
|||||||
compile_commands.json
|
compile_commands.json
|
||||||
.cache/
|
.cache/
|
||||||
config.conf
|
config.conf
|
||||||
|
.clangd
|
||||||
|
6
Makefile
6
Makefile
@ -9,7 +9,8 @@ SRCS=src/main.c src/util.c src/lcd.c src/ths.c src/button.c src/ui/screen.c\
|
|||||||
src/ui/datesel.c src/ui/statsby.c src/ui/datapoints.c src/ui/timesel.c\
|
src/ui/datesel.c src/ui/statsby.c src/ui/datapoints.c src/ui/timesel.c\
|
||||||
src/ui/statrange.c src/config.c src/ui/blankscreen.c src/drive.c\
|
src/ui/statrange.c src/config.c src/ui/blankscreen.c src/drive.c\
|
||||||
src/ui/exportscreen.c src/ui/setdatescreen.c src/ui/viewdatescreen.c\
|
src/ui/exportscreen.c src/ui/setdatescreen.c src/ui/viewdatescreen.c\
|
||||||
src/ui/powerscreen.c src/ui/settzscreen.c src/ui/cleardatascreen.c
|
src/ui/powerscreen.c src/ui/settzscreen.c src/ui/cleardatascreen.c\
|
||||||
|
src/async.c
|
||||||
PROG=rpi4b-temp-humidity
|
PROG=rpi4b-temp-humidity
|
||||||
|
|
||||||
all: config.conf bin/${PROG}
|
all: config.conf bin/${PROG}
|
||||||
@ -24,7 +25,7 @@ bin/main.o bin/util.o bin/lcd.o bin/ths.o bin/button.o: src/util.h
|
|||||||
bin/ui/screen.o bin/ui/statsby.o bin/ui/datesel.o: src/util.h
|
bin/ui/screen.o bin/ui/statsby.o bin/ui/datesel.o: src/util.h
|
||||||
bin/ui/datapoints.o bin/ui/timesel.o bin/ui/statrange.o: src/util.h
|
bin/ui/datapoints.o bin/ui/timesel.o bin/ui/statrange.o: src/util.h
|
||||||
bin/ui/viewdatescreen.o bin/ui/setdatescreen.o bin/ui/powerscreen.o: src/util.h
|
bin/ui/viewdatescreen.o bin/ui/setdatescreen.o bin/ui/powerscreen.o: src/util.h
|
||||||
bin/ui/settzscreen.o bin/ui/cleardatascreen.o: src/util.h
|
bin/ui/settzscreen.o bin/ui/cleardatascreen.o bin/async.o: src/util.h
|
||||||
|
|
||||||
bin/main.o bin/lcd.o bin/screen.o bin/ui/datesel.o: src/lcd.h
|
bin/main.o bin/lcd.o bin/screen.o bin/ui/datesel.o: src/lcd.h
|
||||||
bin/ui/statsby.o bin/ui/timesel.o bin/ui/statrange.o: src/lcd.h
|
bin/ui/statsby.o bin/ui/timesel.o bin/ui/statrange.o: src/lcd.h
|
||||||
@ -59,6 +60,7 @@ bin/main.o bin/ui/powerscreen.o: src/ui/powerscreen.h
|
|||||||
bin/main.o bin/ui/settzscreen.o: src/ui/settzscreen.h
|
bin/main.o bin/ui/settzscreen.o: src/ui/settzscreen.h
|
||||||
bin/main.o bin/ui/cleardatascreen.o: src/ui/cleardatascreen.h
|
bin/main.o bin/ui/cleardatascreen.o: src/ui/cleardatascreen.h
|
||||||
bin/main.o bin/drive.o bin/ui/exportscreen.o: src/drive.h
|
bin/main.o bin/drive.o bin/ui/exportscreen.o: src/drive.h
|
||||||
|
bin/main.o bin/drive.o bin/ui/exportscreen.o: src/async.h
|
||||||
|
|
||||||
.if "${DEFAULT_TEMP_UNIT}" != "F" && "${DEFAULT_TEMP_UNIT}" != "C" &&\
|
.if "${DEFAULT_TEMP_UNIT}" != "F" && "${DEFAULT_TEMP_UNIT}" != "C" &&\
|
||||||
"${DEFAULT_TEMP_UNIT}" != "c" && "${DEFAULT_TEMP_UNIT}" != "c"
|
"${DEFAULT_TEMP_UNIT}" != "c" && "${DEFAULT_TEMP_UNIT}" != "c"
|
||||||
|
111
src/async.c
Normal file
111
src/async.c
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* async.c - Utilities for async operations
|
||||||
|
* Copyright (C) 2024 Alexander Rosenberg
|
||||||
|
*
|
||||||
|
* 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 3 of the License, or (at your option) any later
|
||||||
|
* version. See the LICENSE file for more information.
|
||||||
|
*/
|
||||||
|
#include "async.h"
|
||||||
|
#include "signal.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <sys/signal.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <err.h>
|
||||||
|
|
||||||
|
|
||||||
|
AsyncOperation *async_operation_start(AsyncOperationPollFunc poll,
|
||||||
|
AsyncOperationCleanupFunc cleanup,
|
||||||
|
void *data1, void *data2,
|
||||||
|
const char *path, char * const *args) {
|
||||||
|
pid_t pid = fork();
|
||||||
|
if (pid < 0) {
|
||||||
|
warn("fork(2)");
|
||||||
|
return NULL;
|
||||||
|
} else if (pid == 0) {
|
||||||
|
// child
|
||||||
|
if (!GLOBAL_OPTS.verbose) {
|
||||||
|
freopen("/dev/null", "w", stdout);
|
||||||
|
}
|
||||||
|
execvp(path, args);
|
||||||
|
err(1, "execvp(2) for \"%s\"", path);
|
||||||
|
} else {
|
||||||
|
// parent
|
||||||
|
AsyncOperation *op = malloc_checked(sizeof(AsyncOperation));
|
||||||
|
op->pid = pid;
|
||||||
|
op->data1 = data1;
|
||||||
|
op->data2 = data2;
|
||||||
|
op->poll_func = poll;
|
||||||
|
op->cleanup_func = cleanup;
|
||||||
|
op->done = false;
|
||||||
|
return op;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncOperation *async_operation_take(AsyncOperationPollFunc poll,
|
||||||
|
AsyncOperationCleanupFunc cleanup,
|
||||||
|
void *data1, void *data2, pid_t pid) {
|
||||||
|
AsyncOperation *op = malloc_checked(sizeof(AsyncOperation));
|
||||||
|
op->pid = pid;
|
||||||
|
op->data1 = data1;
|
||||||
|
op->data2 = data2;
|
||||||
|
op->poll_func = poll;
|
||||||
|
op->cleanup_func = cleanup;
|
||||||
|
op->done = false;
|
||||||
|
return op;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncOperationStatus async_operation_poll(AsyncOperation *op, bool hang) {
|
||||||
|
if (op->done) {
|
||||||
|
// polling a done process is a no-op
|
||||||
|
return ASYNC_OPERATION_DONE;
|
||||||
|
}
|
||||||
|
pid_t proc = waitpid(op->pid, &op->status, hang ? 0 : WNOHANG);
|
||||||
|
if (proc < 0) {
|
||||||
|
warn("waitpid(2)");
|
||||||
|
async_operation_cancel(op, false);
|
||||||
|
return ASYNC_OPERATION_FAILED;
|
||||||
|
} else if (proc > 0) {
|
||||||
|
op->done = true;
|
||||||
|
}
|
||||||
|
if (op->poll_func && !op->poll_func(op)) {
|
||||||
|
async_operation_cancel(op, false);
|
||||||
|
return ASYNC_OPERATION_FAILED;
|
||||||
|
}
|
||||||
|
return op->done ? ASYNC_OPERATION_DONE : ASYNC_OPERATION_WORKING;
|
||||||
|
}
|
||||||
|
|
||||||
|
void async_operation_cancel(AsyncOperation *op, bool force) {
|
||||||
|
if (op->done) {
|
||||||
|
// cancelling a done process is a no-op
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
kill(op->pid, force ? SIGKILL : SIGTERM);
|
||||||
|
if (!force) {
|
||||||
|
int status;
|
||||||
|
if (!timed_wait(op->pid, &status, 1000 /* 1ms */)) {
|
||||||
|
warn("waitpid(2)");
|
||||||
|
}
|
||||||
|
if (!WIFEXITED(status) && !WIFSIGNALED(status)) {
|
||||||
|
// force kill it if it refuses to exit itself
|
||||||
|
async_operation_cancel(op, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
op->done = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void async_operation_free(AsyncOperation *op) {
|
||||||
|
if (op) {
|
||||||
|
if (!op->done) {
|
||||||
|
async_operation_cancel(op, false);
|
||||||
|
}
|
||||||
|
if (op->cleanup_func) {
|
||||||
|
op->cleanup_func(op);
|
||||||
|
}
|
||||||
|
free(op);
|
||||||
|
}
|
||||||
|
}
|
75
src/async.h
Normal file
75
src/async.h
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* async.h - Utilities for async operations
|
||||||
|
* Copyright (C) 2024 Alexander Rosenberg
|
||||||
|
*
|
||||||
|
* 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 3 of the License, or (at your option) any later
|
||||||
|
* version. See the LICENSE file for more information.
|
||||||
|
*/
|
||||||
|
#ifndef INCLUDED_ASYNC_H
|
||||||
|
#define INCLUDED_ASYNC_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ASYNC_OPERATION_DONE,
|
||||||
|
ASYNC_OPERATION_FAILED,
|
||||||
|
ASYNC_OPERATION_WORKING
|
||||||
|
} AsyncOperationStatus;
|
||||||
|
|
||||||
|
typedef struct _AsyncOperation AsyncOperation;
|
||||||
|
|
||||||
|
// true on success, false otherwise
|
||||||
|
typedef bool(*AsyncOperationPollFunc)(AsyncOperation *op);
|
||||||
|
typedef void(*AsyncOperationCleanupFunc)(AsyncOperation *op);
|
||||||
|
|
||||||
|
struct _AsyncOperation {
|
||||||
|
AsyncOperationPollFunc poll_func;
|
||||||
|
AsyncOperationCleanupFunc cleanup_func;
|
||||||
|
int status;
|
||||||
|
pid_t pid;
|
||||||
|
bool done;
|
||||||
|
void *data1;
|
||||||
|
void *data2;
|
||||||
|
};
|
||||||
|
#define async_operation_failed(o) (WEXITSTATUS((o)->status) != 0)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Start a new async operation using the executable PATH and the arguments
|
||||||
|
* ARGS. The first element of ARGS should be the name of the new process, by
|
||||||
|
* convention. DATA1 and DATA2 are arbitrary pointers for storing data needed by
|
||||||
|
* CLEANUP or POLL.
|
||||||
|
* Return: NULL on error, the operation otherwise
|
||||||
|
*/
|
||||||
|
AsyncOperation *async_operation_start(AsyncOperationPollFunc poll,
|
||||||
|
AsyncOperationCleanupFunc cleanup,
|
||||||
|
void *data1, void *data2,
|
||||||
|
const char *path, char * const *args);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a new async operation from an already running process with PID.
|
||||||
|
*/
|
||||||
|
AsyncOperation *async_operation_take(AsyncOperationPollFunc poll,
|
||||||
|
AsyncOperationCleanupFunc cleanup,
|
||||||
|
void *data1, void *data2, pid_t pid);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Poll and return the status of OP. If HANG, wait until OP is done.
|
||||||
|
*/
|
||||||
|
AsyncOperationStatus async_operation_poll(AsyncOperation *op, bool hang);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Kill OP, by FORCE if necessary. This does NOT free the process, you need to
|
||||||
|
* do that yourself.
|
||||||
|
*/
|
||||||
|
void async_operation_cancel(AsyncOperation *op, bool force);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Free OP by calling its cleanup function. This will cancel it if it's still
|
||||||
|
* running!
|
||||||
|
*/
|
||||||
|
void async_operation_free(AsyncOperation *op);
|
||||||
|
|
||||||
|
#endif
|
196
src/drive.c
196
src/drive.c
@ -19,7 +19,6 @@
|
|||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <sys/param.h>
|
#include <sys/param.h>
|
||||||
#include <sys/mount.h>
|
#include <sys/mount.h>
|
||||||
#include <pthread.h>
|
|
||||||
|
|
||||||
static int compare_drives(const Drive *d1, const Drive *d2) {
|
static int compare_drives(const Drive *d1, const Drive *d2) {
|
||||||
return strcmp(d1->name, d2->name);
|
return strcmp(d1->name, d2->name);
|
||||||
@ -62,29 +61,16 @@ void drive_free_drives(Drive *drives, size_t count) {
|
|||||||
free(drives);
|
free(drives);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void close_pipe(void *ptr) {
|
|
||||||
int *pipe = ptr;
|
|
||||||
if (pipe[0] >= 0) {
|
|
||||||
close(pipe[0]);
|
|
||||||
}
|
|
||||||
if (pipe[1] >= 0) {
|
|
||||||
close(pipe[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char *drive_guess_fs(const char *dev) {
|
char *drive_guess_fs(const char *dev) {
|
||||||
char *result_fs = NULL;
|
|
||||||
int ctop[2] = {0, 0};
|
int ctop[2] = {0, 0};
|
||||||
pthread_cleanup_push(close_pipe, ctop);
|
|
||||||
pthread_cleanup_push(free, result_fs);
|
|
||||||
if (pipe(ctop) < 0) {
|
if (pipe(ctop) < 0) {
|
||||||
warn("failed to create pipe");
|
warn("failed to create pipe");
|
||||||
goto cleanup;
|
return NULL;
|
||||||
}
|
}
|
||||||
pid_t pid = fork();
|
pid_t pid = fork();
|
||||||
if (pid < 0) {
|
if (pid < 0) {
|
||||||
warn("fork(2) failed");
|
warn("fork(2) failed");
|
||||||
goto cleanup;
|
return NULL;
|
||||||
} else if (pid == 0) {
|
} else if (pid == 0) {
|
||||||
// child
|
// child
|
||||||
close(ctop[0]); // close read end
|
close(ctop[0]); // close read end
|
||||||
@ -104,125 +90,119 @@ char *drive_guess_fs(const char *dev) {
|
|||||||
int status;
|
int status;
|
||||||
if (waitpid(pid, &status, 0) < 0) {
|
if (waitpid(pid, &status, 0) < 0) {
|
||||||
warn("waiting for child failed");
|
warn("waiting for child failed");
|
||||||
goto cleanup;
|
return NULL;
|
||||||
}
|
}
|
||||||
if (WEXITSTATUS(status) != 0) {
|
if (WEXITSTATUS(status) != 0) {
|
||||||
// no error, we probably just don't know the FS type
|
// no error, we probably just don't know the FS type
|
||||||
goto cleanup;
|
return NULL;
|
||||||
}
|
}
|
||||||
char read_buf[32];
|
char read_buf[32];
|
||||||
ssize_t read_count = read(ctop[0], read_buf, 32);
|
ssize_t read_count = read(ctop[0], read_buf, 32);
|
||||||
if (read_count < 0) {
|
if (read_count < 0) {
|
||||||
warn("reading from pipe failed");
|
warn("reading from pipe failed");
|
||||||
goto cleanup;
|
return NULL;
|
||||||
}
|
}
|
||||||
read_buf[read_count - 1] = '\0';
|
read_buf[read_count - 1] = '\0';
|
||||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
return strdup_checked(read_buf);
|
||||||
result_fs = strdup_checked(read_buf);
|
|
||||||
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
|
||||||
cleanup:
|
|
||||||
pthread_cleanup_pop(false); // dont free result
|
|
||||||
pthread_cleanup_pop(true);
|
|
||||||
return result_fs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool drive_newfs_msdos(Drive *drive) {
|
static bool newfs_operation_poll(AsyncOperation *op) {
|
||||||
pid_t pid = fork();
|
if (op->done && !async_operation_failed(op)) {
|
||||||
if (pid < 0) {
|
Drive *drive = op->data1;
|
||||||
warn("fork(2) failed");
|
|
||||||
return false;
|
|
||||||
} else if (pid == 0) {
|
|
||||||
// child
|
|
||||||
if (!GLOBAL_OPTS.verbose) {
|
|
||||||
freopen("/dev/null", "w", stdout);
|
|
||||||
}
|
|
||||||
execl("/sbin/newfs_msdos", "/sbin/newfs_msdos", "-F32", drive->path, NULL);
|
|
||||||
warn("execl(2) failed");
|
|
||||||
exit(1);
|
|
||||||
} else {
|
|
||||||
// parent
|
|
||||||
int status;
|
|
||||||
if (waitpid(pid, &status, 0) < 0) {
|
|
||||||
warn("waiting for child failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (WEXITSTATUS(status) != 0) {
|
|
||||||
warnx("creating FAT32 FS failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
|
||||||
free(drive->fs);
|
free(drive->fs);
|
||||||
drive->fs = strdup_checked("msdosfs");
|
drive->fs = strdup_checked("msdosfs");
|
||||||
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AsyncOperation *drive_newfs_msdos(Drive *drive) {
|
||||||
|
if (drive->mnt) {
|
||||||
|
warnx("\"%s\" is mounted", drive->path);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
char * const args[] = {"newfs_msdos", "-F32", drive->path, NULL};
|
||||||
|
AsyncOperation * op= async_operation_start(newfs_operation_poll, NULL,
|
||||||
|
drive, NULL, args[0], args);
|
||||||
|
if (!op) {
|
||||||
|
warnx("%s: failed to start newfs_msdos process", drive->path);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return op;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool drive_mount(Drive *drive) {
|
static bool mount_operation_poll(AsyncOperation *op) {
|
||||||
bool status = true;
|
Drive *drive = op->data1;
|
||||||
char *name = NULL;
|
if (op->done && !async_operation_failed(op)) {
|
||||||
pthread_cleanup_push(free, name);
|
// steal the mount point string
|
||||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
drive->mnt = op->data2;
|
||||||
name = strdup_checked("/tmp/mntXXXXXX");
|
op->data2 = NULL;
|
||||||
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
LOG_VERBOSE("Mounted \"%s\" at \"%s\"\n", drive->path, drive->mnt);
|
||||||
|
} else if (op->done) {
|
||||||
|
warnx("mount of \"%s\" failed with status %d", drive->path,
|
||||||
|
WEXITSTATUS(op->status));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mount_operation_cleanup(AsyncOperation *op) {
|
||||||
|
free(op->data2);
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncOperation *drive_mount(Drive *drive) {
|
||||||
|
if (drive->mnt) {
|
||||||
|
warnx("\"%s\" is already mounted", drive->path);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
char *name = strdup_checked("/tmp/mntXXXXXX");
|
||||||
if (!mkdtemp(name)) {
|
if (!mkdtemp(name)) {
|
||||||
warn("failed to create temp mount point");
|
warn("failed to create temp mount point");
|
||||||
status = false;
|
return NULL;
|
||||||
goto cleanup;
|
|
||||||
}
|
}
|
||||||
pid_t pid = fork();
|
char * const args[] = { "mount", "-t", drive->fs, drive->path, name, NULL };
|
||||||
if (pid < 0) {
|
AsyncOperation *op = async_operation_start(mount_operation_poll,
|
||||||
warn("fork(2) failed");
|
mount_operation_cleanup,
|
||||||
status = false;
|
drive, name, args[0], args);
|
||||||
goto cleanup;
|
if (!op) {
|
||||||
} else if (pid == 0) {
|
warnx("%s: failed to start mount process", drive->path);
|
||||||
// child
|
free(name);
|
||||||
if (!GLOBAL_OPTS.verbose) {
|
return NULL;
|
||||||
freopen("/dev/null", "w", stdout);
|
|
||||||
}
|
}
|
||||||
execl("/sbin/mount", "/sbin/mount", "-t", drive->fs, drive->path, name, NULL);
|
return op;
|
||||||
warn("execl(2) failed");
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
// parent
|
|
||||||
int exit_code;
|
|
||||||
if (waitpid(pid, &exit_code, 0) < 0) {
|
|
||||||
warn("waiting for child failed");
|
|
||||||
rmdir(name);
|
|
||||||
status = false;
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
if (WEXITSTATUS(exit_code) != 0) {
|
|
||||||
warnx("mount(8) failed: \"%s\"", drive->path);
|
|
||||||
rmdir(name);
|
|
||||||
status = false;
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
|
||||||
drive->mnt = strdup_checked(name);
|
|
||||||
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
|
||||||
LOG_VERBOSE("Mounted \"%s\" with fs %s to \"%s\"\n", drive->path, drive->fs,
|
|
||||||
drive->mnt);
|
|
||||||
cleanup:
|
|
||||||
pthread_cleanup_pop(true);
|
|
||||||
return status;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool drive_unmount(Drive *drive, bool force) {
|
bool unmount_operation_poll(AsyncOperation *op) {
|
||||||
if (!drive->mnt) {
|
if (op->done) {
|
||||||
warnx("\"%s\" is not mounted", drive->path);
|
Drive *drive = op->data1;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
int flags = force ? MNT_FORCE : 0;
|
|
||||||
if (unmount(drive->mnt, flags) < 0) {
|
|
||||||
warn("unmount(2) failed for \"%s\"", drive->path);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
rmdir(drive->mnt);
|
rmdir(drive->mnt);
|
||||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
|
||||||
free(drive->mnt);
|
free(drive->mnt);
|
||||||
drive->mnt = NULL;
|
drive->mnt = NULL;
|
||||||
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
|
||||||
LOG_VERBOSE("Unmounted \"%s\"\n", drive->path);
|
LOG_VERBOSE("Unmounted \"%s\"\n", drive->path);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AsyncOperation *drive_unmount(Drive *drive, bool force) {
|
||||||
|
if (!drive->mnt) {
|
||||||
|
warnx("\"%s\" is not mounted", drive->path);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
const char *args[4];
|
||||||
|
if (force) {
|
||||||
|
args[0] = "umount";
|
||||||
|
args[1] = "-f";
|
||||||
|
args[2] = drive->mnt;
|
||||||
|
args[3] = NULL;
|
||||||
|
} else {
|
||||||
|
args[0] = "umount";
|
||||||
|
args[1] = drive->mnt;
|
||||||
|
args[2] = NULL;
|
||||||
|
}
|
||||||
|
AsyncOperation *op = async_operation_start(unmount_operation_poll, NULL,
|
||||||
|
drive, NULL, args[0],
|
||||||
|
(char *const *) args);
|
||||||
|
if (!op) {
|
||||||
|
warnx("%s: failed to unmount drive", drive->mnt);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return op;
|
||||||
|
}
|
||||||
|
23
src/drive.h
23
src/drive.h
@ -10,8 +10,10 @@
|
|||||||
#ifndef INCLUDED_DRIVE_H
|
#ifndef INCLUDED_DRIVE_H
|
||||||
#define INCLUDED_DRIVE_H
|
#define INCLUDED_DRIVE_H
|
||||||
|
|
||||||
#include <stddef.h>
|
#include "async.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char *name;
|
char *name;
|
||||||
@ -23,7 +25,8 @@ typedef struct {
|
|||||||
/*
|
/*
|
||||||
* Attempt to list all connected USB drives and their file systems. The list is
|
* Attempt to list all connected USB drives and their file systems. The list is
|
||||||
* sorted alphabetically.
|
* sorted alphabetically.
|
||||||
* Return: all connected USB (actually any serial drive) and their file systems.
|
* Return: all connected USB (actually any serial drive, so SATA too) and their
|
||||||
|
* file systems.
|
||||||
*/
|
*/
|
||||||
Drive *drive_list_usb_drives(size_t *count);
|
Drive *drive_list_usb_drives(size_t *count);
|
||||||
|
|
||||||
@ -40,21 +43,21 @@ void drive_free_drives(Drive *drives, size_t count);
|
|||||||
char *drive_guess_fs(const char *dev);
|
char *drive_guess_fs(const char *dev);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Create an MSDOS (FAT32) FS on DRIVE. DRIVE MUST NOT BE MOUNTED!
|
* Create an MSDOS (FAT32) FS on DRIVE.
|
||||||
* Return: true on success, false otherwise
|
* Return: an operation on success, NULL otherwise
|
||||||
*/
|
*/
|
||||||
bool drive_newfs_msdos(Drive *drive);
|
AsyncOperation *drive_newfs_msdos(Drive *drive);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Mount DRIVE onto a new temp. dir in /tmp. DRIVE MUST NOT BE MOUNTED!
|
* Mount DRIVE onto a new temp. dir in /tmp.
|
||||||
* Return: true on success, false otherwise
|
* Return: an operation on success, NULL otherwise
|
||||||
*/
|
*/
|
||||||
bool drive_mount(Drive *drive);
|
AsyncOperation *drive_mount(Drive *drive);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Unmount DRIVE. Force the operation if FORCE. Also rmdir(2) the mount point.
|
* Unmount DRIVE. Force the operation if FORCE. Also rmdir(2) the mount point.
|
||||||
* Return: true of success, false on failure.
|
* Return: an operation on success, NULL otherwise
|
||||||
*/
|
*/
|
||||||
bool drive_unmount(Drive *drive, bool force);
|
AsyncOperation *drive_unmount(Drive *drive, bool force);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -69,7 +69,8 @@ void start_update_thread(pthread_t *thread);
|
|||||||
// cross thread variables
|
// cross thread variables
|
||||||
sqlite3 *DATABASE;
|
sqlite3 *DATABASE;
|
||||||
pthread_mutex_t STAT_MUTEX;
|
pthread_mutex_t STAT_MUTEX;
|
||||||
uint32_t LAST_TEMP, LAST_HUMID;
|
// these values mean that we haven't yet read the temperature
|
||||||
|
uint32_t LAST_TEMP = UINT32_MAX - 1, LAST_HUMID = UINT32_MAX - 1;
|
||||||
_Atomic bool RUNNING = true;
|
_Atomic bool RUNNING = true;
|
||||||
/*
|
/*
|
||||||
* Lock the cross thread variables above.
|
* Lock the cross thread variables above.
|
||||||
@ -92,7 +93,8 @@ int main(int argc, char *const *argv) {
|
|||||||
setenv("TZ", GLOBAL_OPTS.timezone, true);
|
setenv("TZ", GLOBAL_OPTS.timezone, true);
|
||||||
NEED_CLEAR_TZ = true;
|
NEED_CLEAR_TZ = true;
|
||||||
} else {
|
} else {
|
||||||
LOG_VERBOSE("Config timezone option shadowed by local environment variable\n");
|
LOG_VERBOSE("Config timezone option shadowed by local environment "
|
||||||
|
"variable.\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tzset();
|
tzset();
|
||||||
@ -218,7 +220,6 @@ err(1, "failed to setup signal %s", #sig);\
|
|||||||
void setup_signals() {
|
void setup_signals() {
|
||||||
SIGNAL_SETUP_CHECKED(SIGTERM, exit_signal_callback);
|
SIGNAL_SETUP_CHECKED(SIGTERM, exit_signal_callback);
|
||||||
SIGNAL_SETUP_CHECKED(SIGINT, exit_signal_callback);
|
SIGNAL_SETUP_CHECKED(SIGINT, exit_signal_callback);
|
||||||
SIGNAL_SETUP_CHECKED(SIGUSR1, SIG_IGN); // used to kill export operations
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *CREATE_DB_TABLE_QUERY =
|
static const char *CREATE_DB_TABLE_QUERY =
|
||||||
|
@ -10,14 +10,11 @@
|
|||||||
#include "exportscreen.h"
|
#include "exportscreen.h"
|
||||||
|
|
||||||
#include <err.h>
|
#include <err.h>
|
||||||
#include <errno.h>
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <signal.h>
|
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <pthread.h>
|
|
||||||
#include <pthread_np.h>
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
static void export_screen_reset(ExportScreen *screen,
|
static void export_screen_reset(ExportScreen *screen,
|
||||||
SensorState *state) {
|
SensorState *state) {
|
||||||
@ -36,20 +33,25 @@ static void export_screen_reset(ExportScreen *screen,
|
|||||||
* Retrun: true if task is done, false otherwise
|
* Retrun: true if task is done, false otherwise
|
||||||
*/
|
*/
|
||||||
static bool export_check_bg_task(ExportScreen *screen) {
|
static bool export_check_bg_task(ExportScreen *screen) {
|
||||||
void *status;
|
switch (async_operation_poll(screen->current_op, false)) {
|
||||||
if (pthread_peekjoin_np(screen->backgroud_oper, &status) != EBUSY) {
|
case ASYNC_OPERATION_DONE:
|
||||||
// done
|
if (async_operation_failed(screen->current_op)) {
|
||||||
screen->bg_inited = false;
|
goto failed;
|
||||||
if ((intptr_t) status == EXPORT_SCREEN_FAILED) {
|
|
||||||
// error happened
|
|
||||||
warnx("background export operation failed");
|
|
||||||
}
|
}
|
||||||
screen->stage = (intptr_t) status;
|
|
||||||
screen->need_redraw = true;
|
screen->need_redraw = true;
|
||||||
|
screen->current_op = NULL;
|
||||||
return true;
|
return true;
|
||||||
}
|
case ASYNC_OPERATION_FAILED:
|
||||||
|
goto failed;
|
||||||
|
case ASYNC_OPERATION_WORKING:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
failed:
|
||||||
|
screen->stage = EXPORT_SCREEN_FAILED;
|
||||||
|
screen->need_redraw = true;
|
||||||
|
screen->current_op = NULL;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static const char *FORMAT_STRINGS[] = {
|
static const char *FORMAT_STRINGS[] = {
|
||||||
"SQLITE",
|
"SQLITE",
|
||||||
@ -150,15 +152,9 @@ static enum {
|
|||||||
} export_confirm_action(ExportScreen *screen,
|
} export_confirm_action(ExportScreen *screen,
|
||||||
SensorState *state,
|
SensorState *state,
|
||||||
const char *fmt, ...) {
|
const char *fmt, ...) {
|
||||||
int done_status = EXPORT_CONFIRM_CONTINUE;
|
int done_status;
|
||||||
if (screen->bg_inited && export_check_bg_task(screen)) {
|
|
||||||
// background task done
|
|
||||||
done_status = EXPORT_CONFIRM_CONTINUE;
|
|
||||||
goto confirm_done;
|
|
||||||
}
|
|
||||||
if (state->back_down || (state->sel_down && !screen->confirm_state)) {
|
if (state->back_down || (state->sel_down && !screen->confirm_state)) {
|
||||||
done_status = EXPORT_CONFIRM_NO;
|
done_status = EXPORT_CONFIRM_NO;
|
||||||
screen->need_redraw = true;
|
|
||||||
goto confirm_done;
|
goto confirm_done;
|
||||||
}
|
}
|
||||||
if (state->sel_down) {
|
if (state->sel_down) {
|
||||||
@ -191,6 +187,7 @@ static enum {
|
|||||||
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
|
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
|
||||||
LCD_DISPLAY_ON);
|
LCD_DISPLAY_ON);
|
||||||
screen->confirm_state = false;
|
screen->confirm_state = false;
|
||||||
|
screen->need_redraw = true;
|
||||||
return done_status;
|
return done_status;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,6 +200,8 @@ static bool export_show_message(ExportScreen *screen,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (screen->need_redraw) {
|
if (screen->need_redraw) {
|
||||||
|
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
|
||||||
|
LCD_DISPLAY_ON);
|
||||||
lcd_clear(state->lcd);
|
lcd_clear(state->lcd);
|
||||||
lcd_move_to(state->lcd, 0, 0);
|
lcd_move_to(state->lcd, 0, 0);
|
||||||
lcd_write_string(state->lcd, message);
|
lcd_write_string(state->lcd, message);
|
||||||
@ -211,97 +210,49 @@ static bool export_show_message(ExportScreen *screen,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static intptr_t export_newfs_action(ExportScreen *screen) {
|
|
||||||
if (drive_newfs_msdos(&screen->drives[screen->cur_drive])) {
|
|
||||||
return EXPORT_SCREEN_MOUNTING;
|
|
||||||
} else {
|
|
||||||
return EXPORT_SCREEN_FAILED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static intptr_t export_mount_action(ExportScreen *screen) {
|
|
||||||
Drive *drive = &screen->drives[screen->cur_drive];
|
|
||||||
if (drive_mount(drive)) {
|
|
||||||
struct stat statbuf;
|
|
||||||
char *path = NULL;
|
|
||||||
int exist;
|
|
||||||
pthread_cleanup_push(free, path);
|
|
||||||
path = export_build_cur_path(screen);
|
|
||||||
exist = stat(path, &statbuf);
|
|
||||||
screen->need_overwrite = exist == 0;
|
|
||||||
pthread_cleanup_pop(true); // free(path)
|
|
||||||
return screen->need_overwrite ? EXPORT_SCREEN_CONFIRM_OVERWRITE :
|
|
||||||
EXPORT_SCREEN_WRITING;
|
|
||||||
} else {
|
|
||||||
return EXPORT_SCREEN_FAILED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static intptr_t export_write_action(ExportScreen *screen) {
|
|
||||||
char *path = NULL;
|
|
||||||
intptr_t next_target = EXPORT_SCREEN_DONE;
|
|
||||||
pthread_cleanup_push(free, path);
|
|
||||||
path = export_build_cur_path(screen);
|
|
||||||
if (screen->need_overwrite && !recursive_rm(path)) {
|
|
||||||
next_target = EXPORT_SCREEN_FAILED;
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
LOG_VERBOSE("Exporting to \"%s\"\n", path);
|
|
||||||
switch (screen->format) {
|
|
||||||
case EXPORT_FORMAT_SQLITE:
|
|
||||||
if (!export_database(screen->db_cache, path)) {
|
|
||||||
warnx("export to sqlite3 db failed: \"%s\"", path);
|
|
||||||
next_target = EXPORT_SCREEN_FAILED;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case EXPORT_FORMAT_CSV:
|
|
||||||
if (!export_database_as_csv(screen->db_cache, path)) {
|
|
||||||
warnx("export to CSV failed: \"%s\"", path);
|
|
||||||
next_target = EXPORT_SCREEN_FAILED;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
warnx("unknown export format");
|
|
||||||
next_target = EXPORT_SCREEN_FAILED;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
LOG_VERBOSE("Export done\n");
|
|
||||||
cleanup:
|
|
||||||
pthread_cleanup_pop(true); // free(path)
|
|
||||||
drive_unmount(&screen->drives[screen->cur_drive], true);
|
|
||||||
return next_target;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char BACKGROUND_CHARS[] = {
|
static const char BACKGROUND_CHARS[] = {
|
||||||
'|',
|
'|',
|
||||||
'-'
|
'-'
|
||||||
};
|
};
|
||||||
#define NBACKGROUND_CHAR sizeof(BACKGROUND_CHARS)
|
#define NBACKGROUND_CHAR sizeof(BACKGROUND_CHARS)
|
||||||
|
|
||||||
static void export_do_background_action(ExportScreen *screen,
|
/*
|
||||||
SensorState *state,
|
* True if done, false otherwise
|
||||||
intptr_t(*action)(ExportScreen *screen)) {
|
*/
|
||||||
if (!screen->bg_inited) {
|
static bool export_do_background_action(ExportScreen *screen, SensorState *state) {
|
||||||
if (pthread_create(&screen->backgroud_oper, NULL,
|
if (!screen->show_cancel_screen && (state->down_down || state->up_down ||
|
||||||
(void*(*)(void *)) action, screen)) {
|
state->back_down || state->sel_down)) {
|
||||||
warnx("could not start background export operation");
|
screen->show_cancel_screen = true;
|
||||||
screen->stage = EXPORT_SCREEN_FAILED;
|
|
||||||
}
|
|
||||||
screen->bg_inited = true;
|
|
||||||
screen->working_char = 0;
|
|
||||||
}
|
|
||||||
if (state->down_down || state->up_down || state->back_down ||
|
|
||||||
state->sel_down) {
|
|
||||||
screen->last_cancel_stage = screen->stage;
|
|
||||||
screen->stage = EXPORT_SCREEN_CONFIRM_CANCEL;
|
|
||||||
screen->need_redraw = true;
|
screen->need_redraw = true;
|
||||||
return;
|
// we need to make sure that the confirm action screen ignores actions
|
||||||
|
// for this iteration of the UI loop
|
||||||
|
state->back_down = state->up_down =
|
||||||
|
state->sel_down = state->down_down = false;
|
||||||
|
}
|
||||||
|
if (!screen->current_op) {
|
||||||
|
screen->stage = EXPORT_SCREEN_FAILED;
|
||||||
|
screen->need_redraw = true;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
time_t now = time(NULL);
|
|
||||||
if (export_check_bg_task(screen)) {
|
if (export_check_bg_task(screen)) {
|
||||||
// bg task done
|
return true;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
if (screen->show_cancel_screen) {
|
||||||
|
int confirm_status = export_confirm_action(screen, state, "Really cancel?");
|
||||||
|
if (confirm_status == EXPORT_CONFIRM_YES) {
|
||||||
|
async_operation_free(screen->current_op);
|
||||||
|
screen->current_op = NULL;
|
||||||
|
AsyncOperation *op = drive_unmount(&screen->drives[screen->cur_drive], true);
|
||||||
|
async_operation_poll(op, true);
|
||||||
|
async_operation_free(op);
|
||||||
|
screen->stage = EXPORT_SCREEN_CANCELED;
|
||||||
|
screen->need_redraw = true;
|
||||||
|
} else if (confirm_status == EXPORT_CONFIRM_NO) {
|
||||||
|
screen->need_redraw = true;
|
||||||
|
screen->show_cancel_screen = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
time_t now = time(NULL);
|
||||||
if (screen->need_redraw || now != screen->last_char_change) {
|
if (screen->need_redraw || now != screen->last_char_change) {
|
||||||
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
|
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
|
||||||
LCD_DISPLAY_ON);
|
LCD_DISPLAY_ON);
|
||||||
@ -313,6 +264,62 @@ static void export_do_background_action(ExportScreen *screen,
|
|||||||
lcd_write_char(state->lcd, BACKGROUND_CHARS[screen->working_char++ % NBACKGROUND_CHAR]);
|
lcd_write_char(state->lcd, BACKGROUND_CHARS[screen->working_char++ % NBACKGROUND_CHAR]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void export_async_write_action(ExportScreen *screen) {
|
||||||
|
signal(SIGINT, SIG_DFL);
|
||||||
|
signal(SIGTERM, SIG_DFL);
|
||||||
|
char *path = export_build_cur_path(screen);
|
||||||
|
if (screen->need_overwrite && !recursive_rm(path)) {
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
LOG_VERBOSE("Exporting to \"%s\"\n", path);
|
||||||
|
switch (screen->format) {
|
||||||
|
case EXPORT_FORMAT_SQLITE:
|
||||||
|
if (!export_database(screen->db_cache, path)) {
|
||||||
|
errx(1, "export to sqlite3 db failed: \"%s\"", path);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EXPORT_FORMAT_CSV:
|
||||||
|
if (!export_database_as_csv(screen->db_cache, path)) {
|
||||||
|
errx(1, "export to CSV failed: \"%s\"", path);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
errx(1, "unknown export format");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
free(path);
|
||||||
|
LOG_VERBOSE("Export done\n");
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void export_handle_write(ExportScreen *screen, SensorState *state) {
|
||||||
|
if (screen->current_op) {
|
||||||
|
if (export_do_background_action(screen, state)) {
|
||||||
|
screen->need_redraw = true;
|
||||||
|
screen->stage = EXPORT_SCREEN_UNMOUNT;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// if we have no current op, start a new one
|
||||||
|
screen->show_cancel_screen = false;
|
||||||
|
pid_t pid = fork();
|
||||||
|
if (pid < 0) {
|
||||||
|
warn("failed to start write background task");
|
||||||
|
screen->need_redraw = true;
|
||||||
|
screen->stage = EXPORT_SCREEN_FAILED;
|
||||||
|
return;
|
||||||
|
} else if (pid == 0) {
|
||||||
|
// child
|
||||||
|
export_async_write_action(screen);
|
||||||
|
abort();
|
||||||
|
} else {
|
||||||
|
// parent
|
||||||
|
screen->current_op = async_operation_take(NULL, NULL, NULL, NULL, pid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static bool export_screen_dispatch(ExportScreen *screen,
|
static bool export_screen_dispatch(ExportScreen *screen,
|
||||||
SensorState *state) {
|
SensorState *state) {
|
||||||
@ -349,10 +356,31 @@ static bool export_screen_dispatch(ExportScreen *screen,
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case EXPORT_SCREEN_WRITE_NEWFS:
|
case EXPORT_SCREEN_WRITE_NEWFS:
|
||||||
export_do_background_action(screen, state, export_newfs_action);
|
if (!screen->current_op) {
|
||||||
|
screen->current_op = drive_newfs_msdos(cur_drive);
|
||||||
|
screen->show_cancel_screen = false;
|
||||||
|
}
|
||||||
|
if (export_do_background_action(screen, state)) {
|
||||||
|
screen->stage = EXPORT_SCREEN_MOUNTING;
|
||||||
|
screen->need_redraw = true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case EXPORT_SCREEN_MOUNTING:
|
case EXPORT_SCREEN_MOUNTING:
|
||||||
export_do_background_action(screen, state, export_mount_action);
|
if (!screen->current_op) {
|
||||||
|
screen->current_op = drive_mount(cur_drive);
|
||||||
|
screen->show_cancel_screen = false;
|
||||||
|
}
|
||||||
|
if (export_do_background_action(screen, state)) {
|
||||||
|
char *path = export_build_cur_path(screen);
|
||||||
|
struct stat statbuf;
|
||||||
|
if (stat(path, &statbuf) == 0) {
|
||||||
|
screen->stage = EXPORT_SCREEN_CONFIRM_OVERWRITE;
|
||||||
|
} else {
|
||||||
|
screen->stage = EXPORT_SCREEN_WRITING;
|
||||||
|
}
|
||||||
|
screen->need_redraw = true;
|
||||||
|
free(path);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case EXPORT_SCREEN_CONFIRM_OVERWRITE:
|
case EXPORT_SCREEN_CONFIRM_OVERWRITE:
|
||||||
confirm_status = export_confirm_action(screen, state, "Overwrite?");
|
confirm_status = export_confirm_action(screen, state, "Overwrite?");
|
||||||
@ -364,24 +392,21 @@ static bool export_screen_dispatch(ExportScreen *screen,
|
|||||||
screen->need_redraw = true;
|
screen->need_redraw = true;
|
||||||
// on the main thread so force unmount
|
// on the main thread so force unmount
|
||||||
// also, nothing should be writing, so kill it if it is
|
// also, nothing should be writing, so kill it if it is
|
||||||
drive_unmount(cur_drive, true);
|
AsyncOperation *op = drive_unmount(cur_drive, true);
|
||||||
|
async_operation_poll(op, true);
|
||||||
|
async_operation_free(op);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case EXPORT_SCREEN_WRITING:
|
case EXPORT_SCREEN_WRITING:
|
||||||
export_do_background_action(screen, state, export_write_action);
|
export_handle_write(screen, state);
|
||||||
break;
|
break;
|
||||||
case EXPORT_SCREEN_CONFIRM_CANCEL:
|
case EXPORT_SCREEN_UNMOUNT:
|
||||||
confirm_status = export_confirm_action(screen, state, "Really cancel?");
|
if (!screen->current_op) {
|
||||||
if (confirm_status == EXPORT_CONFIRM_YES) {
|
screen->current_op = drive_unmount(cur_drive, false);
|
||||||
kill(0, SIGUSR1);
|
screen->show_cancel_screen = false;
|
||||||
pthread_cancel(screen->backgroud_oper);
|
}
|
||||||
pthread_detach(screen->backgroud_oper);
|
if (export_do_background_action(screen, state)) {
|
||||||
drive_unmount(cur_drive, true);
|
screen->stage = EXPORT_SCREEN_DONE;
|
||||||
screen->bg_inited = false;
|
|
||||||
screen->stage = EXPORT_SCREEN_CANCELED;
|
|
||||||
screen->need_redraw = true;
|
|
||||||
} else if (confirm_status == EXPORT_CONFIRM_NO) {
|
|
||||||
screen->stage = screen->last_cancel_stage;
|
|
||||||
screen->need_redraw = true;
|
screen->need_redraw = true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -398,10 +423,7 @@ static bool export_screen_dispatch(ExportScreen *screen,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void export_screen_cleanup(ExportScreen *screen) {
|
static void export_screen_cleanup(ExportScreen *screen) {
|
||||||
if (screen->bg_inited) {
|
async_operation_free(screen->current_op);
|
||||||
pthread_cancel(screen->backgroud_oper);
|
|
||||||
pthread_detach(screen->backgroud_oper);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ExportScreen *export_screen_new() {
|
ExportScreen *export_screen_new() {
|
||||||
@ -412,5 +434,6 @@ ExportScreen *export_screen_new() {
|
|||||||
s->read_drives = true;
|
s->read_drives = true;
|
||||||
s->stage = EXPORT_SCREEN_FORMAT;
|
s->stage = EXPORT_SCREEN_FORMAT;
|
||||||
s->format = EXPORT_FORMAT_SQLITE;
|
s->format = EXPORT_FORMAT_SQLITE;
|
||||||
|
s->current_op = NULL;
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
@ -11,11 +11,11 @@
|
|||||||
#define INCLUDED_EXPORTSCREEN_H
|
#define INCLUDED_EXPORTSCREEN_H
|
||||||
|
|
||||||
#include "screen.h"
|
#include "screen.h"
|
||||||
|
#include "../async.h"
|
||||||
#include "../drive.h"
|
#include "../drive.h"
|
||||||
|
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <stdatomic.h>
|
|
||||||
#include <sqlite3.h>
|
#include <sqlite3.h>
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@ -28,8 +28,8 @@ typedef struct {
|
|||||||
EXPORT_SCREEN_MOUNTING,
|
EXPORT_SCREEN_MOUNTING,
|
||||||
EXPORT_SCREEN_CONFIRM_OVERWRITE,
|
EXPORT_SCREEN_CONFIRM_OVERWRITE,
|
||||||
EXPORT_SCREEN_WRITING,
|
EXPORT_SCREEN_WRITING,
|
||||||
|
EXPORT_SCREEN_UNMOUNT,
|
||||||
EXPORT_SCREEN_DONE,
|
EXPORT_SCREEN_DONE,
|
||||||
EXPORT_SCREEN_CONFIRM_CANCEL,
|
|
||||||
EXPORT_SCREEN_CANCELED,
|
EXPORT_SCREEN_CANCELED,
|
||||||
EXPORT_SCREEN_FAILED,
|
EXPORT_SCREEN_FAILED,
|
||||||
EXPORT_SCREEN_NO_DRIVES,
|
EXPORT_SCREEN_NO_DRIVES,
|
||||||
@ -47,13 +47,13 @@ typedef struct {
|
|||||||
size_t ndrive;
|
size_t ndrive;
|
||||||
size_t cur_drive;
|
size_t cur_drive;
|
||||||
int confirm_state;
|
int confirm_state;
|
||||||
bool bg_inited;
|
|
||||||
pthread_t backgroud_oper;
|
|
||||||
int working_char;
|
int working_char;
|
||||||
time_t last_char_change;
|
time_t last_char_change;
|
||||||
bool need_overwrite;
|
bool need_overwrite;
|
||||||
sqlite3 *db_cache;
|
sqlite3 *db_cache;
|
||||||
int last_cancel_stage;
|
int last_cancel_stage;
|
||||||
|
AsyncOperation *current_op;
|
||||||
|
bool show_cancel_screen;
|
||||||
} ExportScreen;
|
} ExportScreen;
|
||||||
|
|
||||||
ExportScreen *export_screen_new(void);
|
ExportScreen *export_screen_new(void);
|
||||||
|
@ -152,6 +152,8 @@ static bool stats_screen_dispatch(StatsScreen *screen, SensorState *state) {
|
|||||||
int cur_len;
|
int cur_len;
|
||||||
if (state->temp == UINT32_MAX || state->humid == UINT32_MAX) {
|
if (state->temp == UINT32_MAX || state->humid == UINT32_MAX) {
|
||||||
cur_len = snprintf(buff, sizeof(buff), "ERROR! ");
|
cur_len = snprintf(buff, sizeof(buff), "ERROR! ");
|
||||||
|
} else if (state->temp == UINT_MAX - 1 || state->humid == UINT32_MAX - 1) {
|
||||||
|
cur_len = snprintf(buff, sizeof(buff), "LOADING! ");
|
||||||
} else {
|
} else {
|
||||||
cur_len = snprintf(buff, sizeof(buff), "%-4.1f%c %3" PRIu32 "%% ",
|
cur_len = snprintf(buff, sizeof(buff), "%-4.1f%c %3" PRIu32 "%% ",
|
||||||
convert_temperature(state->temp),
|
convert_temperature(state->temp),
|
||||||
|
98
src/util.c
98
src/util.c
@ -17,9 +17,8 @@
|
|||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <pthread.h>
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <stdatomic.h>
|
#include <sys/signal.h>
|
||||||
|
|
||||||
const char *PERIOD_LABELS[] = {
|
const char *PERIOD_LABELS[] = {
|
||||||
"HOUR",
|
"HOUR",
|
||||||
@ -414,12 +413,6 @@ char *pad_humid_str(int humid, char *buf, size_t buf_size) {
|
|||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void close_dir_if_set(void *dir) {
|
|
||||||
if (dir) {
|
|
||||||
closedir(dir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool recursive_rm_at(int dfd, const char *path) {
|
bool recursive_rm_at(int dfd, const char *path) {
|
||||||
if (dfd != AT_FDCWD) {
|
if (dfd != AT_FDCWD) {
|
||||||
LOG_VERBOSE("Recursive delete: \"%s\"\n", path);
|
LOG_VERBOSE("Recursive delete: \"%s\"\n", path);
|
||||||
@ -432,18 +425,12 @@ bool recursive_rm_at(int dfd, const char *path) {
|
|||||||
int flag = 0;
|
int flag = 0;
|
||||||
if (S_ISDIR(statbuf.st_mode)) {
|
if (S_ISDIR(statbuf.st_mode)) {
|
||||||
flag = AT_REMOVEDIR;
|
flag = AT_REMOVEDIR;
|
||||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
|
||||||
int fd = openat(dfd, path, O_DIRECTORY | O_RDONLY);
|
int fd = openat(dfd, path, O_DIRECTORY | O_RDONLY);
|
||||||
if (fd < 0) {
|
if (fd < 0) {
|
||||||
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
|
||||||
warn("open \"%s\"", path);
|
warn("open \"%s\"", path);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
DIR *dir = NULL;
|
DIR *dir = fdopendir(fd);
|
||||||
bool error = false;
|
|
||||||
pthread_cleanup_push(close_dir_if_set, dir);
|
|
||||||
dir = fdopendir(fd);
|
|
||||||
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
|
||||||
if (!dir) {
|
if (!dir) {
|
||||||
close(fd);
|
close(fd);
|
||||||
warn("fdopendir \"%s\"", path);
|
warn("fdopendir \"%s\"", path);
|
||||||
@ -453,15 +440,11 @@ bool recursive_rm_at(int dfd, const char *path) {
|
|||||||
while ((sd = readdir(dir))) {
|
while ((sd = readdir(dir))) {
|
||||||
if (strcmp(sd->d_name, ".") != 0 && strcmp(sd->d_name, "..") != 0 &&
|
if (strcmp(sd->d_name, ".") != 0 && strcmp(sd->d_name, "..") != 0 &&
|
||||||
!recursive_rm_at(dirfd(dir), sd->d_name)) {
|
!recursive_rm_at(dirfd(dir), sd->d_name)) {
|
||||||
error = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pthread_cleanup_pop(true);
|
|
||||||
if (error) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
closedir(dir);
|
||||||
|
}
|
||||||
if (unlinkat(dfd, path, flag)< 0) {
|
if (unlinkat(dfd, path, flag)< 0) {
|
||||||
warn("remove \"%s\"", path);
|
warn("remove \"%s\"", path);
|
||||||
return false;
|
return false;
|
||||||
@ -474,48 +457,31 @@ bool recursive_rm(const char *path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool export_database(sqlite3 *db, const char *dest) {
|
bool export_database(sqlite3 *db, const char *dest) {
|
||||||
bool status = true;
|
|
||||||
sqlite3 *new_db = NULL;
|
sqlite3 *new_db = NULL;
|
||||||
sqlite3_backup *oper = NULL;
|
sqlite3_backup *oper = NULL;
|
||||||
pthread_cleanup_push((void(*)(void*))sqlite3_close, new_db);
|
|
||||||
pthread_cleanup_push((void(*)(void*))sqlite3_backup_finish, oper);
|
|
||||||
int res = sqlite3_open(dest, &new_db);
|
int res = sqlite3_open(dest, &new_db);
|
||||||
if (res != SQLITE_OK) {
|
if (res != SQLITE_OK) {
|
||||||
warnx("could not open new db: \"%s\": %s", dest,
|
warnx("could not open new db: \"%s\": %s", dest,
|
||||||
sqlite3_errstr(res));
|
sqlite3_errstr(res));
|
||||||
status = false;
|
return false;
|
||||||
goto cleanup;
|
|
||||||
}
|
}
|
||||||
oper = sqlite3_backup_init(new_db, "main", db, "main");
|
oper = sqlite3_backup_init(new_db, "main", db, "main");
|
||||||
if (!oper) {
|
if (!oper) {
|
||||||
warnx("could not start backup: \"%s\": %s", dest,
|
warnx("could not start backup: \"%s\": %s", dest,
|
||||||
sqlite3_errstr(res));
|
sqlite3_errstr(res));
|
||||||
status = false;
|
return false;
|
||||||
goto cleanup;
|
|
||||||
}
|
}
|
||||||
sqlite3_backup_step(oper, -1);
|
sqlite3_backup_step(oper, -1);
|
||||||
cleanup:
|
sqlite3_backup_finish(oper);
|
||||||
pthread_cleanup_pop(true); // cleanup oper
|
sqlite3_close(new_db);
|
||||||
pthread_cleanup_pop(true); // cleanup new_db
|
return true;
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void cleanup_file_obj(void *obj) {
|
|
||||||
if (obj) {
|
|
||||||
fclose(obj);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool export_database_as_csv(sqlite3 *db, const char *dest) {
|
bool export_database_as_csv(sqlite3 *db, const char *dest) {
|
||||||
bool status = true;
|
FILE *outfile = fopen(dest, "w");
|
||||||
FILE *outfile = NULL;
|
|
||||||
pthread_cleanup_push((void(*)(void *))sqlite3_reset, EXPORT_CSV_QUERY);
|
|
||||||
pthread_cleanup_push(cleanup_file_obj, outfile);
|
|
||||||
outfile = fopen(dest, "w");
|
|
||||||
if (!outfile) {
|
if (!outfile) {
|
||||||
warn("fopen \"%s\"", dest);
|
warn("fopen \"%s\"", dest);
|
||||||
status = false;
|
return false;
|
||||||
goto cleanup;
|
|
||||||
}
|
}
|
||||||
int res;
|
int res;
|
||||||
while ((res = sqlite3_step(EXPORT_CSV_QUERY)) == SQLITE_ROW) {
|
while ((res = sqlite3_step(EXPORT_CSV_QUERY)) == SQLITE_ROW) {
|
||||||
@ -526,14 +492,11 @@ bool export_database_as_csv(sqlite3 *db, const char *dest) {
|
|||||||
if (res != SQLITE_DONE) {
|
if (res != SQLITE_DONE) {
|
||||||
warnx("CSV export to \"%s\" faield: %s\n", dest,
|
warnx("CSV export to \"%s\" faield: %s\n", dest,
|
||||||
sqlite3_errstr(res));
|
sqlite3_errstr(res));
|
||||||
status = false;
|
return false;
|
||||||
goto cleanup; // readability
|
|
||||||
}
|
}
|
||||||
fflush(outfile);
|
sqlite3_reset(EXPORT_CSV_QUERY);
|
||||||
cleanup:
|
fclose(outfile);
|
||||||
pthread_cleanup_pop(true); // reset query
|
return true;
|
||||||
pthread_cleanup_pop(true); // close file
|
|
||||||
return status;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool ensure_dir_exists(const char *path, mode_t mode) {
|
static bool ensure_dir_exists(const char *path, mode_t mode) {
|
||||||
@ -556,7 +519,7 @@ static bool ensure_dir_exists(const char *path, mode_t mode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool mkdirs(const char *path, mode_t mode) {
|
bool mkdirs(const char *path, mode_t mode) {
|
||||||
LOG_VERBOSE("Creating directory: \"%s\"\n", path)
|
LOG_VERBOSE("Creating directory: \"%s\"\n", path);
|
||||||
char *copy = strdup_checked(path);
|
char *copy = strdup_checked(path);
|
||||||
char *work_str = copy, *token;
|
char *work_str = copy, *token;
|
||||||
while ((token = strsep(&work_str, "/"))) {
|
while ((token = strsep(&work_str, "/"))) {
|
||||||
@ -576,3 +539,32 @@ void request_restart() {
|
|||||||
RUNNING = false;
|
RUNNING = false;
|
||||||
SHOULD_RESTART = true;
|
SHOULD_RESTART = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool timed_wait(pid_t pid, int *out_status, useconds_t micro) {
|
||||||
|
struct timespec waittime = {
|
||||||
|
.tv_sec = micro / 1000000,
|
||||||
|
.tv_nsec = micro % 1000000,
|
||||||
|
};
|
||||||
|
int status;
|
||||||
|
struct timespec start, now, tmp;
|
||||||
|
clock_gettime(CLOCK_UPTIME, &start);
|
||||||
|
while (true) {
|
||||||
|
if (waitpid(pid, &status, WNOHANG) < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (WIFEXITED(status) || WIFSIGNALED(status)) {
|
||||||
|
if (out_status) {
|
||||||
|
*out_status = status;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
clock_gettime(CLOCK_UPTIME, &now);
|
||||||
|
timespecsub(&now, &start, &tmp);
|
||||||
|
if (timespeccmp(&tmp, &waittime, >=)) {
|
||||||
|
if (out_status) {
|
||||||
|
*out_status = 0;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <libgpio.h>
|
#include <libgpio.h>
|
||||||
#include <sqlite3.h>
|
#include <sqlite3.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
|
||||||
#define _STRINGIFY_LIT(s) (#s)
|
#define _STRINGIFY_LIT(s) (#s)
|
||||||
#define STRINGIFY(v) _STRINGIFY_LIT(v)
|
#define STRINGIFY(v) _STRINGIFY_LIT(v)
|
||||||
@ -254,5 +255,12 @@ bool mkdirs(const char *path, mode_t mode);
|
|||||||
* re-initialize configuration values.
|
* re-initialize configuration values.
|
||||||
*/
|
*/
|
||||||
void request_restart(void);
|
void request_restart(void);
|
||||||
|
/*
|
||||||
|
* waitpid(2) for PID for MICRO microseconds. If STATUS is not NULL, store the
|
||||||
|
* exit status there. Use the various WIF* macros to test the status. This
|
||||||
|
* function sets errno to the errno value of waitpid(2) on error.
|
||||||
|
* Return: true if successful, false otherwise and the error is in errno
|
||||||
|
*/
|
||||||
|
bool timed_wait(pid_t pid, int *out_status, useconds_t micro);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
Loading…
Reference in New Issue
Block a user