Rewrite the entire API just to eliminate heap allocations

Just bumping minor because nobody uses this and I don't want to jump up to 2.0.0
This commit is contained in:
apio 2022-12-25 14:33:47 +01:00
parent 89bc990725
commit 7d4e774cf7
Signed by: apio
GPG Key ID: B8A7D06E42258954
5 changed files with 68 additions and 112 deletions

View File

@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.8..3.22) cmake_minimum_required(VERSION 3.8..3.22)
project(minitar LANGUAGES C VERSION 1.2.1) project(minitar LANGUAGES C VERSION 1.3.0)
set(SOURCES set(SOURCES
src/tar.c src/tar.c

View File

@ -21,21 +21,19 @@ int main(int argc, char** argv)
fprintf(stderr, "Usage: %s [file]\n", argv[0]); fprintf(stderr, "Usage: %s [file]\n", argv[0]);
return 1; return 1;
} }
struct minitar* mp = minitar_open(argv[1]); struct minitar mp;
if(!mp) if(minitar_open(argv[1], &mp) != 0)
{ {
perror(argv[1]); perror(argv[1]);
return 1; return 1;
} }
struct minitar_entry* entry; struct minitar_entry entry;
do { do {
entry = minitar_read_entry(mp); if(minitar_read_entry(&mp, &entry) == 0) {
if(entry) { printf("%s\n", entry.metadata.path);
printf("%s\n", entry->metadata.path); } else break;
minitar_free_entry(entry); } while(true);
} minitar_close(&mp);
} while(entry);
minitar_close(mp);
} }
``` ```
@ -47,27 +45,20 @@ The user-facing API (functions defined in `minitar.h` and documented in this REA
## Functions ## Functions
### minitar_open ### minitar_open
`struct minitar* minitar_open(const char* pathname)` `int minitar_open(const char* pathname, struct minitar* mp)`
Opens a tar archive for reading, and returns a heap-allocated `struct minitar` which must be freed with `minitar_close()` after using it. If opening the file or allocating the struct fails, returns NULL. Initializes the caller-provided `mp` structure by opening the archive pointed to by `pathname` for reading. Returns 0 on success, anything else is failure.
A `struct minitar` is opaque, and should only be passed to other minitar functions. You should not care about its contents.
### minitar_read_entry ### minitar_read_entry
`struct minitar_entry* minitar_read_entry(struct minitar* mp)` `int minitar_read_entry(struct minitar* mp, struct minitar_entry* out)`
Reads the next entry from a `struct minitar` which should be the return value of a previous call to `minitar_open()`. The return value is a heap-allocated `struct minitar_entry`, which should be freed with `minitar_free_entry()` when no longer needed. Reads the next entry from a `struct minitar` which should be initialized by a previous call to `minitar_open()` and stores the result in `out`.
This structure consists of the file metadata (in the `metadata` field), and other internally-used values. The `minitar_entry` structure consists of the file metadata (in the `metadata` field), and other internally-used values.
To read the contents of an entry, you should allocate a buffer large enough to hold `metadata.size` bytes and pass it to `minitar_read_contents()`. To read the contents of an entry, you should allocate a buffer large enough to hold `metadata.size` bytes and pass it to `minitar_read_contents()`.
This function returns NULL on end-of-file (when all entries have been read). This function returns 0 on success and -1 on end-of-file (when all entries have been read).
### minitar_free_entry
`void minitar_free_entry(struct minitar_entry* entry)`
Frees the heap-allocated `struct minitar_entry`. The pointer passed to `minitar_free_entry()` should be the return value of a previous call to `minitar_read_entry()`, `minitar_find_by_name()`, `minitar_find_by_path()` or `minitar_find_any_of()`.
### minitar_rewind ### minitar_rewind
`void minitar_rewind(struct minitar* mp)` `void minitar_rewind(struct minitar* mp)`
@ -75,26 +66,26 @@ Frees the heap-allocated `struct minitar_entry`. The pointer passed to `minitar_
Rewinds the `struct minitar` back to the beginning of the archive file, which means that the next call to `minitar_read_entry()` will return the first entry instead of the entry after the last read entry. Rewinds the `struct minitar` back to the beginning of the archive file, which means that the next call to `minitar_read_entry()` will return the first entry instead of the entry after the last read entry.
### minitar_find_by_name ### minitar_find_by_name
`struct minitar_entry* minitar_find_by_name(struct minitar* mp, const char* name)` `int minitar_find_by_name(struct minitar* mp, const char* name, struct minitar_entry* out)`
Returns the first entry with a matching name, or NULL if none are found. The return value is a `struct minitar_entry`, which is heap-allocated and should be freed after use with `minitar_free_entry()`. This structure is already documented in the entry documenting `minitar_read_entry()`. Stores the first entry with a matching name in `out` and returns 0, or non-zero if none are found. In this case, the state of `out` is unspecified and might have been changed by the function.
This function starts searching from the current archive position, which means that to find a matching entry in the entire archive `minitar_rewind()` should be called on it first. This function starts searching from the current archive position, which means that to find a matching entry in the entire archive `minitar_rewind()` should be called on it first.
The state of `mp` after `minitar_find_by_name()` returns is unspecified, but a successive call to `minitar_find_by_name()` will return the next matching entry, if there is one. (Calling `minitar_find_by_name()` in a loop until it returns NULL will return all matching entries.) The state of `mp` after `minitar_find_by_name()` returns is unspecified, but a successive call to `minitar_find_by_name()` will find the next matching entry, if there is one. (Calling `minitar_find_by_name()` in a loop until it returns non-zero will return all matching entries.)
In order to perform other minitar operations on the archive, `minitar_rewind()` should probably be called first, to get a known state. In order to perform other minitar operations on the archive, `minitar_rewind()` should probably be called first, to get a known state.
### minitar_find_by_path ### minitar_find_by_path
`struct minitar_entry* minitar_find_by_path(struct minitar* mp, const char* path)` `int minitar_find_by_path(struct minitar* mp, const char* path, struct minitar_entry* out)`
Same as `minitar_find_by_name()`, but matches the full path inside the archive instead of the file name. Same as `minitar_find_by_name()`, but matches the full path inside the archive instead of the file name.
### minitar_find_any_of ### minitar_find_any_of
`struct minitar_entry* minitar_find_any_of(struct minitar* mp, enum minitar_file_type type)` `int minitar_find_any_of(struct minitar* mp, enum minitar_file_type type, struct minitar_entry* out)`
Same as `minitar_find_by_name()`, but matches the file type instead of the name. As with `minitar_find_by_name()`, this function starts searching from the current archive position and calling it in a loop until it returns NULL will return all matching entries. Same as `minitar_find_by_name()`, but matches the file type instead of the name. As with `minitar_find_by_name()`, this function starts searching from the current archive position and calling it in a loop until it returns -1 will find all matching entries.
### minitar_read_contents ### minitar_read_contents
`size_t minitar_read_contents(struct minitar* mp, struct minitar_entry* entry, char* buf, size_t max)` `size_t minitar_read_contents(struct minitar* mp, struct minitar_entry* entry, char* buf, size_t max)`
@ -112,7 +103,7 @@ The contents are not null-terminated. If you want null-termination (keep in mind
### minitar_close ### minitar_close
`int minitar_close(struct minitar* mp)` `int minitar_close(struct minitar* mp)`
Closes the tar archive file `mp` points to and frees the heap memory it was using. The pointer passed to `minitar_close()` should be the return value of a previous call to `minitar_open()`. Closes the tar archive file `mp` points to. The pointer passed to `minitar_close()` should be the return value of a previous call to `minitar_open()`.
Returns 0 on success, everything else is failure and you should check `errno`. Returns 0 on success, everything else is failure and you should check `errno`.

View File

@ -3,14 +3,10 @@
#include <stdio.h> #include <stdio.h>
#include <sys/types.h> #include <sys/types.h>
#ifdef _IN_MINITAR
struct minitar struct minitar
{ {
FILE* stream; FILE* stream;
}; };
#else
struct minitar;
#endif
enum minitar_file_type enum minitar_file_type
{ {
@ -43,13 +39,12 @@ extern "C"
{ {
#endif #endif
struct minitar* minitar_open(const char* pathname); int minitar_open(const char* pathname, struct minitar* out);
struct minitar_entry* minitar_read_entry(struct minitar* mp); int minitar_read_entry(struct minitar* mp, struct minitar_entry* out);
void minitar_free_entry(struct minitar_entry* entry);
void minitar_rewind(struct minitar* mp); void minitar_rewind(struct minitar* mp);
struct minitar_entry* minitar_find_by_name(struct minitar* mp, const char* name); int minitar_find_by_name(struct minitar* mp, const char* name, struct minitar_entry* out);
struct minitar_entry* minitar_find_by_path(struct minitar* mp, const char* path); int minitar_find_by_path(struct minitar* mp, const char* path, struct minitar_entry* out);
struct minitar_entry* minitar_find_any_of(struct minitar* mp, enum minitar_file_type type); int minitar_find_any_of(struct minitar* mp, enum minitar_file_type type, struct minitar_entry* out);
size_t minitar_read_contents(struct minitar* mp, struct minitar_entry* entry, char* buf, size_t max); size_t minitar_read_contents(struct minitar* mp, struct minitar_entry* entry, char* buf, size_t max);
int minitar_close(struct minitar* mp); int minitar_close(struct minitar* mp);

102
src/tar.c
View File

@ -1,4 +1,3 @@
#define _IN_MINITAR
#include "tar.h" #include "tar.h"
#include "minitar.h" #include "minitar.h"
#include <stdlib.h> #include <stdlib.h>
@ -8,73 +7,62 @@
int minitar_read_header(struct minitar*, struct tar_header*); int minitar_read_header(struct minitar*, struct tar_header*);
int minitar_validate_header(const struct tar_header*); int minitar_validate_header(const struct tar_header*);
void minitar_parse_metadata_from_tar_header(const struct tar_header*, struct minitar_entry_metadata*); void minitar_parse_metadata_from_tar_header(const struct tar_header*, struct minitar_entry_metadata*);
struct minitar_entry* minitar_dup_entry(const struct minitar_entry*);
size_t minitar_align_up_to_block_size(size_t); size_t minitar_align_up_to_block_size(size_t);
struct minitar* minitar_open(const char* pathname) int minitar_open(const char* pathname, struct minitar* out)
{ {
FILE* fp = FILE* fp =
fopen(pathname, fopen(pathname,
"rb"); // On some systems, opening the file in binary mode might be necessary to read the file properly. "rb"); // On some systems, opening the file in binary mode might be necessary to read the file properly.
if (!fp) return NULL; if (!fp) return -1;
struct minitar* mp = malloc(sizeof(struct minitar)); out->stream = fp;
if (!mp) return 0;
{
fclose(fp);
return NULL;
}
mp->stream = fp;
return mp;
} }
int minitar_close(struct minitar* mp) int minitar_close(struct minitar* mp)
{ {
int rc = fclose(mp->stream); return fclose(mp->stream);
free(mp);
return rc;
} }
// Try to read a valid header, and construct an entry from it. If the 512-byte block at the current read offset is not a // Try to read a valid header, and construct an entry from it. If the 512-byte block at the current read offset is not a
// valid header, valid is set to 0 so we can try again with the next block. In any other case, valid is set to 1. This // valid header, valid is set to 0 so we can try again with the next block. In any other case, valid is set to 1. This
// helps distinguish valid return values, null pointers that should be returned to the user (for example, EOF), and // helps distinguish valid return values, null pointers that should be returned to the user (for example, EOF), and
// invalid headers where we should just try again until we find a valid one. // invalid headers where we should just try again until we find a valid one.
static struct minitar_entry* minitar_try_to_read_valid_entry(struct minitar* mp, int* valid) static int minitar_try_to_read_valid_entry(struct minitar* mp, struct minitar_entry* out, int* valid)
{ {
struct minitar_entry entry;
struct tar_header hdr; struct tar_header hdr;
*valid = 1; *valid = 1;
if (!minitar_read_header(mp, &hdr)) return NULL; if (!minitar_read_header(mp, &hdr)) return -1;
if (!minitar_validate_header(&hdr)) if (!minitar_validate_header(&hdr))
{ {
*valid = 0; *valid = 0;
return NULL; return -1;
} }
// Fetch the current read position (which is currently pointing to the start of the entry's contents), so we can // Fetch the current read position (which is currently pointing to the start of the entry's contents), so we can
// return back to it when reading the contents of this entry using minitar_read_contents(). // return back to it when reading the contents of this entry using minitar_read_contents().
if (fgetpos(mp->stream, &entry.position)) return NULL; if (fgetpos(mp->stream, &out->position)) return -1;
minitar_parse_metadata_from_tar_header(&hdr, &entry.metadata); minitar_parse_metadata_from_tar_header(&hdr, &out->metadata);
if (entry.metadata.size) if (out->metadata.size)
{ {
size_t size_in_archive = minitar_align_up_to_block_size(entry.metadata.size); size_t size_in_archive = minitar_align_up_to_block_size(out->metadata.size);
if (fseek(mp->stream, size_in_archive, if (fseek(mp->stream, size_in_archive,
SEEK_CUR)) // move over to the next block, skipping over the file contents SEEK_CUR)) // move over to the next block, skipping over the file contents
{ {
return NULL; return -1;
} }
} }
return minitar_dup_entry(&entry); return 0;
} }
struct minitar_entry* minitar_read_entry(struct minitar* mp) int minitar_read_entry(struct minitar* mp, struct minitar_entry* out)
{ {
int valid; int valid, result;
struct minitar_entry* result;
do { do {
result = minitar_try_to_read_valid_entry(mp, &valid); result = minitar_try_to_read_valid_entry(mp, out, &valid);
} while (!valid); // Skip over invalid entries } while (!valid); // Skip over invalid entries
return result; return result;
} }
@ -84,51 +72,43 @@ void minitar_rewind(struct minitar* mp)
rewind(mp->stream); rewind(mp->stream);
} }
void minitar_free_entry(struct minitar_entry* entry) int minitar_find_by_name(struct minitar* mp, const char* name, struct minitar_entry* out)
{ {
free(entry); int rc;
do {
rc = minitar_read_entry(mp, out);
if (rc == 0)
{
if (!strcmp(out->metadata.name, name)) return 0;
}
} while (rc == 0);
return -1;
} }
struct minitar_entry* minitar_find_by_name(struct minitar* mp, const char* name) int minitar_find_by_path(struct minitar* mp, const char* path, struct minitar_entry* out)
{ {
struct minitar_entry* entry; int rc;
do { do {
entry = minitar_read_entry(mp); rc = minitar_read_entry(mp, out);
if (entry) if (rc == 0)
{ {
if (!strcmp(entry->metadata.name, name)) return entry; if (!strcmp(out->metadata.path, path)) return 0;
minitar_free_entry(entry);
} }
} while (entry); } while (rc == 0);
return NULL; return -1;
} }
struct minitar_entry* minitar_find_by_path(struct minitar* mp, const char* path) int minitar_find_any_of(struct minitar* mp, enum minitar_file_type type, struct minitar_entry* out)
{ {
struct minitar_entry* entry; int rc;
do { do {
entry = minitar_read_entry(mp); rc = minitar_read_entry(mp, out);
if (entry) if (rc == 0)
{ {
if (!strcmp(entry->metadata.path, path)) return entry; if (out->metadata.type == type) return 0;
minitar_free_entry(entry);
} }
} while (entry); } while (rc == 0);
return NULL; return -1;
}
struct minitar_entry* minitar_find_any_of(struct minitar* mp, enum minitar_file_type type)
{
struct minitar_entry* entry;
do {
entry = minitar_read_entry(mp);
if (entry)
{
if (entry->metadata.type == type) return entry;
minitar_free_entry(entry);
}
} while (entry);
return NULL;
} }
size_t minitar_read_contents(struct minitar* mp, struct minitar_entry* entry, char* buf, size_t max) size_t minitar_read_contents(struct minitar* mp, struct minitar_entry* entry, char* buf, size_t max)

View File

@ -1,4 +1,3 @@
#define _IN_MINITAR
#include "minitar.h" #include "minitar.h"
#include "tar.h" #include "tar.h"
#include <stdio.h> #include <stdio.h>
@ -182,13 +181,4 @@ int minitar_read_header(struct minitar* mp, struct tar_header* hdr)
if (rc == 0 && ferror(mp->stream)) minitar_panic("Error while reading file header from tar archive"); if (rc == 0 && ferror(mp->stream)) minitar_panic("Error while reading file header from tar archive");
if (rc < sizeof *hdr) minitar_panic("Valid tar files should be split in 512-byte blocks"); if (rc < sizeof *hdr) minitar_panic("Valid tar files should be split in 512-byte blocks");
return 1; return 1;
}
// Create a heap-allocated copy of an entry on the stack.
struct minitar_entry* minitar_dup_entry(const struct minitar_entry* original)
{
struct minitar_entry* new = malloc(sizeof *original);
if (!new) return NULL;
memcpy(new, original, sizeof *new);
return new;
} }