Skip to content

Commit

Permalink
Merge pull request #81 from larsewi/partial
Browse files Browse the repository at this point in the history
Leech now accepts partial hash as block identifier argument
  • Loading branch information
larsewi authored Jan 16, 2025
2 parents 5851acc + d8c5e33 commit 29ca1fe
Show file tree
Hide file tree
Showing 7 changed files with 250 additions and 15 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ config.h
*.valgrind
.vscode
tests/tmp/*
tests/tmp_??????
.DS_Store

config.guess~
Expand Down
75 changes: 75 additions & 0 deletions lib/block.c
Original file line number Diff line number Diff line change
Expand Up @@ -202,3 +202,78 @@ bool LCH_BlockGetTimestamp(const LCH_Json *const block,
}
return true;
}

static bool IsValidBlockId(const char *const block_id) {
assert(block_id != NULL);

size_t i;
for (i = 0; block_id[i] != '\0'; i++) {
if (!(block_id[i] >= '0' && block_id[i] <= '9') &&
!(block_id[i] >= 'a' && block_id[i] <= 'f')) {
/* Character is not in range [0-9] or [a-f] */
return false;
}
}

/* Make sure block ID is 40 characters long */
const bool correct_length = (i == strlen(LCH_GENISIS_BLOCK_ID));
return correct_length;
}

char *LCH_BlockIdFromArgument(const char *const work_dir,
const char *const argument) {
assert(work_dir != NULL);
assert(argument != NULL);

char path[PATH_MAX];
if (!LCH_FilePathJoin(path, PATH_MAX, 2, work_dir, "blocks")) {
/* Error already logged */
return NULL;
}

size_t index, num_matching = 0;

LCH_List *const blocks = LCH_FileListDirectory(path, true);

/* Add genesis block to the list */
char *const genisis_id = LCH_StringDuplicate(LCH_GENISIS_BLOCK_ID);
if (genisis_id == NULL) {
LCH_ListDestroy(blocks);
return NULL;
}

if (!LCH_ListAppend(blocks, genisis_id, free)) {
/* Error already logged */
free(genisis_id);
LCH_ListDestroy(blocks);
return NULL;
}

const size_t num_blocks = LCH_ListLength(blocks);

for (size_t i = 0; i < num_blocks; i++) {
const char *const filename = (char *)LCH_ListGet(blocks, i);
if (!IsValidBlockId(filename)) {
LCH_LOG_WARNING(
"The file '%s%c%s' does not conform with the block naming convention "
"and will be ignored",
path, LCH_PATH_SEP, filename);
} else if (LCH_StringStartsWith(filename, argument)) {
index = i;
num_matching += 1;
}
}

char *block_id = NULL;
if (num_matching != 1) {
LCH_LOG_ERROR("%s block identifier '%s': %zu blocks found",
(num_matching > 1) ? "Ambiguous" : "Unknown", argument,
num_matching);
} else {
const char *const filename = (char *)LCH_ListGet(blocks, index);
block_id = LCH_StringDuplicate(filename);
}

LCH_ListDestroy(blocks);
return block_id;
}
10 changes: 10 additions & 0 deletions lib/block.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,14 @@ const LCH_Json *LCH_BlockGetPayload(const LCH_Json *block);
LCH_Json *LCH_BlockRemovePayload(const LCH_Json *block);
bool LCH_BlockGetTimestamp(const LCH_Json *block, double *timestamp);

/**
* @brief Get block ID from partial hash
* @param work_dir Leech work directory
* @param argument Argument containing partial hash matching the start of the
* identifier of an existing block
* @return Full block identifier or NULL in case of ambiguous/unknown blocks or
* memory error
*/
char *LCH_BlockIdFromArgument(const char *work_dir, const char *argument);

