Finished writing the code to generate Subsonic LinkTable

- Also refactored various bits and pieces
This commit is contained in:
Fufu Fang 2019-10-23 21:04:25 +01:00
parent b7c63f4418
commit 5062f511bd
No known key found for this signature in database
GPG Key ID: 0F6BB5EF6F8BB729
11 changed files with 307 additions and 133 deletions

View File

@ -1,7 +1,8 @@
VERSION=1.1.10
CFLAGS+= -O2 -Wall -Wextra -Wshadow -rdynamic\
-D_FILE_OFFSET_BITS=64 -DVERSION=\"$(VERSION)\" \
CFLAGS+= -O2 -Wall -Wextra -Wshadow\
-rdynamic -D_XOPEN_SOURCE=700 -D_DEFAULT_SOURCE\
-D_FILE_OFFSET_BITS=64 -DVERSION=\"$(VERSION)\"\
`pkg-config --cflags-only-I gumbo libcurl fuse uuid expat`
LIBS = -pthread -lgumbo -lcurl -lfuse -lcrypto -luuid -lexpat
COBJS = network.o fuse_local.o link.o cache.o util.o main.o

View File

@ -9,21 +9,6 @@
#include <string.h>
#include <unistd.h>
/**
* \brief Data file block size
* \details We set it to 1024*1024*8 = 8MiB
*/
#define DEFAULT_DATA_BLK_SZ 8*1024*1024
/**
* \brief Maximum segment block count
* \details This is set to 128*1024 blocks, which uses 128KB. By default,
* this allows the user to store (128*1024)*(8*1024*1024) = 1TB of data
*/
#define DEFAULT_MAX_SEGBC 128*1024
/**
* \brief error associated with metadata
*/
@ -36,10 +21,7 @@ typedef enum {
} MetaError;
/* ---------------- External variables -----------------------*/
int CACHE_SYSTEM_INIT = 0;
int DATA_BLK_SZ = 0;
int MAX_SEGBC = DEFAULT_MAX_SEGBC;
char *META_DIR;
/* ----------------- Static variables ----------------------- */
@ -156,10 +138,6 @@ void CacheSystem_init(const char *path, int url_supplied)
strerror(errno));
}
if (!DATA_BLK_SZ) {
DATA_BLK_SZ = DEFAULT_DATA_BLK_SZ;
}
CACHE_SYSTEM_INIT = 1;
}
@ -202,12 +180,12 @@ blksz: %d, segbc: %ld\n", cf->path, cf->content_length, cf->blksz, cf->segbc);
return EZERO;
}
if (cf->blksz != DATA_BLK_SZ) {
fprintf(stderr, "Meta_read(): Warning: cf->blksz != DATA_BLK_SZ\n");
if (cf->blksz != CONFIG.data_blksz) {
fprintf(stderr, "Meta_read(): Warning: cf->blksz != CONFIG.data_blksz\n");
}
/* Allocate some memory for the segment */
if (cf->segbc > MAX_SEGBC) {
if (cf->segbc > CONFIG.max_segbc) {
fprintf(stderr, "Meta_read(): Error: segbc: %ld\n", cf->segbc);
return EMEM;
}
@ -671,7 +649,7 @@ int Cache_create(Link *this_link)
cf->path = strndup(fn, MAX_PATH_LEN);
cf->time = this_link->time;
cf->content_length = this_link->content_length;
cf->blksz = DATA_BLK_SZ;
cf->blksz = CONFIG.data_blksz;
cf->segbc = (cf->content_length / cf->blksz) + 1;
cf->seg = CALLOC(cf->segbc, sizeof(Seg));

View File

@ -70,16 +70,6 @@ struct Cache {
*/
extern int CACHE_SYSTEM_INIT;
/**
* \brief The size of each download segment
*/
extern int DATA_BLK_SZ;
/**
* \brief The maximum segment count for a single cache file
*/
extern int MAX_SEGBC;
/**
* \brief The metadata directory
*/

View File

@ -49,9 +49,9 @@ LinkTable *LinkSystem_init(const char *url)
}
/* ----------- Enable cache system --------------------*/
if (NETWORK_CONFIG.cache_enabled) {
if (NETWORK_CONFIG.cache_dir) {
CacheSystem_init(NETWORK_CONFIG.cache_dir, 0);
if (CONFIG.cache_enabled) {
if (CONFIG.cache_dir) {
CacheSystem_init(CONFIG.cache_dir, 0);
} else {
CacheSystem_init(url, 1);
}
@ -62,7 +62,7 @@ LinkTable *LinkSystem_init(const char *url)
return ROOT_LINK_TBL;
}
static void LinkTable_add(LinkTable *linktbl, Link *link)
void LinkTable_add(LinkTable *linktbl, Link *link)
{
linktbl->num++;
linktbl->links = realloc(linktbl->links, linktbl->num * sizeof(Link *));
@ -139,7 +139,7 @@ static CURL *Link_to_curl(Link *link)
fprintf(stderr, "Link_to_curl(): curl_easy_init() failed!\n");
}
/* set up some basic curl stuff */
curl_easy_setopt(curl, CURLOPT_USERAGENT, NETWORK_CONFIG.user_agent);
curl_easy_setopt(curl, CURLOPT_USERAGENT, CONFIG.user_agent);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
/* for following directories without the '/' */
curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 2);
@ -151,26 +151,26 @@ static CURL *Link_to_curl(Link *link)
// curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
if (NETWORK_CONFIG.username) {
curl_easy_setopt(curl, CURLOPT_USERNAME, NETWORK_CONFIG.username);
if (CONFIG.http_username) {
curl_easy_setopt(curl, CURLOPT_USERNAME, CONFIG.http_username);
}
if (NETWORK_CONFIG.password) {
curl_easy_setopt(curl, CURLOPT_PASSWORD, NETWORK_CONFIG.password);
if (CONFIG.http_password) {
curl_easy_setopt(curl, CURLOPT_PASSWORD, CONFIG.http_password);
}
if (NETWORK_CONFIG.proxy) {
curl_easy_setopt(curl, CURLOPT_PROXY, NETWORK_CONFIG.proxy);
if (CONFIG.proxy) {
curl_easy_setopt(curl, CURLOPT_PROXY, CONFIG.proxy);
}
if (NETWORK_CONFIG.proxy_user) {
if (CONFIG.proxy_username) {
curl_easy_setopt(curl, CURLOPT_PROXYUSERNAME,
NETWORK_CONFIG.proxy_user);
CONFIG.proxy_username);
}
if (NETWORK_CONFIG.proxy_pass) {
if (CONFIG.proxy_password) {
curl_easy_setopt(curl, CURLOPT_PROXYPASSWORD,
NETWORK_CONFIG.proxy_pass);
CONFIG.proxy_password);
}
return curl;
@ -355,7 +355,7 @@ DataStruct Link_to_DataStruct(Link *head_link)
fprintf(stderr,
"LinkTable_new(): URL: %s, HTTP %ld, retrying later.\n",
url, http_resp);
sleep(HTTP_WAIT_SEC);
sleep(CONFIG.http_wait_sec);
} else if (http_resp != HTTP_OK) {
fprintf(stderr,
"LinkTable_new(): cannot retrieve URL: %s, HTTP %ld\n",

View File

@ -159,4 +159,8 @@ void LinkTable_free(LinkTable *linktbl);
*/
void LinkTable_print(LinkTable *linktbl);
/**
* \brief add a Link to a LinkTable
*/
void LinkTable_add(LinkTable *linktbl, Link *link);
#endif

View File

@ -36,7 +36,7 @@ int main(int argc, char **argv)
add_arg(&fuse_argv, &fuse_argc, argv[0]);
/* initialise network configuration struct */
NetworkConfig_init();
Config_init();
/* initialise network subsystem */
NetworkSystem_init();
@ -140,7 +140,10 @@ parse_arg_list(int argc, char **argv, char ***fuse_argv, int *fuse_argc)
{"max-conns", required_argument, NULL, 'L'}, /* 11 */
{"user-agent", required_argument, NULL, 'L'}, /* 12 */
{"retry-wait", required_argument, NULL, 'L'}, /* 13 */
{"cache-location", required_argument, NULL, 'L'}, /* 14 */
{"cache-location", required_argument, NULL, 'L'}, /* 14 */
{"sonic-username", required_argument, NULL, 'L'}, /* 15 */
{"sonic-password", required_argument, NULL, 'L'}, /* 16 */
{"sonic", no_argument, NULL, 'L'}, /* 17 */
{0, 0, 0, 0}
};
while ((c =
@ -170,43 +173,52 @@ parse_arg_list(int argc, char **argv, char ***fuse_argv, int *fuse_argc)
add_arg(fuse_argv, fuse_argc, "-s");
break;
case 'u':
NETWORK_CONFIG.username = strdup(optarg);
CONFIG.http_username = strdup(optarg);
break;
case 'p':
NETWORK_CONFIG.password = strdup(optarg);
CONFIG.http_password = strdup(optarg);
break;
case 'P':
NETWORK_CONFIG.proxy = strdup(optarg);
CONFIG.proxy = strdup(optarg);
break;
case 'L':
/* Long options */
switch (long_index) {
case 6:
NETWORK_CONFIG.proxy_user = strdup(optarg);
CONFIG.proxy_username = strdup(optarg);
break;
case 7:
NETWORK_CONFIG.proxy_pass = strdup(optarg);
CONFIG.proxy_password = strdup(optarg);
break;
case 8:
NETWORK_CONFIG.cache_enabled = 1;
CONFIG.cache_enabled = 1;
break;
case 9:
DATA_BLK_SZ = atoi(optarg) * 1024 * 1024;
CONFIG.data_blksz = atoi(optarg) * 1024 * 1024;
break;
case 10:
MAX_SEGBC = atoi(optarg);
CONFIG.max_segbc = atoi(optarg);
break;
case 11:
NETWORK_CONFIG.max_conns = atoi(optarg);
CONFIG.max_conns = atoi(optarg);
break;
case 12:
NETWORK_CONFIG.user_agent = strdup(optarg);
CONFIG.user_agent = strdup(optarg);
break;
case 13:
HTTP_WAIT_SEC = atoi(optarg);
CONFIG.http_wait_sec = atoi(optarg);
break;
case 14:
NETWORK_CONFIG.cache_dir = strdup(optarg);
CONFIG.cache_dir = strdup(optarg);
break;
case 15:
CONFIG.sonic_username = strdup(optarg);
break;
case 16:
CONFIG.sonic_password = strdup(optarg);
break;
case 17:
CONFIG.sonic_mode = 1;
break;
default:
fprintf(stderr, "see httpdirfs -h for usage\n");

View File

@ -9,13 +9,9 @@
#include <stdio.h>
#include <unistd.h>
#define DEFAULT_NETWORK_MAX_CONNS 10
#define DEFAULT_HTTP_WAIT_SEC 5
/* ----------------- External variables ---------------------- */
CURLSH *CURL_SHARE;
NetworkConfigStruct NETWORK_CONFIG;
int HTTP_WAIT_SEC = DEFAULT_HTTP_WAIT_SEC;
/* ----------------- Static variable ----------------------- */
/** \brief curl multi interface handle */
@ -123,8 +119,8 @@ static void curl_process_msgs(CURLMsg *curl_msg, int n_running_curl,
if (!slept) {
fprintf(stderr,
"curl_process_msgs(): HTTP %ld, sleeping for %d sec\n",
http_resp, HTTP_WAIT_SEC);
sleep(HTTP_WAIT_SEC);
http_resp, CONFIG.http_wait_sec);
sleep(CONFIG.http_wait_sec);
slept = 1;
}
} else {
@ -231,19 +227,6 @@ int curl_multi_perform_once(void)
return n_running_curl;
}
void NetworkConfig_init(void)
{
NETWORK_CONFIG.username = NULL;
NETWORK_CONFIG.password = NULL;
NETWORK_CONFIG.proxy = NULL;
NETWORK_CONFIG.proxy_user = NULL;
NETWORK_CONFIG.proxy_pass = NULL;
NETWORK_CONFIG.max_conns = DEFAULT_NETWORK_MAX_CONNS;
NETWORK_CONFIG.user_agent = DEFAULT_USER_AGENT;
NETWORK_CONFIG.cache_enabled = 0;
NETWORK_CONFIG.cache_dir = NULL;
}
void NetworkSystem_init(void)
{
/* ------- Global related ----------*/
@ -277,9 +260,9 @@ void NetworkSystem_init(void)
exit_failure();
}
curl_multi_setopt(curl_multi, CURLMOPT_MAX_TOTAL_CONNECTIONS,
NETWORK_CONFIG.max_conns);
CONFIG.max_conns);
curl_multi_setopt(curl_multi, CURLMOPT_MAX_HOST_CONNECTIONS,
NETWORK_CONFIG.max_conns);
CONFIG.max_conns);
/* ------------ Initialise locks ---------*/
if (pthread_mutex_init(&transfer_lock, NULL)) {

View File

@ -8,24 +8,6 @@
#include "link.h"
/**
* \brief the default user agent string
*/
#define DEFAULT_USER_AGENT "HTTPDirFS-" VERSION
typedef struct {
char *username;
char *password;
char *proxy;
char *proxy_user;
char *proxy_pass;
long max_conns;
char *user_agent;
int http_429_wait;
char *cache_dir;
int cache_enabled;
} NetworkConfigStruct;
/** \brief HTTP response codes */
typedef enum {
HTTP_OK = 200,
@ -36,21 +18,12 @@ typedef enum {
HTTP_CLOUDFLARE_TIMEOUT = 524
} HTTPResponseCode;
/** \brief The waiting time after getting HTTP 429 */
extern int HTTP_WAIT_SEC;
/** \brief CURL configuration */
extern NetworkConfigStruct NETWORK_CONFIG;
/** \brief curl shared interface */
extern CURLSH *CURL_SHARE;
/** \brief perform one transfer cycle */
int curl_multi_perform_once(void);
/** \brief initialise network config struct */
void NetworkConfig_init(void);
/** \brief initialise the network module */
void NetworkSystem_init(void);

View File

@ -8,6 +8,8 @@
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
typedef struct {
char *server;
@ -31,8 +33,8 @@ void sonic_config_init(const char *server, const char *username,
if (SONIC_CONFIG.server[server_url_len] == '/') {
SONIC_CONFIG.server[server_url_len] = '\0';
}
SONIC_CONFIG.username = strndup(username, MAX_FILENAME_LEN);
SONIC_CONFIG.password = strndup(password, MAX_FILENAME_LEN);
SONIC_CONFIG.http_username = strndup(username, MAX_FILENAME_LEN);
SONIC_CONFIG.http_password = strndup(password, MAX_FILENAME_LEN);
SONIC_CONFIG.client = DEFAULT_USER_AGENT;
/*
* API 1.13.0 is the minimum version that supports
@ -47,16 +49,16 @@ void sonic_config_init(const char *server, const char *username,
static char *sonic_gen_auth_str()
{
char *salt = generate_salt();
size_t password_len = strnlen(SONIC_CONFIG.password, MAX_FILENAME_LEN);
size_t password_len = strnlen(SONIC_CONFIG.http_password, MAX_FILENAME_LEN);
size_t password_salt_len = password_len + strnlen(salt, MAX_FILENAME_LEN);
char *password_salt = CALLOC(password_salt_len + 1, sizeof(char));
strncat(password_salt, SONIC_CONFIG.password, MAX_FILENAME_LEN);
strncat(password_salt, SONIC_CONFIG.http_password, MAX_FILENAME_LEN);
strncat(password_salt + password_len, salt, MAX_FILENAME_LEN);
char *token = generate_md5sum(password_salt);
char *auth_str = CALLOC(MAX_PATH_LEN + 1, sizeof(char));
snprintf(auth_str, MAX_PATH_LEN,
".view?u=%s&t=%s&s=%s&v=%s&c=%s",
SONIC_CONFIG.username, token, salt,
SONIC_CONFIG.http_username, token, salt,
SONIC_CONFIG.api_version, SONIC_CONFIG.client);
free(salt);
free(token);
@ -76,27 +78,54 @@ static char *sonic_gen_url_first_part(char *method)
return url;
}
/**
* \brief generate a getMusicDirectory request URL
*/
static char *sonic_getMusicDirectory_link(const int id)
{
char *first_part = sonic_gen_url_first_part("getMusicDirectory");
char *url = CALLOC(MAX_PATH_LEN + 1, sizeof(char));
snprintf(url, MAX_PATH_LEN, "%s&id=%d", first_part, id);
free(first_part);
return url;
}
/**
* \brief generate a download request URL
*/
static char *sonic_download_link(const int id)
{
char *first_part = sonic_gen_url_first_part("download");
char *url = CALLOC(MAX_PATH_LEN + 1, sizeof(char));
snprintf(url, MAX_PATH_LEN, "%s&id=%d", first_part, id);
free(first_part);
return url;
}
/**
* \brief Process a single element output by the parser
* \details This is the callback function called by the the XML parser.
* \param[in] data user supplied data, in this case it is the pointer to the
* LinkTable.
* \param[in] element the name of this element, it should be either "child" or
* \param[in] elem the name of this element, it should be either "child" or
* "artist"
* \param[in] attributes Each attribute seen in a start (or empty) tag occupies
* \param[in] attr Each attribute seen in a start (or empty) tag occupies
* 2 consecutive places in this vector: the attribute name followed by the
* attribute value. These pairs are terminated by a null pointer.
* \note we are using strcmp rather than strncmp, because we are assuming the
* parser terminates the strings properly, which is a fair assumption,
* considering how mature expat is.
*/
static void XMLCALL XML_process_single_element(void *data, const char *element,
const char **attributes)
static void XMLCALL XML_process_single_element(void *data, const char *elem,
const char **attr)
{
LinkTable *linktbl = (LinkTable *) data;
Link *link;
if (!strncmp(element, "child", 5)) {
if (!strcmp(elem, "child")) {
/* Return from getMusicDirectory */
link = CALLOC(1, sizeof(Link));
link->type = LINK_INVALID;
} else if (!strncmp(element, "artist", 6)){
} else if (!strcmp(elem, "artist")){
/* Return from getIndexes */
link = CALLOC(1, sizeof(Link));
link->type = LINK_DIR;
@ -105,9 +134,78 @@ static void XMLCALL XML_process_single_element(void *data, const char *element,
return;
}
for (int i = 0; attributes[i]; i += 2) {
int id_set = 0;
int linkname_set = 0;
for (int i = 0; attr[i]; i += 2) {
if (!strcmp("id", attr[i])) {
link->sonic_id = atoi(attr[i+1]);
id_set = 1;
continue;
}
if (!strcmp("name", attr[i]) || !strcmp("title", attr[i])) {
strncpy(link->linkname, attr[i+1], MAX_FILENAME_LEN);
linkname_set = 1;
continue;
}
if (!strcmp("isDir", attr[i])) {
if (!strcmp("true", attr[i+1])) {
link->type = LINK_DIR;
} else if (!strcmp("false", attr[i+1])) {
link->type = LINK_FILE;
} else {
link->type = LINK_DIR;
}
continue;
}
if (!strcmp("created", attr[i])) {
struct tm *tm = calloc(1, sizeof(struct tm));
strptime(attr[i+1], "%Y-%m-%dT%H:%M:%S.000Z", tm);
link->time = mktime(tm);
free(tm);
continue;
}
if (!strcmp("size", attr[i])) {
link->content_length = atoll(attr[i+1]);
continue;
}
}
/* Clean up if linkname is not set */
if (!linkname_set) {
free(link);
return;
}
/* Clean up if id is not set */
if (!id_set) {
if (linkname_set) {
free(link->linkname);
}
free(link);
return;
}
if (link->type == LINK_DIR) {
char *url = sonic_getMusicDirectory_link(link->sonic_id);
strncpy(link->f_url, url, MAX_PATH_LEN);
free(url);
} else if (link->type == LINK_FILE) {
char *url = sonic_download_link(link->sonic_id);
strncpy(link->f_url, url, MAX_PATH_LEN);
free(url);
} else {
/* Invalid link */
free(link->linkname);
free(link);
return;
}
LinkTable_add(linktbl, link);
}
/**
@ -127,14 +225,12 @@ static void sonic_XML_to_LinkTable(DataStruct ds, LinkTable *linktbl)
XML_ParserFree(parser);
}
LinkTable *sonic_LinkTable_new(const int id)
{
char *url;
if (id > 0) {
char *first_part = sonic_gen_url_first_part("getMusicDirectory");
url = CALLOC(MAX_PATH_LEN + 1, sizeof(char));
snprintf(url, MAX_PATH_LEN, "%s&id=%d", first_part, id);
free(first_part);
url = sonic_getMusicDirectory_link(id);
} else {
url = sonic_gen_url_first_part("getIndexes");
}
@ -166,7 +262,7 @@ int main(int argc, char **argv)
sonic_config_init(argv[1], argv[2], argv[3]);
NetworkConfig_init();
Config_init();
NetworkSystem_init();
sonic_LinkTable_new(0);

View File

@ -9,10 +9,86 @@
#include <string.h>
#include <errno.h>
/**
* \brief Backtrace buffer size
*/
#define BT_BUF_SIZE 100
/**
* \brief The length of a MD5SUM string
*/
#define MD5_HASH_LEN 32
/**
* \brief The length of the salt
* \details This is basically the length of a UUID
*/
#define SALT_LEN 36
/**
* \brief The default maximum number of network connections
*/
#define DEFAULT_NETWORK_MAX_CONNS 10
/**
* \brief The default HTTP 429 (too many requests) wait time
*/
#define DEFAULT_HTTP_WAIT_SEC 5
/**
* \brief Data file block size
* \details We set it to 1024*1024*8 = 8MiB
*/
#define DEFAULT_DATA_BLKSZ 8*1024*1024
/**
* \brief Maximum segment block count
* \details This is set to 128*1024 blocks, which uses 128KB. By default,
* this allows the user to store (128*1024)*(8*1024*1024) = 1TB of data
*/
#define DEFAULT_MAX_SEGBC 128*1024
ConfigStruct CONFIG;
/**
* \note The opening curly bracket should be at line 39, so the code lines up
* with the definition code in util.h.
*/
void Config_init(void)
{
/*---------------- Network related --------------*/
CONFIG.http_username = NULL;
CONFIG.http_password = NULL;
CONFIG.proxy = NULL;
CONFIG.proxy_username = NULL;
CONFIG.proxy_password = NULL;
CONFIG.max_conns = DEFAULT_NETWORK_MAX_CONNS;
CONFIG.user_agent = DEFAULT_USER_AGENT;
CONFIG.http_wait_sec = DEFAULT_HTTP_WAIT_SEC;
/*--------------- Cache related ---------------*/
CONFIG.cache_enabled = 0;
CONFIG.cache_dir = NULL;
CONFIG.data_blksz = DEFAULT_DATA_BLKSZ;
CONFIG.max_segbc = DEFAULT_MAX_SEGBC;
/*-------------- Subsonic related -------------*/
CONFIG.sonic_mode = 0;
CONFIG.sonic_username = NULL;
CONFIG.sonic_password = NULL;
}
char *path_append(const char *path, const char *filename)
{
int needs_separator = 0;
@ -123,3 +199,4 @@ void *CALLOC(size_t nmemb, size_t size)
}
return ptr;
}

View File

@ -21,6 +21,61 @@
*/
#define MAX_FILENAME_LEN 255
/**
* \brief the default user agent string
*/
#define DEFAULT_USER_AGENT "HTTPDirFS-" VERSION
/**
* \brief configuration data structure
* \note The opening curly bracket should be at line 39, so the code belong
* lines up with the initialisation code in util.c
*/
typedef struct {
/** \brief HTTP username */
char *http_username;
/** \brief HTTP password */
char *http_password;
/** \brief HTTP proxy URL */
char *proxy;
/** \brief HTTP proxy username */
char *proxy_username;
/** \brief HTTP proxy password */
char *proxy_password;
/** \brief HTTP maximum connection count */
long max_conns;
/** \brief HTTP user agent*/
char *user_agent;
/** \brief The waiting time after getting HTTP 429 (too many requests) */
int http_wait_sec;
/** \brief Whether cache mode is enabled */
int cache_enabled;
/** \brief The cache location*/
char *cache_dir;
/** \brief The size of each download segment for cache mode */
int data_blksz;
/** \brief The maximum segment count for a single cache file */
int max_segbc;
/** \brief Whether we are using the Subsonic mode */
int sonic_mode;
/** \brief The Subsonic server username */
char *sonic_username;
/** \brief The Subsonic server password */
char *sonic_password;
} ConfigStruct;
/**
* \brief The Configuration data structure
*/
extern ConfigStruct CONFIG;
/**
* \brief append a path
* \details This function appends a path with the next level, while taking the
@ -74,4 +129,9 @@ char *generate_md5sum(const char *str);
*/
void *CALLOC(size_t nmemb, size_t size);
/**
* \brief initialise the configuration data structure
*/
void Config_init(void);
#endif