diff --git a/CMakeLists.txt b/CMakeLists.txt index 40fc5d9..6e65ecf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -117,7 +117,11 @@ add_library(aaruformat SHARED include/aaruformat/consts.h include/aaruformat/enu src/blocks/dump.c src/blocks/checksum.c src/index/index_v3.c - src/ddt/ddt_v2.c) + src/ddt/ddt_v2.c + include/aaruformat/structs/options.h + src/options.c + src/create.c + src/time.c) include_directories(include include/aaruformat) diff --git a/include/aaruformat/context.h b/include/aaruformat/context.h index 61ea7d6..70fd81e 100644 --- a/include/aaruformat/context.h +++ b/include/aaruformat/context.h @@ -115,7 +115,7 @@ typedef struct aaruformatContext uint64_t cachedDdtOffset; uint16_t *cachedSecondaryDdtSmall; uint32_t *cachedSecondaryDdtBig; - + bool isWriting; } aaruformatContext; typedef struct DumpHardwareEntriesWithData diff --git a/include/aaruformat/decls.h b/include/aaruformat/decls.h index 7cbfc3a..c49a29e 100644 --- a/include/aaruformat/decls.h +++ b/include/aaruformat/decls.h @@ -62,6 +62,11 @@ AARU_EXPORT int AARU_CALL aaruf_identify_stream(FILE *imageStream); AARU_EXPORT void *AARU_CALL aaruf_open(const char *filepath); +AARU_EXPORT void *AARU_CALL aaruf_create(const char *filepath, uint32_t mediaType, uint32_t sectorSize, + uint64_t userSectors, uint64_t negativeSectors, uint64_t overflowSectors, + const char *options, const uint8_t *applicationName, + uint8_t applicationMajorVersion, uint8_t applicationMinorVersion); + AARU_EXPORT int AARU_CALL aaruf_close(void *context); AARU_EXPORT int32_t AARU_CALL aaruf_read_media_tag(void *context, uint8_t *data, int32_t tag, uint32_t *length); diff --git a/include/aaruformat/errors.h b/include/aaruformat/errors.h index 670d72b..faca2ff 100644 --- a/include/aaruformat/errors.h +++ b/include/aaruformat/errors.h @@ -37,6 +37,9 @@ #define AARUF_ERROR_SECTOR_TAG_NOT_PRESENT -16 #define AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK -17 #define AARUF_ERROR_INVALID_BLOCK_CRC -18 +#define AARUF_ERROR_CANNOT_CREATE_FILE -19 +#define AARUF_ERROR_INVALID_APP_NAME_LENGTH -20 +#define AARUF_ERROR_CANNOT_WRITE_HEADER -21 #define AARUF_STATUS_OK 0 #define AARUF_STATUS_SECTOR_NOT_DUMPED 1 diff --git a/include/aaruformat/structs.h b/include/aaruformat/structs.h index f0b193f..58e0445 100644 --- a/include/aaruformat/structs.h +++ b/include/aaruformat/structs.h @@ -37,6 +37,7 @@ #include "structs/index.h" #include "structs/metadata.h" #include "structs/optical.h" +#include "structs/options.h" #endif // LIBAARUFORMAT_STRUCTS_H diff --git a/include/aaruformat/structs/options.h b/include/aaruformat/structs/options.h new file mode 100644 index 0000000..583df16 --- /dev/null +++ b/include/aaruformat/structs/options.h @@ -0,0 +1,37 @@ +/* + * This file is part of the Aaru Data Preservation Suite. + * Copyright (c) 2019-2025 Natalia Portillo. + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#ifndef LIBAARUFORMAT_OPTIONS_H +#define LIBAARUFORMAT_OPTIONS_H + +typedef struct +{ + bool compress; + bool deduplicate; + uint32_t dictionary; + uint8_t table_shift; + uint8_t data_shift; + uint8_t block_alignment; + bool md5; + bool sha1; + bool sha256; + bool blake3; + bool spamsum; +} aaru_options; + +#endif // LIBAARUFORMAT_OPTIONS_H diff --git a/include/internal.h b/include/internal.h index 43a7c8c..5121fec 100644 --- a/include/internal.h +++ b/include/internal.h @@ -45,5 +45,7 @@ int32_t decode_ddt_single_level_v2(aaruformatContext *ctx, uint64_t sectorAddr uint64_t *blockOffset, uint8_t *sectorStatus); int32_t decode_ddt_multi_level_v2(aaruformatContext *ctx, uint64_t sectorAddress, uint64_t *offset, uint64_t *blockOffset, uint8_t *sectorStatus); +aaru_options parse_options(const char *options); +uint64_t get_filetime_uint64(); #endif // LIBAARUFORMAT_INTERNAL_H diff --git a/src/close.c b/src/close.c index a5e1a7e..a792dde 100644 --- a/src/close.c +++ b/src/close.c @@ -47,6 +47,19 @@ int aaruf_close(void *context) return -1; } + if(ctx->isWriting) + { + // Write the header at the beginning of the file + fseek(ctx->imageStream, 0, SEEK_SET); + if(fwrite(&ctx->header, sizeof(AaruHeaderV2), 1, ctx->imageStream) != 1) + { + fclose(ctx->imageStream); + ctx->imageStream = NULL; + errno = AARUF_ERROR_CANNOT_WRITE_HEADER; + return -1; + } + } + // This may do nothing if imageStream is NULL, but as the behaviour is undefined, better sure than sorry if(ctx->imageStream != NULL) { diff --git a/src/create.c b/src/create.c new file mode 100644 index 0000000..fd509c2 --- /dev/null +++ b/src/create.c @@ -0,0 +1,145 @@ +/* + * This file is part of the Aaru Data Preservation Suite. + * Copyright (c) 2019-2025 Natalia Portillo. + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +#include +#include +#include +#include +#include +#include + +#include "aaruformat.h" +#include "internal.h" + +void *aaruf_create(const char *filepath, uint32_t mediaType, uint32_t sectorSize, uint64_t userSectors, + uint64_t negativeSectors, uint64_t overflowSectors, const char *options, + const uint8_t *applicationName, uint8_t applicationNameLength, uint8_t applicationMajorVersion, + uint8_t applicationMinorVersion); +{ + // Parse the options + aaru_options parsedOptions = parse_options(options); + + // Allocate context + aaruformatContext *ctx = (aaruformatContext *)malloc(sizeof(aaruformatContext)); + if(ctx == NULL) + { + errno = AARUF_ERROR_NOT_ENOUGH_MEMORY; + return NULL; + } + + memset(ctx, 0, sizeof(aaruformatContext)); + + // Create the image file + ctx->imageStream = fopen(filepath, "wb+"); + if(ctx->imageStream == NULL) + { + free(ctx); + errno = AARUF_ERROR_CANNOT_CREATE_FILE; + return NULL; + } + + if(applicationNameLength > AARU_HEADER_APP_NAME_LEN) + { + free(ctx); + errno = AARUF_ERROR_INVALID_APP_NAME_LENGTH; + return NULL; + } + + // Initialize header + ctx->header.identifier = AARU_MAGIC; + memcpy(ctx->header.application, applicationName, applicationNameLength); + ctx->header.imageMajorVersion = AARUF_VERSION_V2; + ctx->header.imageMinorVersion = 0; + ctx->header.applicationMajorVersion = applicationMajorVersion; + ctx->header.applicationMinorVersion = applicationMinorVersion; + ctx->header.mediaType = mediaType; + ctx->header.indexOffset = 0; + ctx->header.creationTime = get_filetime_uint64(); + ctx->header.lastWrittenTime = get_filetime_uint64(); + + ctx->readableSectorTags = (bool *)malloc(sizeof(bool) * MaxSectorTag); + + if(ctx->readableSectorTags == NULL) + { + free(ctx); + errno = AARUF_ERROR_NOT_ENOUGH_MEMORY; + + return NULL; + } + + memset(ctx->readableSectorTags, 0, sizeof(bool) * MaxSectorTag); + + // Initilize image info + ctx->imageInfo.Application = ctx->header.application; + ctx->imageInfo.ApplicationVersion = (uint8_t *)malloc(32); + if(ctx->imageInfo.ApplicationVersion != NULL) + { + memset(ctx->imageInfo.ApplicationVersion, 0, 32); + sprintf((char *)ctx->imageInfo.ApplicationVersion, "%d.%d", ctx->header.applicationMajorVersion, + ctx->header.applicationMinorVersion); + } + ctx->imageInfo.Version = (uint8_t *)malloc(32); + if(ctx->imageInfo.Version != NULL) + { + memset(ctx->imageInfo.Version, 0, 32); + sprintf((char *)ctx->imageInfo.Version, "%d.%d", ctx->header.imageMajorVersion, ctx->header.imageMinorVersion); + } + ctx->imageInfo.MediaType = ctx->header.mediaType; + ctx->imageInfo.ImageSize = 0; + ctx->imageInfo.CreationTime = ctx->header.creationTime; + ctx->imageInfo.LastModificationTime = ctx->header.lastWrittenTime; + ctx->imageInfo.XmlMediaType = aaruf_get_xml_mediatype(ctx->header.mediaType); + ctx->imageInfo.SectorSize = sectorSize; + + // Initialize caches + ctx->blockHeaderCache.cache = NULL; + ctx->blockHeaderCache.max_items = MAX_CACHE_SIZE / (ctx->imageInfo.SectorSize * (1 << ctx->shift)); + ctx->blockCache.cache = NULL; + ctx->blockCache.max_items = ctx->blockHeaderCache.max_items; + + // TODO: Cache tracks and sessions? + + // Initialize ECC for Compact Disc + ctx->eccCdContext = (CdEccContext *)aaruf_ecc_cd_init(); + + ctx->magic = AARU_MAGIC; + ctx->libraryMajorVersion = LIBAARUFORMAT_MAJOR_VERSION; + ctx->libraryMinorVersion = LIBAARUFORMAT_MINOR_VERSION; + + // Initialize DDT2 + ctx->inMemoryDdt = true; + ctx->userDataDdtHeader.identifier = DeDuplicationTable2; + ctx->userDataDdtHeader.type = UserData; + ctx->userDataDdtHeader.compression = None; + ctx->userDataDdtHeader.levels = 2; + ctx->userDataDdtHeader.tableLevel = 0; + ctx->userDataDdtHeader.previousLevelOffset = 0; + ctx->userDataDdtHeader.negative = negativeSectors; + ctx->userDataDdtHeader.blocks = userSectors + overflowSectors + negativeSectors; + ctx->userDataDdtHeader.overflow = overflowSectors; + ctx->userDataDdtHeader.start = 0; + ctx->userDataDdtHeader.blockAlignmentShift = parsedOptions.block_alignment; + ctx->userDataDdtHeader.dataShift = parsedOptions.data_shift; + ctx->userDataDdtHeader.tableShift = parsedOptions.table_shift; + ctx->userDataDdtHeader.sizeType = 1; + ctx->userDataDdtHeader.entries = ctx->userDataDdtHeader.blocks / (1 << ctx->userDataDdtHeader.tableShift); + + if(ctx->userDataDdtHeader.blocks % (1 << ctx->userDataDdtHeader.tableShift) != 0) ctx->userDataDdtHeader.entries++; + + // Is writing + ctx->isWriting = true; +} diff --git a/src/options.c b/src/options.c new file mode 100644 index 0000000..9d00e11 --- /dev/null +++ b/src/options.c @@ -0,0 +1,96 @@ +/* + * This file is part of the Aaru Data Preservation Suite. + * Copyright (c) 2019-2025 Natalia Portillo. + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ +#include +#include +#include +#include +#include + +#include "internal.h" +#include "structs/options.h" + +aaru_options parse_options(const char *options) +{ + aaru_options parsed = {.compress = true, + .deduplicate = true, + .dictionary = 33554432, + .table_shift = 9, + .data_shift = 12, + .block_alignment = 9, + .md5 = false, + .sha1 = false, + .sha256 = false, + .blake3 = false, + .spamsum = false}; + + char buffer[1024]; + strncpy(buffer, options, sizeof(buffer)); + buffer[sizeof(buffer) - 1] = '\0'; + + const char *token = strtok(buffer, ";"); + while(token != NULL) + { + char *equal = strchr(token, '='); + if(equal) + { + *equal = '\0'; + const char *key = token; + const char *value = equal + 1; + + bool bval = strncmp(value, "true", 4) == 0; + + if(strncmp(key, "compress", 8) == 0) + parsed.compress = bval; + else if(strncmp(key, "deduplicate", 11) == 0) + parsed.deduplicate = bval; + else if(strncmp(key, "dictionary", 10) == 0) + { + parsed.dictionary = (uint32_t)strtoul(value, NULL, 10); + if(parsed.dictionary == 0) parsed.dictionary = 33554432; + } + else if(strncmp(key, "table_shift", 11) == 0) + { + parsed.table_shift = (uint8_t)atoi(value); + if(parsed.table_shift == 0) parsed.table_shift = 9; + } + else if(strncmp(key, "data_shift", 10) == 0) + { + parsed.data_shift = (uint8_t)atoi(value); + if(parsed.data_shift == 0) parsed.data_shift = 12; + } + else if(strncmp(key, "block_alignment", 15) == 0) + { + parsed.block_alignment = (uint8_t)atoi(value); + if(parsed.block_alignment == 0) parsed.block_alignment = 9; + } + else if(strncmp(key, "md5", 3) == 0) + parsed.md5 = bval; + else if(strncmp(key, "sha1", 4) == 0) + parsed.sha1 = bval; + else if(strncmp(key, "sha256", 6) == 0) + parsed.sha256 = bval; + else if(strncmp(key, "blake3", 6) == 0) + parsed.blake3 = bval; + else if(strncmp(key, "spamsum", 7) == 0) + parsed.spamsum = bval; + } + token = strtok(NULL, ";"); + } + + return parsed; +} \ No newline at end of file diff --git a/src/time.c b/src/time.c new file mode 100644 index 0000000..294f396 --- /dev/null +++ b/src/time.c @@ -0,0 +1,46 @@ +/* + * This file is part of the Aaru Data Preservation Suite. + * Copyright (c) 2019-2025 Natalia Portillo. + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include +#include + +#ifdef _WIN32 +#include + +uint64_t get_filetime_uint64() +{ + FILETIME ft; + SYSTEMTIME st; + GetSystemTime(&st); // UTC time + SystemTimeToFileTime(&st, &ft); + return ((uint64_t)ft.dwHighDateTime << 32) | ft.dwLowDateTime; +} + +#else +#include + +uint64_t get_filetime_uint64() +{ + struct timeval tv; + gettimeofday(&tv, NULL); // seconds + microseconds since 1970 + + const uint64_t EPOCH_DIFF = 11644473600ULL; // seconds between 1601 and 1970 + uint64_t ft = (tv.tv_sec + EPOCH_DIFF) * 10000000ULL + tv.tv_usec * 10; + return ft; +} +#endif