#endif // _LEECH_BLOCK_H
72 changes: 63 additions & 9 deletions lib/files.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,25 +44,28 @@ bool LCH_FileSize(FILE *file, size_t *size) {
/******************************************************************************/

bool LCH_FileExists(const char *const path) {
struct stat sb;
memset(&sb, 0, sizeof(struct stat));
return stat(path, &sb) == 0;
const bool exists = LCH_FileIsRegular(path) || LCH_FileIsDirectory(path);
return exists;
}

/******************************************************************************/

bool LCH_FileIsRegular(const char *const path) {
struct stat sb;
memset(&sb, 0, sizeof(struct stat));
return (stat(path, &sb) == 0) && ((sb.st_mode & S_IFMT) == S_IFREG);
const bool is_regular =
(lstat(path, &sb) == 0) && ((sb.st_mode & S_IFMT) == S_IFREG);
return is_regular;
}

/******************************************************************************/

bool LCH_FileIsDirectory(const char *const path) {
struct stat sb;
memset(&sb, 0, sizeof(struct stat));
return (stat(path, &sb) == 0) && ((sb.st_mode & S_IFMT) == S_IFDIR);
const bool is_directory =
(lstat(path, &sb) == 0) && ((sb.st_mode & S_IFMT) == S_IFDIR);
return is_directory;
}

/******************************************************************************/
Expand Down Expand Up @@ -108,11 +111,62 @@ bool LCH_FilePathJoin(char *path, const size_t path_max, const size_t n_items,

/******************************************************************************/

bool LCH_FileDelete(const char *const filename) {
if (unlink(filename) != 0) {
LCH_LOG_ERROR("Failed to delete file '%s': %s", filename, strerror(errno));
bool LCH_FileDelete(const char *const parent) {
assert(parent != NULL);

if (LCH_FileIsDirectory(parent)) {
LCH_List *const children = LCH_FileListDirectory(parent, false);
if (children == NULL) {
return false;
}

char path[PATH_MAX];
const size_t num_children = LCH_ListLength(children);

for (size_t i = 0; i < num_children; i++) {
const char *const child = (char *)LCH_ListGet(children, i);
assert(child != NULL);

if (LCH_StringEqual(child, ".") || LCH_StringEqual(child, "..")) {
continue;
}

if (!LCH_FilePathJoin(path, sizeof(path), 2, parent, child)) {
/* Error is already logged */
LCH_ListDestroy(children);
return false;
}

if (!LCH_FileDelete(path)) {
/* Error is already logged */
LCH_ListDestroy(children);
return false;
}
}

LCH_ListDestroy(children);

const int ret = rmdir(parent);
if (ret == -1) {
LCH_LOG_ERROR("Failed to remove directory '%s': %s", parent,
strerror(errno));
return false;
}
LCH_LOG_DEBUG("Removed directory '%s'", parent);
} else if (LCH_FileIsRegular(parent)) {
const int ret = unlink(parent);
if (ret == -1) {
LCH_LOG_ERROR("Failed to delete regular file '%s': %s", parent,
strerror(errno));
return false;
}
LCH_LOG_DEBUG("Deleted regular file '%s'", parent);
} else {
LCH_LOG_ERROR(
"Failed to delete file '%s': It's not a directory or regular file");
return false;
}

return true;
}

Expand All @@ -126,7 +180,7 @@ bool LCH_FileCreateParentDirectories(const char *const filename) {
LCH_List *const dirs = LCH_ListCreate();
struct stat sb;

while (stat(parent, &sb) == -1) {
while (lstat(parent, &sb) == -1) {
char *const dir = LCH_StringDuplicate(parent);
if (dir == NULL) {
LCH_ListDestroy(dirs);
Expand Down
15 changes: 13 additions & 2 deletions lib/leech.c
Original file line number Diff line number Diff line change
Expand Up @@ -478,13 +478,19 @@ static LCH_Json *MergeBlocks(const LCH_Instance *const instance,
return merged;
}

LCH_Buffer *LCH_Diff(const char *const work_dir, const char *const final_id) {
LCH_Buffer *LCH_Diff(const char *const work_dir, const char *const argument) {
assert(work_dir != NULL);
assert(final_id != NULL);
assert(argument != NULL);

char *const final_id = LCH_BlockIdFromArgument(work_dir, argument);
if (final_id == NULL) {
return NULL;
}

LCH_Instance *const instance = LCH_InstanceLoad(work_dir);
if (instance == NULL) {
LCH_LOG_ERROR("Failed to load instance from configuration file");
free(final_id);
return NULL;
}

Expand All @@ -496,6 +502,7 @@ LCH_Buffer *LCH_Diff(const char *const work_dir, const char *const final_id) {
"Failed to get block identifier from the head of the chain. "
"Maybe there has not been any commits yet?");
LCH_InstanceDestroy(instance);
free(final_id);
return NULL;
}

Expand All @@ -504,6 +511,7 @@ LCH_Buffer *LCH_Diff(const char *const work_dir, const char *const final_id) {
LCH_LOG_ERROR("Failed to create patch");
free(block_id);
LCH_InstanceDestroy(instance);
free(final_id);
return NULL;
}

Expand All @@ -513,6 +521,7 @@ LCH_Buffer *LCH_Diff(const char *const work_dir, const char *const final_id) {
LCH_LOG_ERROR("Failed to create empty block");
LCH_JsonDestroy(patch);
LCH_InstanceDestroy(instance);
free(final_id);
return NULL;
}

Expand All @@ -521,9 +530,11 @@ LCH_Buffer *LCH_Diff(const char *const work_dir, const char *const final_id) {
LCH_LOG_ERROR("Failed to generate patch file");
LCH_JsonDestroy(patch);
LCH_InstanceDestroy(instance);
free(final_id);
return NULL;
}
LCH_InstanceDestroy(instance);
free(final_id);

if (!LCH_PatchAppendBlock(patch, block)) {
LCH_LOG_ERROR("Failed to append block to patch");
Expand Down
5 changes: 1 addition & 4 deletions tests/acceptance.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@
import os
import json

# If you want to run this as current user
# sudo -u postgres createuser -s larsewi

SEED = 1 # Seed used by random generator
CHANCE = 20 # Percent chance of report collection
HUB_ID = "SHA=b9353fd"
Expand Down Expand Up @@ -168,7 +165,7 @@ def record_state(self):
execute(cmd)

def generate_diff(self):
lastseen = "0000000000000000000000000000000000000000"
lastseen = "00000"
path = Path(self.hub_workdir, self.id)
if path.exists():
with open(path, "r") as f:
Expand Down
87 changes: 87 additions & 0 deletions tests/check_block.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
#include <check.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>

#include "../lib/block.h"
#include "../lib/files.h"
#include "../lib/logger.h"

START_TEST(test_LCH_BlockCreate) {
Expand Down Expand Up @@ -42,12 +46,95 @@ START_TEST(test_LCH_BlockCreate) {
}
END_TEST

START_TEST(test_LCH_BlockIdFromArgument) {
char tmpl[] = "tmp_XXXXXX";
const char *work_dir = mkdtemp(tmpl);
ck_assert_ptr_nonnull(work_dir);

const char *blocks[] = {
"0820ee7abd43af0221f2ad3f81f667dd87cad6c8",
"0957d9468925b66a5acbdd6551c11dc6344337b3",
"be3e991161dcde612b61be9562e08942e9a47903",
"f80cc0ac9dc567acb99b076412c9884cbaa84f0",
"8de90ff4c64c0a251d0cdb45b4ec2253b7c6e2a3a",
"invalid block identifier",
"3d28755d158bf7a7aabbc308c527fdc9d413c9c8",
"3d28755d158b158bf7a7aabbc308c527fdc9d4c8",
NULL,
};

LCH_Buffer *const buffer = LCH_BufferCreate();
ck_assert_ptr_nonnull(buffer);

char filename[PATH_MAX];
for (size_t i = 0; blocks[i] != NULL; i++) {
/* Create some empty files in $(work_dir)/blocks/ */
ck_assert(
LCH_FilePathJoin(filename, PATH_MAX, 3, work_dir, "blocks", blocks[i]));
ck_assert(LCH_BufferWriteFile(buffer, filename));
}

LCH_BufferDestroy(buffer);

char *block_id = LCH_BlockIdFromArgument(work_dir, "0820ee7");
ck_assert_str_eq(block_id, blocks[0]);
free(block_id);

block_id = LCH_BlockIdFromArgument(work_dir, "0957d946");
ck_assert_str_eq(block_id, blocks[1]);
free(block_id);

/* Try with the entire hash */
block_id = LCH_BlockIdFromArgument(
work_dir, "be3e991161dcde612b61be9562e08942e9a47903");
ck_assert_str_eq(block_id, blocks[2]);
free(block_id);

/* Try with more than the entire hash */
block_id = LCH_BlockIdFromArgument(
work_dir, "be3e991161dcde612b61be9562e08942e9a47903a");
ck_assert_ptr_null(block_id);
free(block_id);

block_id = LCH_BlockIdFromArgument(work_dir, "957d94");
ck_assert_ptr_null(block_id);
free(block_id);

block_id = LCH_BlockIdFromArgument(work_dir, "f80cc0ac9");
ck_assert_ptr_null(block_id);
free(block_id);

block_id = LCH_BlockIdFromArgument(work_dir, "8de90ff4c64c0");
ck_assert_ptr_null(block_id);
free(block_id);

block_id = LCH_BlockIdFromArgument(work_dir, "invalid");
ck_assert_ptr_null(block_id);
free(block_id);

block_id = LCH_BlockIdFromArgument(work_dir, "3d28755d158b");
ck_assert_ptr_null(block_id);
free(block_id);

block_id = LCH_BlockIdFromArgument(work_dir, "3d28755d158b1");
ck_assert_str_eq(block_id, blocks[7]);
free(block_id);

ck_assert(LCH_FileDelete(work_dir));
}
END_TEST

Suite *BlockSuite(void) {
Suite *s = suite_create("block.c");
{
TCase *tc = tcase_create("LCH_BlockCreate");
tcase_add_test(tc, test_LCH_BlockCreate);
suite_add_tcase(s, tc);
}
{
TCase *tc = tcase_create("LCH_BlockIdFromArgument");
tcase_add_test(tc, test_LCH_BlockIdFromArgument);
suite_add_tcase(s, tc);
}
return s;
}

0 comments on commit 29ca1fe

Please sign in to comment.