Merge branch 'main' of git.zander.im:Zander671/rpi4b-temp-humidity

This commit is contained in:
Alexander Rosenberg 2024-04-13 16:01:21 -07:00
commit 54c23fc67b
Signed by: Zander671
GPG Key ID: 5FD0394ADBD72730
12 changed files with 524 additions and 326 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@ bin/
compile_commands.json compile_commands.json
.cache/ .cache/
config.conf config.conf
.clangd

View File

@ -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
View 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
View 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

View File

@ -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;
} }
}
bool drive_mount(Drive *drive) {
bool status = true;
char *name = NULL;
pthread_cleanup_push(free, name);
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
name = strdup_checked("/tmp/mntXXXXXX");
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
if (!mkdtemp(name)) {
warn("failed to create temp mount point");
status = false;
goto cleanup;
}
pid_t pid = fork();
if (pid < 0) {
warn("fork(2) failed");
status = false;
goto cleanup;
} else if (pid == 0) {
// child
if (!GLOBAL_OPTS.verbose) {
freopen("/dev/null", "w", stdout);
}
execl("/sbin/mount", "/sbin/mount", "-t", drive->fs, drive->path, name, NULL);
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) {
if (!drive->mnt) {
warnx("\"%s\" is not mounted", drive->path);
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);
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
free(drive->mnt);
drive->mnt = NULL;
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
LOG_VERBOSE("Unmounted \"%s\"\n", drive->path);
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;
}
static bool mount_operation_poll(AsyncOperation *op) {
Drive *drive = op->data1;
if (op->done && !async_operation_failed(op)) {
// steal the mount point string
drive->mnt = op->data2;
op->data2 = 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)) {
warn("failed to create temp mount point");
return NULL;
}
char * const args[] = { "mount", "-t", drive->fs, drive->path, name, NULL };
AsyncOperation *op = async_operation_start(mount_operation_poll,
mount_operation_cleanup,
drive, name, args[0], args);
if (!op) {
warnx("%s: failed to start mount process", drive->path);
free(name);
return NULL;
}
return op;
}
bool unmount_operation_poll(AsyncOperation *op) {
if (op->done) {
Drive *drive = op->data1;
rmdir(drive->mnt);
free(drive->mnt);
drive->mnt = NULL;
LOG_VERBOSE("Unmounted \"%s\"\n", drive->path);
}
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;
}

View File

@ -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

View File

@ -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 =

View File

@ -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,19 +33,24 @@ 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[] = {
@ -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) {
@ -188,9 +184,10 @@ static enum {
} }
return EXPORT_CONFIRM_CONTINUE; return EXPORT_CONFIRM_CONTINUE;
confirm_done: confirm_done:
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,106 +210,114 @@ 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;
}
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) {
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
LCD_DISPLAY_ON);
screen->last_char_change = now;
screen->need_redraw = false;
lcd_clear(state->lcd);
lcd_move_to(state->lcd, 0, 0);
lcd_write_string(state->lcd, "Working");
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; return;
} }
if (screen->need_redraw || now != screen->last_char_change) { // if we have no current op, start a new one
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF, screen->show_cancel_screen = false;
LCD_DISPLAY_ON); pid_t pid = fork();
screen->last_char_change = now; if (pid < 0) {
screen->need_redraw = false; warn("failed to start write background task");
lcd_clear(state->lcd); screen->need_redraw = true;
lcd_move_to(state->lcd, 0, 0); screen->stage = EXPORT_SCREEN_FAILED;
lcd_write_string(state->lcd, "Working"); return;
lcd_write_char(state->lcd, BACKGROUND_CHARS[screen->working_char++ % NBACKGROUND_CHAR]); } else if (pid == 0) {
// child
export_async_write_action(screen);
abort();
} else {
// parent
screen->current_op = async_operation_take(NULL, NULL, NULL, NULL, pid);
} }
} }
@ -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;
} }

View File

@ -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);

View File

@ -151,7 +151,9 @@ static bool stats_screen_dispatch(StatsScreen *screen, SensorState *state) {
char buff[17]; char buff[17];
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),

View File

@ -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,14 +440,10 @@ 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; return false;
break;
} }
} }
pthread_cleanup_pop(true); closedir(dir);
if (error) {
return false;
}
} }
if (unlinkat(dfd, path, flag)< 0) { if (unlinkat(dfd, path, flag)< 0) {
warn("remove \"%s\"", path); warn("remove \"%s\"", path);
@ -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;
}
}
}

View File

@ -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