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
.cache/
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/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/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
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/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/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/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/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/async.h
.if "${DEFAULT_TEMP_UNIT}" != "F" && "${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 <sys/param.h>
#include <sys/mount.h>
#include <pthread.h>
static int compare_drives(const Drive *d1, const Drive *d2) {
return strcmp(d1->name, d2->name);
@ -62,29 +61,16 @@ void drive_free_drives(Drive *drives, size_t count) {
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 *result_fs = NULL;
int ctop[2] = {0, 0};
pthread_cleanup_push(close_pipe, ctop);
pthread_cleanup_push(free, result_fs);
if (pipe(ctop) < 0) {
warn("failed to create pipe");
goto cleanup;
return NULL;
}
pid_t pid = fork();
if (pid < 0) {
warn("fork(2) failed");
goto cleanup;
return NULL;
} else if (pid == 0) {
// child
close(ctop[0]); // close read end
@ -104,125 +90,119 @@ char *drive_guess_fs(const char *dev) {
int status;
if (waitpid(pid, &status, 0) < 0) {
warn("waiting for child failed");
goto cleanup;
return NULL;
}
if (WEXITSTATUS(status) != 0) {
// no error, we probably just don't know the FS type
goto cleanup;
return NULL;
}
char read_buf[32];
ssize_t read_count = read(ctop[0], read_buf, 32);
if (read_count < 0) {
warn("reading from pipe failed");
goto cleanup;
return NULL;
}
read_buf[read_count - 1] = '\0';
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
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;
return strdup_checked(read_buf);
}
bool drive_newfs_msdos(Drive *drive) {
pid_t pid = fork();
if (pid < 0) {
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);
static bool newfs_operation_poll(AsyncOperation *op) {
if (op->done && !async_operation_failed(op)) {
Drive *drive = op->data1;
free(drive->fs);
drive->fs = strdup_checked("msdosfs");
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
}
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) {
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);
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");
status = false;
goto cleanup;
return NULL;
}
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);
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;
}
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;
return op;
}
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;
}
bool unmount_operation_poll(AsyncOperation *op) {
if (op->done) {
Drive *drive = op->data1;
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;
}
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
#define INCLUDED_DRIVE_H
#include <stddef.h>
#include "async.h"
#include <stdbool.h>
#include <sys/types.h>
typedef struct {
char *name;
@ -23,7 +25,8 @@ typedef struct {
/*
* Attempt to list all connected USB drives and their file systems. The list is
* 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);
@ -40,21 +43,21 @@ void drive_free_drives(Drive *drives, size_t count);
char *drive_guess_fs(const char *dev);
/*
* Create an MSDOS (FAT32) FS on DRIVE. DRIVE MUST NOT BE MOUNTED!
* Return: true on success, false otherwise
* Create an MSDOS (FAT32) FS on DRIVE.
* 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!
* Return: true on success, false otherwise
* Mount DRIVE onto a new temp. dir in /tmp.
* 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.
* 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

View File

@ -69,7 +69,8 @@ void start_update_thread(pthread_t *thread);
// cross thread variables
sqlite3 *DATABASE;
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;
/*
* Lock the cross thread variables above.
@ -92,7 +93,8 @@ int main(int argc, char *const *argv) {
setenv("TZ", GLOBAL_OPTS.timezone, true);
NEED_CLEAR_TZ = true;
} else {
LOG_VERBOSE("Config timezone option shadowed by local environment variable\n");
LOG_VERBOSE("Config timezone option shadowed by local environment "
"variable.\n");
}
}
tzset();
@ -218,7 +220,6 @@ err(1, "failed to setup signal %s", #sig);\
void setup_signals() {
SIGNAL_SETUP_CHECKED(SIGTERM, 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 =

View File

@ -10,14 +10,11 @@
#include "exportscreen.h"
#include <err.h>
#include <errno.h>
#include <sys/types.h>
#include <signal.h>
#include <stdarg.h>
#include <pthread.h>
#include <pthread_np.h>
#include <unistd.h>
#include <sys/stat.h>
#include <signal.h>
static void export_screen_reset(ExportScreen *screen,
SensorState *state) {
@ -36,20 +33,25 @@ static void export_screen_reset(ExportScreen *screen,
* Retrun: true if task is done, false otherwise
*/
static bool export_check_bg_task(ExportScreen *screen) {
void *status;
if (pthread_peekjoin_np(screen->backgroud_oper, &status) != EBUSY) {
// done
screen->bg_inited = false;
if ((intptr_t) status == EXPORT_SCREEN_FAILED) {
// error happened
warnx("background export operation failed");
switch (async_operation_poll(screen->current_op, false)) {
case ASYNC_OPERATION_DONE:
if (async_operation_failed(screen->current_op)) {
goto failed;
}
screen->stage = (intptr_t) status;
screen->need_redraw = true;
screen->current_op = NULL;
return true;
}
case ASYNC_OPERATION_FAILED:
goto failed;
case ASYNC_OPERATION_WORKING:
return false;
}
failed:
screen->stage = EXPORT_SCREEN_FAILED;
screen->need_redraw = true;
screen->current_op = NULL;
return true;
}
static const char *FORMAT_STRINGS[] = {
"SQLITE",
@ -150,15 +152,9 @@ static enum {
} export_confirm_action(ExportScreen *screen,
SensorState *state,
const char *fmt, ...) {
int done_status = EXPORT_CONFIRM_CONTINUE;
if (screen->bg_inited && export_check_bg_task(screen)) {
// background task done
done_status = EXPORT_CONFIRM_CONTINUE;
goto confirm_done;
}
int done_status;
if (state->back_down || (state->sel_down && !screen->confirm_state)) {
done_status = EXPORT_CONFIRM_NO;
screen->need_redraw = true;
goto confirm_done;
}
if (state->sel_down) {
@ -191,6 +187,7 @@ static enum {
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
LCD_DISPLAY_ON);
screen->confirm_state = false;
screen->need_redraw = true;
return done_status;
}
@ -203,6 +200,8 @@ static bool export_show_message(ExportScreen *screen,
return true;
}
if (screen->need_redraw) {
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
LCD_DISPLAY_ON);
lcd_clear(state->lcd);
lcd_move_to(state->lcd, 0, 0);
lcd_write_string(state->lcd, message);
@ -211,97 +210,49 @@ static bool export_show_message(ExportScreen *screen,
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[] = {
'|',
'-'
};
#define NBACKGROUND_CHAR sizeof(BACKGROUND_CHARS)
static void export_do_background_action(ExportScreen *screen,
SensorState *state,
intptr_t(*action)(ExportScreen *screen)) {
if (!screen->bg_inited) {
if (pthread_create(&screen->backgroud_oper, NULL,
(void*(*)(void *)) action, screen)) {
warnx("could not start background export operation");
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;
/*
* True if done, false otherwise
*/
static bool export_do_background_action(ExportScreen *screen, SensorState *state) {
if (!screen->show_cancel_screen && (state->down_down || state->up_down ||
state->back_down || state->sel_down)) {
screen->show_cancel_screen = 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)) {
// bg task done
return;
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);
@ -313,6 +264,62 @@ static void export_do_background_action(ExportScreen *screen,
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,
SensorState *state) {
@ -349,10 +356,31 @@ static bool export_screen_dispatch(ExportScreen *screen,
}
break;
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;
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;
case EXPORT_SCREEN_CONFIRM_OVERWRITE:
confirm_status = export_confirm_action(screen, state, "Overwrite?");
@ -364,24 +392,21 @@ static bool export_screen_dispatch(ExportScreen *screen,
screen->need_redraw = true;
// on the main thread so force unmount
// 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;
case EXPORT_SCREEN_WRITING:
export_do_background_action(screen, state, export_write_action);
export_handle_write(screen, state);
break;
case EXPORT_SCREEN_CONFIRM_CANCEL:
confirm_status = export_confirm_action(screen, state, "Really cancel?");
if (confirm_status == EXPORT_CONFIRM_YES) {
kill(0, SIGUSR1);
pthread_cancel(screen->backgroud_oper);
pthread_detach(screen->backgroud_oper);
drive_unmount(cur_drive, true);
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;
case EXPORT_SCREEN_UNMOUNT:
if (!screen->current_op) {
screen->current_op = drive_unmount(cur_drive, false);
screen->show_cancel_screen = false;
}
if (export_do_background_action(screen, state)) {
screen->stage = EXPORT_SCREEN_DONE;
screen->need_redraw = true;
}
break;
@ -398,10 +423,7 @@ static bool export_screen_dispatch(ExportScreen *screen,
}
static void export_screen_cleanup(ExportScreen *screen) {
if (screen->bg_inited) {
pthread_cancel(screen->backgroud_oper);
pthread_detach(screen->backgroud_oper);
}
async_operation_free(screen->current_op);
}
ExportScreen *export_screen_new() {
@ -412,5 +434,6 @@ ExportScreen *export_screen_new() {
s->read_drives = true;
s->stage = EXPORT_SCREEN_FORMAT;
s->format = EXPORT_FORMAT_SQLITE;
s->current_op = NULL;
return s;
}

View File

@ -11,11 +11,11 @@
#define INCLUDED_EXPORTSCREEN_H
#include "screen.h"
#include "../async.h"
#include "../drive.h"
#include <pthread.h>
#include <time.h>
#include <stdatomic.h>
#include <sqlite3.h>
typedef struct {
@ -28,8 +28,8 @@ typedef struct {
EXPORT_SCREEN_MOUNTING,
EXPORT_SCREEN_CONFIRM_OVERWRITE,
EXPORT_SCREEN_WRITING,
EXPORT_SCREEN_UNMOUNT,
EXPORT_SCREEN_DONE,
EXPORT_SCREEN_CONFIRM_CANCEL,
EXPORT_SCREEN_CANCELED,
EXPORT_SCREEN_FAILED,
EXPORT_SCREEN_NO_DRIVES,
@ -47,13 +47,13 @@ typedef struct {
size_t ndrive;
size_t cur_drive;
int confirm_state;
bool bg_inited;
pthread_t backgroud_oper;
int working_char;
time_t last_char_change;
bool need_overwrite;
sqlite3 *db_cache;
int last_cancel_stage;
AsyncOperation *current_op;
bool show_cancel_screen;
} ExportScreen;
ExportScreen *export_screen_new(void);

View File

@ -152,6 +152,8 @@ static bool stats_screen_dispatch(StatsScreen *screen, SensorState *state) {
int cur_len;
if (state->temp == UINT32_MAX || state->humid == UINT32_MAX) {
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 {
cur_len = snprintf(buff, sizeof(buff), "%-4.1f%c %3" PRIu32 "%% ",
convert_temperature(state->temp),

View File

@ -17,9 +17,8 @@
#include <dirent.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <errno.h>
#include <stdatomic.h>
#include <sys/signal.h>
const char *PERIOD_LABELS[] = {
"HOUR",
@ -414,12 +413,6 @@ char *pad_humid_str(int humid, char *buf, size_t buf_size) {
return buf;
}
static void close_dir_if_set(void *dir) {
if (dir) {
closedir(dir);
}
}
bool recursive_rm_at(int dfd, const char *path) {
if (dfd != AT_FDCWD) {
LOG_VERBOSE("Recursive delete: \"%s\"\n", path);
@ -432,18 +425,12 @@ bool recursive_rm_at(int dfd, const char *path) {
int flag = 0;
if (S_ISDIR(statbuf.st_mode)) {
flag = AT_REMOVEDIR;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
int fd = openat(dfd, path, O_DIRECTORY | O_RDONLY);
if (fd < 0) {
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
warn("open \"%s\"", path);
return false;
}
DIR *dir = NULL;
bool error = false;
pthread_cleanup_push(close_dir_if_set, dir);
dir = fdopendir(fd);
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
DIR *dir = fdopendir(fd);
if (!dir) {
close(fd);
warn("fdopendir \"%s\"", path);
@ -453,15 +440,11 @@ bool recursive_rm_at(int dfd, const char *path) {
while ((sd = readdir(dir))) {
if (strcmp(sd->d_name, ".") != 0 && strcmp(sd->d_name, "..") != 0 &&
!recursive_rm_at(dirfd(dir), sd->d_name)) {
error = true;
break;
}
}
pthread_cleanup_pop(true);
if (error) {
return false;
}
}
closedir(dir);
}
if (unlinkat(dfd, path, flag)< 0) {
warn("remove \"%s\"", path);
return false;
@ -474,48 +457,31 @@ bool recursive_rm(const char *path) {
}
bool export_database(sqlite3 *db, const char *dest) {
bool status = true;
sqlite3 *new_db = 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);
if (res != SQLITE_OK) {
warnx("could not open new db: \"%s\": %s", dest,
sqlite3_errstr(res));
status = false;
goto cleanup;
return false;
}
oper = sqlite3_backup_init(new_db, "main", db, "main");
if (!oper) {
warnx("could not start backup: \"%s\": %s", dest,
sqlite3_errstr(res));
status = false;
goto cleanup;
return false;
}
sqlite3_backup_step(oper, -1);
cleanup:
pthread_cleanup_pop(true); // cleanup oper
pthread_cleanup_pop(true); // cleanup new_db
return status;
}
static void cleanup_file_obj(void *obj) {
if (obj) {
fclose(obj);
}
sqlite3_backup_finish(oper);
sqlite3_close(new_db);
return true;
}
bool export_database_as_csv(sqlite3 *db, const char *dest) {
bool status = true;
FILE *outfile = NULL;
pthread_cleanup_push((void(*)(void *))sqlite3_reset, EXPORT_CSV_QUERY);
pthread_cleanup_push(cleanup_file_obj, outfile);
outfile = fopen(dest, "w");
FILE *outfile = fopen(dest, "w");
if (!outfile) {
warn("fopen \"%s\"", dest);
status = false;
goto cleanup;
return false;
}
int res;
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) {
warnx("CSV export to \"%s\" faield: %s\n", dest,
sqlite3_errstr(res));
status = false;
goto cleanup; // readability
return false;
}
fflush(outfile);
cleanup:
pthread_cleanup_pop(true); // reset query
pthread_cleanup_pop(true); // close file
return status;
sqlite3_reset(EXPORT_CSV_QUERY);
fclose(outfile);
return true;
}
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) {
LOG_VERBOSE("Creating directory: \"%s\"\n", path)
LOG_VERBOSE("Creating directory: \"%s\"\n", path);
char *copy = strdup_checked(path);
char *work_str = copy, *token;
while ((token = strsep(&work_str, "/"))) {
@ -576,3 +539,32 @@ void request_restart() {
RUNNING = false;
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 <libgpio.h>
#include <sqlite3.h>
#include <sys/wait.h>
#define _STRINGIFY_LIT(s) (#s)
#define STRINGIFY(v) _STRINGIFY_LIT(v)
@ -254,5 +255,12 @@ bool mkdirs(const char *path, mode_t mode);
* re-initialize configuration values.
*/
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