lardon3d/src/image_catalog_persistent.c

219 lines
8.6 KiB
C

#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <openssl/evp.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include <lardon3d/image_catalog.h>
enum { COPY_BUFFER_SIZE = 64 * 1024 };
static bool
join_path(char output[PATH_MAX], const char *parent, const char *child)
{
int written = snprintf(output, PATH_MAX, "%s/%s", parent, child);
return written > 0 && (size_t)written < PATH_MAX;
}
static bool
ensure_directory(const char *path)
{
struct stat information;
if (lstat(path, &information) == 0)
return S_ISDIR(information.st_mode) && !S_ISLNK(information.st_mode);
if (errno != ENOENT) return false;
if (mkdir(path, 0755) == 0) return true;
return errno == EEXIST && lstat(path, &information) == 0
&& S_ISDIR(information.st_mode) && !S_ISLNK(information.st_mode);
}
static bool
write_all(int descriptor, const unsigned char *data, size_t size)
{
size_t offset = 0;
while (offset < size) {
ssize_t written = write(descriptor, data + offset, size - offset);
if (written < 0 && errno == EINTR) continue;
if (written <= 0) return false;
offset += (size_t)written;
}
return true;
}
static bool
hash_stream(int input, int output,
unsigned char hash[LARDON3D_PROJECT_DB_SHA256_SIZE], uint64_t *size)
{
EVP_MD_CTX *context = EVP_MD_CTX_new();
if (!context) return false;
bool success = EVP_DigestInit_ex(context, EVP_sha256(), NULL) == 1;
unsigned char buffer[COPY_BUFFER_SIZE];
uint64_t total = 0;
while (success) {
ssize_t count = read(input, buffer, sizeof(buffer));
if (count < 0 && errno == EINTR) continue;
if (count < 0) { success = false; break; }
if (count == 0) break;
size_t bytes = (size_t)count;
if (total > (uint64_t)INT64_MAX - bytes
|| EVP_DigestUpdate(context, buffer, bytes) != 1
|| (output >= 0 && !write_all(output, buffer, bytes))) {
success = false; break;
}
total += bytes;
}
unsigned int hash_size = 0;
if (!success || EVP_DigestFinal_ex(context, hash, &hash_size) != 1
|| hash_size != LARDON3D_PROJECT_DB_SHA256_SIZE) success = false;
EVP_MD_CTX_free(context);
if (success) *size = total;
return success;
}
static void
hash_hex(const unsigned char hash[LARDON3D_PROJECT_DB_SHA256_SIZE],
char text[65])
{
static const char digits[] = "0123456789abcdef";
for (size_t index = 0; index < LARDON3D_PROJECT_DB_SHA256_SIZE; ++index) {
text[index * 2] = digits[hash[index] >> 4];
text[index * 2 + 1] = digits[hash[index] & 15U];
}
text[64] = '\0';
}
static bool
same_published_asset(const char *path,
const unsigned char expected[LARDON3D_PROJECT_DB_SHA256_SIZE],
uint64_t expected_size)
{
int descriptor = open(path, O_RDONLY | O_NOFOLLOW);
if (descriptor < 0) return false;
struct stat information;
unsigned char actual[LARDON3D_PROJECT_DB_SHA256_SIZE];
uint64_t size = 0;
bool success = fstat(descriptor, &information) == 0
&& S_ISREG(information.st_mode)
&& hash_stream(descriptor, -1, actual, &size)
&& size == expected_size
&& memcmp(actual, expected, sizeof(actual)) == 0;
(void)close(descriptor);
return success;
}
static bool
sync_directory(const char *path)
{
int descriptor = open(path, O_RDONLY | O_DIRECTORY);
if (descriptor < 0) return false;
bool success = fsync(descriptor) == 0;
if (close(descriptor) != 0) success = false;
return success;
}
bool
lardon3d_image_catalog_create_scanset(Lardon3DAppState *state,
const char *name, Lardon3DProjectDbScanSet *scanset)
{
return state && state->project_loaded && state->project_db
&& lardon3d_project_db_create_scanset(state->project_db, name, scanset)
== LARDON3D_PROJECT_DB_OK;
}
Lardon3DImageCatalogImportResult
lardon3d_image_catalog_import_file(Lardon3DAppState *state,
uint64_t scanset_id, const char *source_path, uint64_t producer_task_id,
Lardon3DProjectDbImage *image, Lardon3DProjectDbImageAsset *asset)
{
if (!state || !state->project_loaded || !state->project_db
|| scanset_id == 0 || !source_path || !source_path[0] || !image || !asset)
return LARDON3D_IMAGE_CATALOG_INVALID_ARGUMENT;
const char *basename = strrchr(source_path, '/');
basename = basename ? basename + 1 : source_path;
if (!basename[0] || strchr(basename, '\\') || strchr(basename, '\t')
|| strchr(basename, '\r') || strchr(basename, '\n')
|| strnlen(basename, LARDON3D_PROJECT_DB_IMAGE_NAME_CAPACITY)
>= LARDON3D_PROJECT_DB_IMAGE_NAME_CAPACITY)
return LARDON3D_IMAGE_CATALOG_INVALID_ARGUMENT;
Lardon3DProjectDbScanSet scanset;
if (lardon3d_project_db_load_scanset(state->project_db, scanset_id, &scanset)
!= LARDON3D_PROJECT_DB_OK) return LARDON3D_IMAGE_CATALOG_INVALID_ARGUMENT;
int input = open(source_path, O_RDONLY | O_NOFOLLOW);
if (input < 0) return LARDON3D_IMAGE_CATALOG_SOURCE_ERROR;
struct stat source_information;
if (fstat(input, &source_information) != 0
|| !S_ISREG(source_information.st_mode) || source_information.st_size < 0) {
(void)close(input); return LARDON3D_IMAGE_CATALOG_SOURCE_ERROR;
}
char assets[PATH_MAX], images[PATH_MAX], temporary[PATH_MAX];
if (!join_path(assets, state->project_path, "assets")
|| !ensure_directory(assets) || !sync_directory(state->project_path)
|| !join_path(images, assets, "images") || !ensure_directory(images)
|| !sync_directory(assets)
|| snprintf(temporary, sizeof(temporary), "%s/.asset.tmp.XXXXXX", images) <= 0) {
(void)close(input); return LARDON3D_IMAGE_CATALOG_PUBLICATION_ERROR;
}
int output = mkstemp(temporary);
if (output < 0) { (void)close(input); return LARDON3D_IMAGE_CATALOG_PUBLICATION_ERROR; }
unsigned char hash[LARDON3D_PROJECT_DB_SHA256_SIZE];
uint64_t size = 0;
bool copied = hash_stream(input, output, hash, &size);
if (close(input) != 0) copied = false;
if (copied && fsync(output) != 0) copied = false;
if (close(output) != 0) copied = false;
if (!copied) {
(void)unlink(temporary); return LARDON3D_IMAGE_CATALOG_SOURCE_ERROR;
}
char hex[65], prefix[3], prefix_path[PATH_MAX], final[PATH_MAX];
hash_hex(hash, hex); prefix[0] = hex[0]; prefix[1] = hex[1]; prefix[2] = '\0';
if (!join_path(prefix_path, images, prefix) || !ensure_directory(prefix_path)
|| !sync_directory(images)
|| !join_path(final, prefix_path, hex)) {
(void)unlink(temporary); return LARDON3D_IMAGE_CATALOG_PUBLICATION_ERROR;
}
bool published = false;
if (link(temporary, final) == 0) {
bool temporary_removed = unlink(temporary) == 0;
bool directory_synced = sync_directory(prefix_path);
published = temporary_removed && directory_synced;
} else if (errno == EEXIST && same_published_asset(final, hash, size)) {
published = unlink(temporary) == 0;
}
if (!published) {
(void)unlink(temporary); return LARDON3D_IMAGE_CATALOG_PUBLICATION_ERROR;
}
char relative[LARDON3D_PROJECT_DB_PATH_CAPACITY];
int relative_size = snprintf(relative, sizeof(relative),
"assets/images/%s/%s", prefix, hex);
if (relative_size <= 0 || (size_t)relative_size >= sizeof(relative))
return LARDON3D_IMAGE_CATALOG_PUBLICATION_ERROR;
Lardon3DProjectDbImageRegisterStatus status;
int64_t imported_at = (int64_t)time(NULL);
if (imported_at < 0 || lardon3d_project_db_register_image(state->project_db,
scanset_id, hash, relative, size, basename, source_path,
producer_task_id, imported_at, &status, image)
!= LARDON3D_PROJECT_DB_OK) return LARDON3D_IMAGE_CATALOG_DB_ERROR;
if (lardon3d_project_db_load_image(state->project_db, image->image_id,
image, asset) != LARDON3D_PROJECT_DB_OK)
return LARDON3D_IMAGE_CATALOG_DB_ERROR;
return status == LARDON3D_PROJECT_DB_IMAGE_ALREADY_PRESENT
? LARDON3D_IMAGE_CATALOG_ALREADY_PRESENT
: LARDON3D_IMAGE_CATALOG_IMPORTED;
}
Lardon3DProjectDbResult
lardon3d_image_catalog_list(Lardon3DAppState *state, uint64_t scanset_id,
uint64_t after_image_id, Lardon3DProjectDbImage *images,
Lardon3DProjectDbImageAsset *assets, size_t capacity, size_t *count)
{
if (!state || !state->project_loaded || !state->project_db)
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
return lardon3d_project_db_list_images(state->project_db, scanset_id,
after_image_id, images, assets, capacity, count);
}