summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAidan MacDonald <amachronic@protonmail.com>2021-11-26 14:37:36 +0000
committerAidan MacDonald <amachronic@protonmail.com>2021-11-27 13:13:35 +0000
commitc1709e3194855b93ffd6f68eaa750c6704b3e52f (patch)
tree485af751c5a6ee3cd85baf6be117099fbbf1f3ec
parent4052a9ddcfedcb6bc55f94e4ec5bc8e803e2dc7e (diff)
downloadrockbox-c1709e3194855b93ffd6f68eaa750c6704b3e52f.tar.gz
rockbox-c1709e3194855b93ffd6f68eaa750c6704b3e52f.zip
microtar: Update to latest upstream commit
This is a major overhaul of the library with some API changes and ease of use improvements, particularly for creating new archives. The updated functionality is intended to support a new X1000 installer framework. Change-Id: Icf192bb546831231d49303fbf529ef1c1ce8905c
-rw-r--r--lib/microtar/.gitignore3
-rw-r--r--lib/microtar/LICENSE1
-rw-r--r--lib/microtar/Makefile25
-rw-r--r--lib/microtar/README.md312
-rw-r--r--lib/microtar/README.rockbox14
-rw-r--r--lib/microtar/mtar.c243
-rw-r--r--lib/microtar/src/microtar-rockbox.c115
-rw-r--r--lib/microtar/src/microtar-rockbox.h29
-rw-r--r--lib/microtar/src/microtar-stdio.c113
-rw-r--r--lib/microtar/src/microtar-stdio.h39
-rw-r--r--lib/microtar/src/microtar.c886
-rw-r--r--lib/microtar/src/microtar.h193
12 files changed, 1415 insertions, 558 deletions
diff --git a/lib/microtar/.gitignore b/lib/microtar/.gitignore
new file mode 100644
index 0000000000..cccd994a85
--- /dev/null
+++ b/lib/microtar/.gitignore
@@ -0,0 +1,3 @@
1*.o
2*.a
3/mtar
diff --git a/lib/microtar/LICENSE b/lib/microtar/LICENSE
index 7e3bf17ccb..72cf80d63b 100644
--- a/lib/microtar/LICENSE
+++ b/lib/microtar/LICENSE
@@ -1,4 +1,5 @@
1Copyright (c) 2017 rxi 1Copyright (c) 2017 rxi
2Copyright (c) 2021 Aidan MacDonald
2 3
3Permission is hereby granted, free of charge, to any person obtaining a copy of 4Permission is hereby granted, free of charge, to any person obtaining a copy of
4this software and associated documentation files (the "Software"), to deal in 5this software and associated documentation files (the "Software"), to deal in
diff --git a/lib/microtar/Makefile b/lib/microtar/Makefile
new file mode 100644
index 0000000000..ae2c4f6d6e
--- /dev/null
+++ b/lib/microtar/Makefile
@@ -0,0 +1,25 @@
1CPPFLAGS = -Isrc
2CFLAGS = -std=c99 -Wall -Wextra -O2
3
4MTAR_OBJ = mtar.o
5MTAR_BIN = mtar
6
7MICROTAR_OBJ = src/microtar.o src/microtar-stdio.o
8MICROTAR_LIB = libmicrotar.a
9
10$(MTAR_BIN): $(MTAR_OBJ) $(MICROTAR_LIB)
11 $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $^
12
13$(MICROTAR_LIB): $(MICROTAR_OBJ)
14 $(AR) cr $@ $^
15
16%.o: %.c
17 $(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@
18
19src/microtar.o: src/microtar.h
20src/microtar-stdio.o: src/microtar.h src/microtar-stdio.h
21mtar.o: src/microtar.h src/microtar-stdio.h
22
23clean:
24 rm -f $(MICROTAR_LIB) $(MICROTAR_OBJ)
25 rm -f $(MTAR_BIN) $(MTAR_OBJ)
diff --git a/lib/microtar/README.md b/lib/microtar/README.md
index 18153caa21..109626cdd4 100644
--- a/lib/microtar/README.md
+++ b/lib/microtar/README.md
@@ -1,128 +1,268 @@
1# microtar 1# microtar
2A lightweight tar library written in ANSI C
3 2
3A lightweight tar library written in ANSI C.
4 4
5## Modifications from upstream 5This version is a fork of [rxi's microtar](https://github.com/rxi/microtar)
6with bugfixes and API changes aimed at improving usability, but still keeping
7with the minimal design of the original library.
6 8
7[Upstream](https://github.com/rxi/microtar) has numerous bugs and gotchas, 9## License
8which I fixed in order to improve the overall robustness of the library. 10
11This library is free software; you can redistribute it and/or modify it under
12the terms of the MIT license. See [LICENSE](LICENSE) for details.
9 13
10A summary of my changes, in no particular order:
11 14
12- Fix possible sscanf beyond the bounds of the input buffer 15## Supported format variants
13- Fix possible buffer overruns due to strcpy on untrusted input
14- Fix incorrect octal formatting by sprintf and possible output overrruns
15- Catch read/writes which are too big and handle them gracefully
16- Handle over-long names in `mtar_write_file_header` / `mtar_write_dir_header`
17- Ensure strings in `mtar_header_t` are always null-terminated
18- Save and load group information so we don't lose information
19- Move `mtar_open()` to `microtar-stdio.c` so `microtar.c` can be used in
20 a freestanding environment
21- Allow control of stack usage by moving temporary variables into `mtar_t`,
22 so the caller can decide whether to use the stack or heap
23 16
24An up-to-date copy of this modified version can be found 17No effort has been put into handling every tar format variant. Basically
25[here](https://github.com/amachronic/microtar). 18what is accepted is the "old-style" format, which appears to work well
19enough to access basic archives created by GNU `tar`.
26 20
27 21
28## Modifications for Rockbox 22## Basic usage
29 23
30Added file `microtar-rockbox.c` implementing `mtar_open()` with native 24The library consists of two files, `microtar.c` and `microtar.h`, which only
31Rockbox filesystem API. 25depend on a tiny part of the standard C library & can be easily incorporated
26into a host project's build system.
32 27
28The core library does not include any I/O hooks as these are supposed to be
29provided by the host application. If the C library's `fopen` and friends is
30good enough, you can use `microtar-stdio.c`.
33 31
34## Basic Usage
35The library consists of `microtar.c` and `microtar.h`. These two files can be
36dropped into an existing project and compiled along with it.
37 32
33### Initialization
34
35Initialization is very simple. Everything the library needs is contained in
36the `mtar_t` struct; there is no memory allocation and no global state. It is
37enough to zero-initialize an `mtar_t` object to put it into a "closed" state.
38You can use `mtar_is_open()` to query whether the archive is open or not.
39
40An archive can be opened for reading _or_ writing, but not both. You have to
41specify which access mode you're using when you create the archive.
38 42
39#### Reading
40```c 43```c
41mtar_t tar; 44mtar_t tar;
42mtar_header_t h; 45mtar_init(&tar, MTAR_READ, my_io_ops, my_stream);
43char *p; 46```
44 47
45/* Open archive for reading */ 48Or if using `microtar-stdio.c`:
46mtar_open(&tar, "test.tar", "r");
47 49
48/* Print all file names and sizes */ 50```c
49while ( (mtar_read_header(&tar, &h)) != MTAR_ENULLRECORD ) { 51int error = mtar_open(&tar, "file.tar", "rb");
50 printf("%s (%d bytes)\n", h.name, h.size); 52if(error) {
51 mtar_next(&tar); 53 /* do something about it */
52} 54}
55```
53 56
54/* Load and print contents of file "test.txt" */ 57Note that `mtar_init()` is called for you in this case and the access mode is
55mtar_find(&tar, "test.txt", &h); 58deduced from the mode flags.
56p = calloc(1, h.size + 1);
57mtar_read_data(&tar, p, h.size);
58printf("%s", p);
59free(p);
60 59
61/* Close archive */
62mtar_close(&tar);
63```
64 60
65#### Writing 61### Iterating and locating files
62
63If you opened an archive for reading, you'll likely want to iterate over
64all the files. Here's the long way of doing it:
65
66```c 66```c
67mtar_t tar; 67mtar_t tar;
68const char *str1 = "Hello world"; 68int err;
69const char *str2 = "Goodbye world"; 69
70/* Go to the start of the archive... Not necessary if you've
71 * just opened the archive and are already at the beginning.
72 * (And of course you normally want to check the return value.) */
73mtar_rewind(&tar);
70 74
71/* Open archive for writing */ 75/* Iterate over the archive members */
72mtar_open(&tar, "test.tar", "w"); 76while((err = mtar_next(&tar)) == MTAR_ESUCCESS) {
77 /* Get a pointer to the current file header. It will
78 * remain valid until you move to another record with
79 * mtar_next() or call mtar_rewind() */
80 const mtar_header_t* header = mtar_get_header(&tar);
73 81
74/* Write strings to files `test1.txt` and `test2.txt` */ 82 printf("%s (%d bytes)\n", header->name, header->size);
75mtar_write_file_header(&tar, "test1.txt", strlen(str1)); 83}
76mtar_write_data(&tar, str1, strlen(str1)); 84
77mtar_write_file_header(&tar, "test2.txt", strlen(str2)); 85if(err != MTAR_ENULLRECORD) {
78mtar_write_data(&tar, str2, strlen(str2)); 86 /* ENULLRECORD means we hit end of file; any
87 * other return value is an actual error. */
88}
89```
79 90
80/* Finalize -- this needs to be the last thing done before closing */ 91There's a useful shortcut for this type of iteration which removes the
81mtar_finalize(&tar); 92loop boilerplate, replacing it with another kind of boilerplate that may
93be more palatable in some cases.
94
95```c
96/* Will be called for each archive member visited by mtar_foreach().
97 * The member's header is passed in as an argument so you don't need
98 * to fetch it manually with mtar_get_header(). You can freely read
99 * data (if present) and seek around. There is no special cleanup
100 * required and it is not necessary to read to the end of the stream.
101 *
102 * The callback should return zero (= MTAR_SUCCESS) to continue the
103 * iteration or return nonzero to abort. On abort, the value returned
104 * by the callback will be returned from mtar_foreach(). Since it may
105 * also return normal microtar error codes, it is suggested to use a
106 * positive value or pass the result via 'arg'.
107 */
108int foreach_cb(mtar_t* tar, const mtar_header_t* header, void* arg)
109{
110 // ...
111 return 0;
112}
82 113
83/* Close archive */ 114void main()
84mtar_close(&tar); 115{
116 mtar_t tar;
117
118 // ...
119
120 int ret = mtar_foreach(&tar, foreach_cb, NULL);
121 if(ret < 0) {
122 /* Microtar error codes are negative and may be returned if
123 * there is a problem with the iteration. */
124 } else if(ret == MTAR_ESUCCESS) {
125 /* If the iteration reaches the end of the archive without
126 * errors, the return code is MTAR_ESUCCESS. */
127 } else if(ret > 0) {
128 /* Positive values might be returned by the callback to
129 * signal some condition was met; they'll never be returned
130 * by microtar */
131 }
132}
133```
134
135The other thing you're likely to do is look for a specific file:
136
137```c
138/* Seek to a specific member in the archive */
139int err = mtar_find(&tar, "foo.txt");
140if(err == MTAR_ESUCCESS) {
141 /* File was found -- read the header with mtar_get_header() */
142} else if(err == MTAR_ENOTFOUND) {
143 /* File wasn't in the archive */
144} else {
145 /* Some error occurred */
146}
85``` 147```
86 148
149Note this isn't terribly efficient since it scans the entire archive
150looking for the file.
151
152
153### Reading file data
154
155Once pointed at a file via `mtar_next()` or `mtar_find()` you can read the
156data with a simple POSIX-like API.
157
158- `mtar_read_data(tar, buf, count)` reads up to `count` bytes into `buf`,
159 returning the actual number of bytes read, or a negative error value.
160 If at EOF, this returns zero.
161
162- `mtar_seek_data(tar, offset, whence)` works exactly like `fseek()` with
163 `whence` being one of `SEEK_SET`, `SEEK_CUR`, or `SEEK_END` and `offset`
164 indicating a point relative to the beginning, current position, or end
165 of the file. Returns zero on success, or a negative error code.
166
167- `mtar_eof_data(tar)` returns nonzero if the end of the file has been
168 reached. It is possible to seek backward to clear this condition.
169
170
171### Writing archives
172
173Microtar has limited support for creating archives. When an archive is opened
174for writing, you can add new members using `mtar_write_header()`.
175
176- `mtar_write_header(tar, header)` writes out the header for a new member.
177 The amount of data that follows is dictated by `header->size`, though if
178 the underlying stream supports seeking and re-writing data, this size can
179 be updated later with `mtar_update_header()` or `mtar_update_file_size()`.
180
181- `mtar_update_header(tar, header)` will re-write the previously written
182 header. This may be used to change any header field. The underlying stream
183 must support seeking. On a successful return the stream will be returned
184 to the position it was at before the call.
185
186File data can be written with `mtar_write_data()`, and if the underlying stream
187supports seeking, you can seek with `mtar_seek_data()` and read back previously
188written data with `mtar_read_data()`. Note that it is not possible to truncate
189the file stream by any means.
190
191- `mtar_write_data(tar, buf, count)` will write up to `count` bytes from
192 `buf` to the current member's data. Returns the number of bytes actually
193 written or a negative error code.
194
195- `mtar_update_file_size(tar)` will update the header size to reflect the
196 actual amount of written data. This is intended to be called right before
197 `mtar_end_data()` if you are not declaring file sizes in advance.
198
199- `mtar_end_data(tar)` will end the current member. It will complain if you
200 did not write the correct amount data provided in the header. This must be
201 called before writing the next header.
202
203- `mtar_finalize(tar)` is called after you have written all members to the
204 archive. It writes out some null records which mark the end of the archive,
205 so you cannot write any more archive members after this.
206
207Note that `mtar_close()` can fail if there was a problem flushing buffered
208data to disk, so its return value should always be checked.
209
87 210
88## Error handling 211## Error handling
89All functions which return an `int` will return `MTAR_ESUCCESS` if the operation
90is successful. If an error occurs an error value less-than-zero will be
91returned; this value can be passed to the function `mtar_strerror()` to get its
92corresponding error string.
93 212
213Most functions that return `int` return an error code from `enum mtar_error`.
214Zero is success and all other error codes are negative. `mtar_strerror()` can
215return a string describing the error code.
94 216
95## Wrapping a stream 217A couple of functions use a different return value convention:
96If you want to read or write from something other than a file, the `mtar_t`
97struct can be manually initialized with your own callback functions and a
98`stream` pointer.
99 218
100All callback functions are passed a pointer to the `mtar_t` struct as their 219- `mtar_foreach()` may error codes or an arbitrary nonzero value provided
101first argument. They should return `MTAR_ESUCCESS` if the operation succeeds 220 by the callback.
102without an error, or an integer below zero if an error occurs. 221- `mtar_read_data()` and `mtar_write_data()` returns the number of bytes read
222 or written, or a negative error code. In particular zero means that no bytes
223 were read or written.
224- `mtar_get_header()` may return `NULL` if there is no valid header.
225 It is only possible to see a null pointer if misusing the API or after
226 a previous error so checking for this is usually not necessary.
103 227
104After the `stream` field has been set, all required callbacks have been set and 228There is essentially no support for error recovery. After an error you can
105all unused fields have been zeroset the `mtar_t` struct can be safely used with 229only do two things reliably: close the archive with `mtar_close()` or try
106the microtar functions. `mtar_open` *should not* be called if the `mtar_t` 230rewinding to the beginning with `mtar_rewind()`.
107struct was initialized manually.
108 231
109#### Reading
110The following callbacks should be set for reading an archive from a stream:
111 232
112Name | Arguments | Description 233## I/O hooks
113--------|------------------------------------------|---------------------------
114`read` | `mtar_t *tar, void *data, unsigned size` | Read data from the stream
115`seek` | `mtar_t *tar, unsigned pos` | Set the position indicator
116`close` | `mtar_t *tar` | Close the stream
117 234
118#### Writing 235You can provide your own I/O hooks in a `mtar_ops_t` struct. The same ops
119The following callbacks should be set for writing an archive to a stream: 236struct can be shared among multiple `mtar_t` objects but each object gets
237its own `void* stream` pointer.
120 238
121Name | Arguments | Description 239Name | Arguments | Required
122--------|------------------------------------------------|--------------------- 240--------|-------------------------------------------|------------
123`write` | `mtar_t *tar, const void *data, unsigned size` | Write data to the stream 241`read` | `void* stream, void* data, unsigned size` | If reading
242`write` | `void* stream, void* data, unsigned size` | If writing
243`seek` | `void* stream, unsigned pos` | If reading
244`close` | `void* stream` | Always
124 245
246`read` and `write` should transfer the number of bytes indicated
247and return the number of bytes actually read or written, or a negative
248`enum mtar_error` code on error.
125 249
126## License 250`seek` must have semantics like `lseek(..., pos, SEEK_SET)`; that is,
127This library is free software; you can redistribute it and/or modify it under 251the position is an absolute byte offset in the stream. Seeking is not
128the terms of the MIT license. See [LICENSE](LICENSE) for details. 252optional for read support, but the library only performs backward
253seeks under two circumstances:
254
255- `mtar_rewind()` seeks to position 0.
256- `mtar_seek_data()` may seek backward if the user requests it.
257
258Therefore, you will be able to get away with a limited forward-only
259seek function if you're able to read everything in a single pass use
260the API carefully. Note `mtar_find()` and `mtar_foreach()` will call
261`mtar_rewind()`.
262
263`close` is called by `mtar_close()` to clean up the stream. Note the
264library assumes that the stream handle is cleaned up by `close` even
265if an error occurs.
266
267`seek` and `close` should return an `enum mtar_error` code, either
268`MTAR_SUCCESS`, or a negative value on error.
diff --git a/lib/microtar/README.rockbox b/lib/microtar/README.rockbox
new file mode 100644
index 0000000000..944f792996
--- /dev/null
+++ b/lib/microtar/README.rockbox
@@ -0,0 +1,14 @@
1Upstream commit/repo
2 1e921369b2c92bb219fcef84a37d4d2347794c0f
3 https://github.com/amachronic/microtar
4
5Summary of Rockbox-specific changes:
6
7- microtar.make, SOURCES
8 Target build system integration.
9
10- microtar-rockbox.c, microtar-rockbox.h
11 Implements mtar ops with Rockbox filesystem API.
12
13- microtar.c
14 Define strncpy since Rockbox's libc doesn't define it.
diff --git a/lib/microtar/mtar.c b/lib/microtar/mtar.c
new file mode 100644
index 0000000000..4a9a27b782
--- /dev/null
+++ b/lib/microtar/mtar.c
@@ -0,0 +1,243 @@
1/*
2 * Copyright (c) 2021 Aidan MacDonald
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to
6 * deal in the Software without restriction, including without limitation the
7 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8 * sell copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20 * IN THE SOFTWARE.
21 */
22
23#include "microtar-stdio.h"
24#include <stdio.h>
25#include <string.h>
26#include <stdlib.h>
27#include <stdarg.h>
28#include <sys/stat.h>
29#include <fcntl.h>
30#include <errno.h>
31#include <unistd.h>
32
33/* exit codes */
34#define E_TAR 1
35#define E_FS 2
36#define E_OTHER 4
37#define E_ARGS 8
38
39enum {
40 OP_LIST,
41 OP_CREATE,
42 OP_EXTRACT,
43};
44
45void die(int err, const char* msg, ...)
46{
47 fprintf(stderr, "mtar: ");
48
49 va_list ap;
50 va_start(ap, msg);
51 vfprintf(stderr, msg, ap);
52 va_end(ap);
53
54 fprintf(stderr, "\n");
55 exit(err);
56}
57
58int list_foreach_cb(mtar_t* tar, const mtar_header_t* h, void* arg)
59{
60 (void)tar;
61 (void)arg;
62 printf("%s\n", h->name);
63 return 0;
64}
65
66void list_files(mtar_t* tar)
67{
68 int err = mtar_foreach(tar, list_foreach_cb, NULL);
69 if(err)
70 die(E_TAR, "listing failed: %s", mtar_strerror(err));
71}
72
73struct extract_args {
74 char** names;
75 int count;
76};
77
78int extract_foreach_cb(mtar_t* tar, const mtar_header_t* h, void* arg)
79{
80 struct extract_args* args = arg;
81 (void)args; /* TODO */
82
83 if(h->type == MTAR_TDIR) {
84 if(mkdir(h->name, h->mode) != 0)
85 die(E_FS, "cannot create directory \"%s\"", h->name);
86 return 0;
87 }
88
89 if(h->type != MTAR_TREG) {
90 fprintf(stderr, "warning: not extracting unsupported type \"%s\"", h->name);
91 return 0;
92 }
93
94 int fd = open(h->name, O_CREAT|O_WRONLY|O_TRUNC, h->mode);
95 if(fd < 0)
96 die(E_FS, "extracting \"%s\" failed: %s", h->name, strerror(errno));
97
98 char iobuf[1024];
99 while(!mtar_eof_data(tar)) {
100 int rcount = mtar_read_data(tar, iobuf, sizeof(iobuf));
101 if(rcount < 0)
102 die(E_TAR, "extracting \"%s\" failed: %s", h->name, mtar_strerror(rcount));
103
104 int wcount = write(fd, iobuf, rcount);
105 if(wcount != rcount)
106 die(E_FS, "extracting \"%s\" failed: %s", h->name, strerror(errno));
107 }
108
109 close(fd);
110 return 0;
111}
112
113void extract_files(mtar_t* tar, char** files, int num_files)
114{
115 struct extract_args args;
116 args.names = files;
117 args.count = num_files;
118
119 int err = mtar_foreach(tar, extract_foreach_cb, &args);
120 if(err)
121 die(E_TAR, "extraction failed: %s", mtar_strerror(err));
122}
123
124void add_files(mtar_t* tar, char** files, int num_files)
125{
126 for(int i = 0; i < num_files; ++i) {
127 int fd = open(files[i], O_RDONLY);
128 if(fd < 0)
129 die(E_FS, "adding \"%s\" failed: %s", files[i], strerror(errno));
130
131 off_t off = lseek(fd, 0, SEEK_END);
132 if(off < 0)
133 die(E_FS, "adding \"%s\" failed: %s", files[i], strerror(errno));
134
135 unsigned filesize = off;
136 lseek(fd, 0, SEEK_SET);
137
138 int err = mtar_write_file_header(tar, files[i], filesize);
139 if(err)
140 die(E_TAR, "adding \"%s\" failed: %s", files[i], mtar_strerror(err));
141
142 char iobuf[1024];
143 while(1) {
144 int rcount = read(fd, iobuf, sizeof(iobuf));
145 if(rcount < 0)
146 die(E_FS, "adding \"%s\" failed: %s", files[i], strerror(errno));
147 if(rcount == 0)
148 break;
149
150 int wcount = mtar_write_data(tar, iobuf, rcount);
151 if(wcount < 0)
152 die(E_TAR, "adding \"%s\" failed: %s", files[i], mtar_strerror(wcount));
153 if(wcount != rcount)
154 die(E_TAR, "adding \"%s\" failed: write too short %d/%d", files[i], wcount, rcount);
155 }
156
157 close(fd);
158
159 err = mtar_end_data(tar);
160 if(err)
161 die(E_TAR, "adding \"%s\" failed: %s", files[i], mtar_strerror(err));
162 }
163}
164
165int main(int argc, char* argv[])
166{
167 ++argv, --argc;
168 if(argc == 0)
169 die(E_ARGS, "no input files");
170
171 if(!strcmp(*argv, "--help")) {
172 printf(
173"usage:\n"
174" mtar list tar-file\n"
175" List the members of the given tar archive, one filename per line.\n"
176"\n"
177" mtar create tar-file members...\n"
178" mtar add tar-file members...\n"
179" Create a new tar archive from the files listed on the command line.\n"
180" WARNING: Any existing file at tar-file will be overwritten!\n"
181"\n"
182" mtar extract tar-file [members...]\n"
183" Extract the contents of the tar archive to the current directory.\n"
184" If filenames are given, only the named members will be extracted.\n"
185"\n");
186 exit(E_ARGS);
187 }
188
189 int op;
190 if(!strcmp(*argv, "list"))
191 op = OP_LIST;
192 else if(!strcmp(*argv, "create"))
193 op = OP_CREATE;
194 else if(!strcmp(*argv, "extract"))
195 op = OP_EXTRACT;
196 else
197 die(E_ARGS, "invalid operation \"%s\"", *argv);
198 ++argv, --argc;
199
200 if(argc == 0)
201 die(E_ARGS, "missing archive name");
202 const char* archive_name = *argv;
203 ++argv, --argc;
204
205 if(op == OP_LIST && argc != 0)
206 die(E_ARGS, "excess arguments on command line");
207
208 const char* mode = "rb";
209 if(op == OP_CREATE)
210 mode = "wb";
211
212 mtar_t tar;
213 int err = mtar_open(&tar, archive_name, mode);
214 if(err)
215 die(E_TAR, "can't open archive: %s", mtar_strerror(err));
216
217 switch(op) {
218 case OP_LIST:
219 list_files(&tar);
220 break;
221
222 case OP_EXTRACT:
223 extract_files(&tar, argv, argc);
224 break;
225
226 case OP_CREATE:
227 add_files(&tar, argv, argc);
228 err = mtar_finalize(&tar);
229 if(err)
230 die(E_TAR, "failed to finalize archive: %s", mtar_strerror(err));
231 break;
232
233 default:
234 die(E_OTHER, "not implemented");
235 break;
236 }
237
238 err = mtar_close(&tar);
239 if(err)
240 die(E_TAR, "failed to close archive: %s", mtar_strerror(err));
241
242 return 0;
243}
diff --git a/lib/microtar/src/microtar-rockbox.c b/lib/microtar/src/microtar-rockbox.c
index 04ba93cc42..d0f579fefb 100644
--- a/lib/microtar/src/microtar-rockbox.c
+++ b/lib/microtar/src/microtar-rockbox.c
@@ -20,81 +20,72 @@
20 ****************************************************************************/ 20 ****************************************************************************/
21 21
22#include "file.h" 22#include "file.h"
23#include "microtar.h" 23#include "microtar-rockbox.h"
24#include <stdint.h> 24#include <stdint.h>
25 25
26static int fd_write(mtar_t *tar, const void *data, unsigned size) { 26static int fd_read(void* stream, void* data, unsigned size)
27 intptr_t fd = (intptr_t)tar->stream; 27{
28 ssize_t res = write(fd, data, size); 28 ssize_t res = read((intptr_t)stream, data, size);
29 if(res < 0 || ((unsigned)res != size)) 29 if(res < 0)
30 return MTAR_EWRITEFAIL; 30 return MTAR_EREADFAIL;
31 else
32 return MTAR_ESUCCESS;
33}
34 31
35static int fd_read(mtar_t *tar, void *data, unsigned size) { 32 return res;
36 intptr_t fd = (intptr_t)tar->stream;
37 ssize_t res = read(fd, data, size);
38 if(res < 0 || ((unsigned)res != size))
39 return MTAR_EREADFAIL;
40 else
41 return MTAR_ESUCCESS;
42} 33}
43 34
44static int fd_seek(mtar_t *tar, unsigned offset) { 35static int fd_write(void* stream, const void* data, unsigned size)
45 intptr_t fd = (intptr_t)tar->stream; 36{
46 off_t res = lseek(fd, offset, SEEK_SET); 37 ssize_t res = write((intptr_t)stream, data, size);
47 if(res < 0 || ((unsigned)res != offset)) 38 if(res < 0)
48 return MTAR_ESEEKFAIL; 39 return MTAR_EWRITEFAIL;
49 else
50 return MTAR_ESUCCESS;
51}
52 40
53static int fd_close(mtar_t *tar) { 41 return res;
54 intptr_t fd = (intptr_t)tar->stream;
55 close(fd);
56 return MTAR_ESUCCESS;
57} 42}
58 43
44static int fd_seek(void* stream, unsigned offset)
45{
46 off_t res = lseek((intptr_t)stream, offset, SEEK_SET);
47 if(res < 0 || ((unsigned)res != offset))
48 return MTAR_ESEEKFAIL;
49 else
50 return MTAR_ESUCCESS;
51}
59 52
60int mtar_open(mtar_t *tar, const char *filename, const char *mode) { 53static int fd_close(void* stream)
61 int err; 54{
62 int openmode; 55 close((intptr_t)stream);
63 int fd; 56 return MTAR_ESUCCESS;
64 57}
65 /* Init tar struct and functions */
66 memset(tar, 0, sizeof(*tar));
67 tar->write = fd_write;
68 tar->read = fd_read;
69 tar->seek = fd_seek;
70 tar->close = fd_close;
71 58
72 /* Get correct mode flags */ 59static const mtar_ops_t fd_ops = {
73 if ( strchr(mode, 'r') ) 60 .read = fd_read,
74 openmode = O_RDONLY; 61 .write = fd_write,
75 else if ( strchr(mode, 'w') ) 62 .seek = fd_seek,
76 openmode = O_CREAT|O_TRUNC|O_WRONLY; 63 .close = fd_close,
77 else if ( strchr(mode, 'a') ) 64};
78 openmode = O_WRONLY|O_APPEND;
79 else
80 return MTAR_EFAILURE;
81 65
82 /* Open file */ 66int mtar_open(mtar_t* tar, const char* filename, int flags)
83 fd = open(filename, openmode); 67{
84 if(fd < 0) 68 /* Determine access mode */
85 return MTAR_EOPENFAIL; 69 int access;
70 switch(flags & O_ACCMODE) {
71 case O_RDONLY:
72 access = MTAR_READ;
73 break;
86 74
87 tar->stream = (void*)(intptr_t)fd; 75 case O_WRONLY:
76 access = MTAR_WRITE;
77 break;
88 78
89 /* Read first header to check it is valid if mode is `r` */ 79 default:
90 if ( openmode & O_RDONLY ) { 80 /* Note - O_RDWR isn't very useful so we don't allow it */
91 err = mtar_read_header(tar, &tar->header); 81 return MTAR_EAPI;
92 if (err != MTAR_ESUCCESS) {
93 mtar_close(tar);
94 return err;
95 } 82 }
96 }
97 83
98 /* Return ok */ 84 int fd = open(filename, flags);
99 return MTAR_ESUCCESS; 85 if(fd < 0)
86 return MTAR_EOPENFAIL;
87
88 /* Init tar struct and functions */
89 mtar_init(tar, access, &fd_ops, (void*)(intptr_t)fd);
90 return MTAR_ESUCCESS;
100} 91}
diff --git a/lib/microtar/src/microtar-rockbox.h b/lib/microtar/src/microtar-rockbox.h
new file mode 100644
index 0000000000..36acbb2bfe
--- /dev/null
+++ b/lib/microtar/src/microtar-rockbox.h
@@ -0,0 +1,29 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2021 Aidan MacDonald
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#ifndef MICROTAR_ROCKBOX_H
23#define MICROTAR_ROCKBOX_H
24
25#include "microtar.h"
26
27int mtar_open(mtar_t* tar, const char* filename, int flags);
28
29#endif
diff --git a/lib/microtar/src/microtar-stdio.c b/lib/microtar/src/microtar-stdio.c
index 215000aa98..2697d005cd 100644
--- a/lib/microtar/src/microtar-stdio.c
+++ b/lib/microtar/src/microtar-stdio.c
@@ -1,65 +1,88 @@
1/** 1/*
2 * Copyright (c) 2017 rxi 2 * Copyright (c) 2017 rxi
3 * Copyright (c) 2021 Aidan MacDonald
3 * 4 *
4 * This library is free software; you can redistribute it and/or modify it 5 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * under the terms of the MIT license. See `microtar.c` for details. 6 * of this software and associated documentation files (the "Software"), to
7 * deal in the Software without restriction, including without limitation the
8 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 * sell copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included in
13 * all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 * IN THE SOFTWARE.
6 */ 22 */
7 23
24#include "microtar-stdio.h"
8#include <stdio.h> 25#include <stdio.h>
9#include <string.h> 26#include <string.h>
10 27
11#include "microtar.h" 28static int file_read(void* stream, void* data, unsigned size)
29{
30 unsigned res = fread(data, 1, size, (FILE*)stream);
31 if(res != size && ferror((FILE*)stream))
32 return MTAR_EREADFAIL;
12 33
13static int file_write(mtar_t *tar, const void *data, unsigned size) { 34 return res;
14 unsigned res = fwrite(data, 1, size, tar->stream);
15 return (res == size) ? MTAR_ESUCCESS : MTAR_EWRITEFAIL;
16} 35}
17 36
18static int file_read(mtar_t *tar, void *data, unsigned size) { 37static int file_write(void* stream, const void* data, unsigned size)
19 unsigned res = fread(data, 1, size, tar->stream); 38{
20 return (res == size) ? MTAR_ESUCCESS : MTAR_EREADFAIL; 39 unsigned res = fwrite(data, 1, size, (FILE*)stream);
21} 40 if(res != size && ferror((FILE*)stream))
41 return MTAR_EWRITEFAIL;
22 42
23static int file_seek(mtar_t *tar, unsigned offset) { 43 return res;
24 int res = fseek(tar->stream, offset, SEEK_SET);
25 return (res == 0) ? MTAR_ESUCCESS : MTAR_ESEEKFAIL;
26} 44}
27 45
28static int file_close(mtar_t *tar) { 46static int file_seek(void* stream, unsigned offset)
29 fclose(tar->stream); 47{
30 return MTAR_ESUCCESS; 48 int res = fseek((FILE*)stream, offset, SEEK_SET);
49 return (res == 0) ? MTAR_ESUCCESS : MTAR_ESEEKFAIL;
31} 50}
32 51
52static int file_close(void* stream)
53{
54 int err = fclose((FILE*)stream);
55 return (err == 0 ? MTAR_ESUCCESS : MTAR_EFAILURE);
56}
33 57
34int mtar_open(mtar_t *tar, const char *filename, const char *mode) { 58static const mtar_ops_t file_ops = {
35 int err; 59 .read = file_read,
36 mtar_header_t h; 60 .write = file_write,
37 61 .seek = file_seek,
38 /* Init tar struct and functions */ 62 .close = file_close,
39 memset(tar, 0, sizeof(*tar)); 63};
40 tar->write = file_write;
41 tar->read = file_read;
42 tar->seek = file_seek;
43 tar->close = file_close;
44 64
45 /* Assure mode is always binary */ 65int mtar_open(mtar_t* tar, const char* filename, const char* mode)
46 if ( strchr(mode, 'r') ) mode = "rb"; 66{
47 if ( strchr(mode, 'w') ) mode = "wb"; 67 /* Determine access mode */
48 if ( strchr(mode, 'a') ) mode = "ab"; 68 int access;
49 /* Open file */ 69 char* read = strchr(mode, 'r');
50 tar->stream = fopen(filename, mode); 70 char* write = strchr(mode, 'w');
51 if (!tar->stream) { 71 if(read) {
52 return MTAR_EOPENFAIL; 72 if(write)
53 } 73 return MTAR_EAPI;
54 /* Read first header to check it is valid if mode is `r` */ 74 access = MTAR_READ;
55 if (*mode == 'r') { 75 } else if(write) {
56 err = mtar_read_header(tar, &h); 76 access = MTAR_WRITE;
57 if (err != MTAR_ESUCCESS) { 77 } else {
58 mtar_close(tar); 78 return MTAR_EAPI;
59 return err;
60 } 79 }
61 }
62 80
63 /* Return ok */ 81 /* Open file */
64 return MTAR_ESUCCESS; 82 FILE* file = fopen(filename, mode);
83 if(!file)
84 return MTAR_EOPENFAIL;
85
86 mtar_init(tar, access, &file_ops, file);
87 return MTAR_ESUCCESS;
65} 88}
diff --git a/lib/microtar/src/microtar-stdio.h b/lib/microtar/src/microtar-stdio.h
new file mode 100644
index 0000000000..ad46af0390
--- /dev/null
+++ b/lib/microtar/src/microtar-stdio.h
@@ -0,0 +1,39 @@
1/*
2 * Copyright (c) 2017 rxi
3 * Copyright (c) 2021 Aidan MacDonald
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to
7 * deal in the Software without restriction, including without limitation the
8 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 * sell copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included in
13 * all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 * IN THE SOFTWARE.
22 */
23
24#ifndef MICROTAR_STDIO_H
25#define MICROTAR_STDIO_H
26
27#include "microtar.h"
28
29#ifdef __cplusplus
30extern "C" {
31#endif
32
33int mtar_open(mtar_t* tar, const char* filename, const char* mode);
34
35#ifdef __cplusplus
36}
37#endif
38
39#endif
diff --git a/lib/microtar/src/microtar.c b/lib/microtar/src/microtar.c
index 04cd4ea132..8ab9a8c5f4 100644
--- a/lib/microtar/src/microtar.c
+++ b/lib/microtar/src/microtar.c
@@ -1,5 +1,6 @@
1/* 1/*
2 * Copyright (c) 2017 rxi 2 * Copyright (c) 2017 rxi
3 * Copyright (c) 2021 Aidan MacDonald
3 * 4 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy 5 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to 6 * of this software and associated documentation files (the "Software"), to
@@ -20,16 +21,14 @@
20 * IN THE SOFTWARE. 21 * IN THE SOFTWARE.
21 */ 22 */
22 23
23#include <stdlib.h> 24#include "microtar.h"
24#include <stddef.h>
25#include <limits.h> 25#include <limits.h>
26#include <string.h> 26#include <string.h>
27 27
28#include "microtar.h"
29
30#ifdef ROCKBOX 28#ifdef ROCKBOX
31/* Rockbox lacks strncpy in its libc */ 29/* Rockbox lacks strncpy in its libc */
32static char* strncpy(char* dest, const char* src, size_t n) { 30#define strncpy my_strncpy
31static char* my_strncpy(char* dest, const char* src, size_t n) {
33 size_t i; 32 size_t i;
34 33
35 for(i = 0; i < n && *src; ++i) 34 for(i = 0; i < n && *src; ++i)
@@ -42,362 +41,685 @@ static char* strncpy(char* dest, const char* src, size_t n) {
42} 41}
43#endif 42#endif
44 43
44enum {
45 S_HEADER_VALID = 1 << 0,
46 S_WROTE_HEADER = 1 << 1,
47 S_WROTE_DATA = 1 << 2,
48 S_WROTE_DATA_EOF = 1 << 3,
49 S_WROTE_FINALIZE = 1 << 4,
50};
51
52enum {
53 NAME_OFF = 0, NAME_LEN = 100,
54 MODE_OFF = NAME_OFF+NAME_LEN, MODE_LEN = 8,
55 OWNER_OFF = MODE_OFF+MODE_LEN, OWNER_LEN = 8,
56 GROUP_OFF = OWNER_OFF+OWNER_LEN, GROUP_LEN = 8,
57 SIZE_OFF = GROUP_OFF+GROUP_LEN, SIZE_LEN = 12,
58 MTIME_OFF = SIZE_OFF+SIZE_LEN, MTIME_LEN = 12,
59 CHKSUM_OFF = MTIME_OFF+MTIME_LEN, CHKSUM_LEN = 8,
60 TYPE_OFF = CHKSUM_OFF+CHKSUM_LEN,
61 LINKNAME_OFF = TYPE_OFF+1, LINKNAME_LEN = 100,
62
63 HEADER_LEN = 512,
64};
65
66static int parse_octal(const char* str, size_t len, unsigned* ret)
67{
68 unsigned n = 0;
69
70 while(len-- > 0 && *str != 0) {
71 if(*str < '0' || *str > '9')
72 return MTAR_EOVERFLOW;
73
74 if(n > UINT_MAX/8)
75 return MTAR_EOVERFLOW;
76 else
77 n *= 8;
78
79 char r = *str++ - '0';
80
81 if(n > UINT_MAX - r)
82 return MTAR_EOVERFLOW;
83 else
84 n += r;
85 }
45 86
46static int parse_octal(const char* str, size_t len, unsigned* ret) { 87 *ret = n;
47 unsigned n = 0; 88 return MTAR_ESUCCESS;
48 while(len-- > 0 && *str != 0) {
49 if(*str < '0' || *str > '9')
50 return MTAR_EOVERFLOW;
51
52 if(n > UINT_MAX/8)
53 return MTAR_EOVERFLOW;
54 else
55 n *= 8;
56
57 char r = *str++ - '0';
58 if(n > UINT_MAX - r)
59 return MTAR_EOVERFLOW;
60 else
61 n += r;
62 }
63
64 *ret = n;
65 return MTAR_ESUCCESS;
66} 89}
67 90
91static int print_octal(char* str, size_t len, unsigned value)
92{
93 /* move backwards over the output string */
94 char* ptr = str + len - 1;
95 *ptr = 0;
68 96
69static int print_octal(char* str, size_t len, unsigned value) { 97 /* output the significant digits */
70 /* move backwards over the output string */ 98 while(value > 0) {
71 char* ptr = str + len - 1; 99 if(ptr == str)
72 *ptr = 0; 100 return MTAR_EOVERFLOW;
73 101
74 /* output the significant digits */ 102 --ptr;
75 while(value > 0) { 103 *ptr = '0' + (value % 8);
76 if(ptr == str) 104 value /= 8;
77 return MTAR_EOVERFLOW; 105 }
78 106
79 --ptr; 107 /* pad the remainder of the field with zeros */
80 *ptr = '0' + (value % 8); 108 while(ptr != str) {
81 value /= 8; 109 --ptr;
82 } 110 *ptr = '0';
111 }
83 112
84 /* pad the remainder of the field with zeros */ 113 return MTAR_ESUCCESS;
85 while(ptr != str) { 114}
86 --ptr;
87 *ptr = '0';
88 }
89 115
90 return MTAR_ESUCCESS; 116static unsigned round_up_512(unsigned n)
117{
118 return (n + 511u) & ~511u;
91} 119}
92 120
121static int tread(mtar_t* tar, void* data, unsigned size)
122{
123 int ret = tar->ops->read(tar->stream, data, size);
124 if(ret >= 0)
125 tar->pos += ret;
93 126
94static unsigned round_up(unsigned n, unsigned incr) { 127 return ret;
95 return n + (incr - n % incr) % incr;
96} 128}
97 129
130static int twrite(mtar_t* tar, const void* data, unsigned size)
131{
132 int ret = tar->ops->write(tar->stream, data, size);
133 if(ret >= 0)
134 tar->pos += ret;
98 135
99static unsigned checksum(const mtar_raw_header_t* rh) { 136 return ret;
100 unsigned i;
101 unsigned char *p = (unsigned char*) rh;
102 unsigned res = 256;
103 for (i = 0; i < offsetof(mtar_raw_header_t, checksum); i++) {
104 res += p[i];
105 }
106 for (i = offsetof(mtar_raw_header_t, type); i < sizeof(*rh); i++) {
107 res += p[i];
108 }
109 return res;
110} 137}
111 138
112 139static int tseek(mtar_t* tar, unsigned pos)
113static int tread(mtar_t *tar, void *data, unsigned size) { 140{
114 int err = tar->read(tar, data, size); 141 int err = tar->ops->seek(tar->stream, pos);
115 tar->pos += size; 142 tar->pos = pos;
116 return err; 143 return err;
117} 144}
118 145
146static int write_null_bytes(mtar_t* tar, size_t count)
147{
148 int ret;
149 size_t n;
150
151 memset(tar->buffer, 0, sizeof(tar->buffer));
152 while(count > 0) {
153 n = count < sizeof(tar->buffer) ? count : sizeof(tar->buffer);
154 ret = twrite(tar, tar->buffer, n);
155 if(ret < 0)
156 return ret;
157 if(ret != (int)n)
158 return MTAR_EWRITEFAIL;
159
160 count -= n;
161 }
119 162
120static int twrite(mtar_t *tar, const void *data, unsigned size) { 163 return MTAR_ESUCCESS;
121 int err = tar->write(tar, data, size);
122 tar->pos += size;
123 return err;
124} 164}
125 165
166static unsigned checksum(const char* raw)
167{
168 unsigned i;
169 unsigned char* p = (unsigned char*)raw;
170 unsigned res = 256;
126 171
127static int write_null_bytes(mtar_t *tar, int n) { 172 for(i = 0; i < CHKSUM_OFF; i++)
128 int i, err; 173 res += p[i];
129 char nul = '\0'; 174 for(i = TYPE_OFF; i < HEADER_LEN; i++)
130 for (i = 0; i < n; i++) { 175 res += p[i];
131 err = twrite(tar, &nul, 1); 176
132 if (err) { 177 return res;
133 return err;
134 }
135 }
136 return MTAR_ESUCCESS;
137} 178}
138 179
180static int raw_to_header(mtar_header_t* h, const char* raw)
181{
182 unsigned chksum;
183 int rc;
184
185 /* If the checksum starts with a null byte we assume the record is NULL */
186 if(raw[CHKSUM_OFF] == '\0')
187 return MTAR_ENULLRECORD;
188
189 /* Compare the checksum */
190 if((rc = parse_octal(&raw[CHKSUM_OFF], CHKSUM_LEN, &chksum)))
191 return rc;
192 if(chksum != checksum(raw))
193 return MTAR_EBADCHKSUM;
194
195 /* Load raw header into header */
196 if((rc = parse_octal(&raw[MODE_OFF], MODE_LEN, &h->mode)))
197 return rc;
198 if((rc = parse_octal(&raw[OWNER_OFF], OWNER_LEN, &h->owner)))
199 return rc;
200 if((rc = parse_octal(&raw[GROUP_OFF], GROUP_LEN, &h->group)))
201 return rc;
202 if((rc = parse_octal(&raw[SIZE_OFF], SIZE_LEN, &h->size)))
203 return rc;
204 if((rc = parse_octal(&raw[MTIME_OFF], MTIME_LEN, &h->mtime)))
205 return rc;
206
207 h->type = raw[TYPE_OFF];
208 if(!h->type)
209 h->type = MTAR_TREG;
210
211 memcpy(h->name, &raw[NAME_OFF], NAME_LEN);
212 h->name[sizeof(h->name) - 1] = 0;
213
214 memcpy(h->linkname, &raw[LINKNAME_OFF], LINKNAME_LEN);
215 h->linkname[sizeof(h->linkname) - 1] = 0;
216
217 return MTAR_ESUCCESS;
218}
139 219
140static int raw_to_header(mtar_header_t *h, const mtar_raw_header_t *rh) { 220static int header_to_raw(char* raw, const mtar_header_t* h)
141 unsigned chksum1, chksum2; 221{
142 int rc; 222 unsigned chksum;
223 int rc;
224
225 memset(raw, 0, HEADER_LEN);
226
227 /* Load header into raw header */
228 if((rc = print_octal(&raw[MODE_OFF], MODE_LEN, h->mode)))
229 return rc;
230 if((rc = print_octal(&raw[OWNER_OFF], OWNER_LEN, h->owner)))
231 return rc;
232 if((rc = print_octal(&raw[GROUP_OFF], GROUP_LEN, h->group)))
233 return rc;
234 if((rc = print_octal(&raw[SIZE_OFF], SIZE_LEN, h->size)))
235 return rc;
236 if((rc = print_octal(&raw[MTIME_OFF], MTIME_LEN, h->mtime)))
237 return rc;
238
239 raw[TYPE_OFF] = h->type ? h->type : MTAR_TREG;
240
241#if defined(__GNUC__) && (__GNUC__ >= 8)
242/* Sigh. GCC wrongly assumes the output of strncpy() is supposed to be
243 * a null-terminated string -- which it is not, and we are relying on
244 * that fact here -- and tries to warn about 'string truncation' because
245 * the null terminator might not be copied. Just suppress the warning. */
246# pragma GCC diagnostic push
247# pragma GCC diagnostic ignored "-Wstringop-truncation"
248#endif
143 249
144 /* If the checksum starts with a null byte we assume the record is NULL */ 250 strncpy(&raw[NAME_OFF], h->name, NAME_LEN);
145 if (*rh->checksum == '\0') { 251 strncpy(&raw[LINKNAME_OFF], h->linkname, LINKNAME_LEN);
146 return MTAR_ENULLRECORD;
147 }
148 252
149 /* Build and compare checksum */ 253#if defined(__GNUC__) && (__GNUC__ >= 8)
150 chksum1 = checksum(rh); 254# pragma GCC diagnostic pop
151 if((rc = parse_octal(rh->checksum, sizeof(rh->checksum), &chksum2))) 255#endif
152 return rc;
153 if (chksum1 != chksum2) {
154 return MTAR_EBADCHKSUM;
155 }
156 256
157 /* Load raw header into header */ 257 /* Calculate and write checksum */
158 if((rc = parse_octal(rh->mode, sizeof(rh->mode), &h->mode))) 258 chksum = checksum(raw);
159 return rc; 259 if((rc = print_octal(&raw[CHKSUM_OFF], CHKSUM_LEN-1, chksum)))
160 if((rc = parse_octal(rh->owner, sizeof(rh->owner), &h->owner))) 260 return rc;
161 return rc;
162 if((rc = parse_octal(rh->group, sizeof(rh->group), &h->group)))
163 return rc;
164 if((rc = parse_octal(rh->size, sizeof(rh->size), &h->size)))
165 return rc;
166 if((rc = parse_octal(rh->mtime, sizeof(rh->mtime), &h->mtime)))
167 return rc;
168 261
169 h->type = rh->type; 262 raw[CHKSUM_OFF + CHKSUM_LEN - 1] = ' ';
170 263
171 memcpy(h->name, rh->name, sizeof(rh->name)); 264 return MTAR_ESUCCESS;
172 h->name[sizeof(h->name) - 1] = 0; 265}
173 266
174 memcpy(h->linkname, rh->linkname, sizeof(rh->linkname)); 267static unsigned data_beg_pos(const mtar_t* tar)
175 h->linkname[sizeof(h->linkname) - 1] = 0; 268{
269 return tar->header_pos + HEADER_LEN;
270}
176 271
177 return MTAR_ESUCCESS; 272static unsigned data_end_pos(const mtar_t* tar)
273{
274 return tar->end_pos;
178} 275}
179 276
277static int ensure_header(mtar_t* tar)
278{
279 int ret, err;
180 280
181static int header_to_raw(mtar_raw_header_t *rh, const mtar_header_t *h) { 281 if(tar->state & S_HEADER_VALID)
182 unsigned chksum; 282 return MTAR_ESUCCESS;
183 int rc;
184 283
185 /* Load header into raw header */ 284 if(tar->pos > UINT_MAX - HEADER_LEN)
186 memset(rh, 0, sizeof(*rh)); 285 return MTAR_EOVERFLOW;
187 if((rc = print_octal(rh->mode, sizeof(rh->mode), h->mode)))
188 return rc;
189 if((rc = print_octal(rh->owner, sizeof(rh->owner), h->owner)))
190 return rc;
191 if((rc = print_octal(rh->group, sizeof(rh->group), h->group)))
192 return rc;
193 if((rc = print_octal(rh->size, sizeof(rh->size), h->size)))
194 return rc;
195 if((rc = print_octal(rh->mtime, sizeof(rh->mtime), h->mtime)))
196 return rc;
197 rh->type = h->type ? h->type : MTAR_TREG;
198 strncpy(rh->name, h->name, sizeof(rh->name));
199 strncpy(rh->linkname, h->linkname, sizeof(rh->linkname));
200 286
201 /* Calculate and write checksum */ 287 tar->header_pos = tar->pos;
202 chksum = checksum(rh); 288 tar->end_pos = data_beg_pos(tar);
203 if((rc = print_octal(rh->checksum, 7, chksum)))
204 return rc;
205 rh->checksum[7] = ' ';
206 289
207 return MTAR_ESUCCESS; 290 ret = tread(tar, tar->buffer, HEADER_LEN);
208} 291 if(ret < 0)
292 return ret;
293 if(ret != HEADER_LEN)
294 return MTAR_EREADFAIL;
209 295
296 err = raw_to_header(&tar->header, tar->buffer);
297 if(err)
298 return err;
210 299
211const char* mtar_strerror(int err) { 300 if(tar->end_pos > UINT_MAX - tar->header.size)
212 switch (err) { 301 return MTAR_EOVERFLOW;
213 case MTAR_ESUCCESS : return "success"; 302 tar->end_pos += tar->header.size;
214 case MTAR_EFAILURE : return "failure"; 303
215 case MTAR_EOPENFAIL : return "could not open"; 304 tar->state |= S_HEADER_VALID;
216 case MTAR_EREADFAIL : return "could not read"; 305 return MTAR_ESUCCESS;
217 case MTAR_EWRITEFAIL : return "could not write";
218 case MTAR_ESEEKFAIL : return "could not seek";
219 case MTAR_EBADCHKSUM : return "bad checksum";
220 case MTAR_ENULLRECORD : return "null record";
221 case MTAR_ENOTFOUND : return "file not found";
222 case MTAR_EOVERFLOW : return "overflow";
223 }
224 return "unknown error";
225} 306}
226 307
308const char* mtar_strerror(int err)
309{
310 switch(err) {
311 case MTAR_ESUCCESS: return "success";
312 case MTAR_EFAILURE: return "failure";
313 case MTAR_EOPENFAIL: return "could not open";
314 case MTAR_EREADFAIL: return "could not read";
315 case MTAR_EWRITEFAIL: return "could not write";
316 case MTAR_ESEEKFAIL: return "could not seek";
317 case MTAR_ESEEKRANGE: return "seek out of bounds";
318 case MTAR_EBADCHKSUM: return "bad checksum";
319 case MTAR_ENULLRECORD: return "null record";
320 case MTAR_ENOTFOUND: return "file not found";
321 case MTAR_EOVERFLOW: return "overflow";
322 case MTAR_EAPI: return "API usage error";
323 case MTAR_ENAMETOOLONG: return "name too long";
324 case MTAR_EWRONGSIZE: return "wrong amount of data written";
325 case MTAR_EACCESS: return "wrong access mode";
326 default: return "unknown error";
327 }
328}
227 329
228int mtar_close(mtar_t *tar) { 330void mtar_init(mtar_t* tar, int access, const mtar_ops_t* ops, void* stream)
229 return tar->close(tar); 331{
332 memset(tar, 0, sizeof(mtar_t));
333 tar->access = access;
334 tar->ops = ops;
335 tar->stream = stream;
230} 336}
231 337
338int mtar_close(mtar_t* tar)
339{
340 int err = tar->ops->close(tar->stream);
341 tar->ops = NULL;
342 tar->stream = NULL;
343 return err;
344}
232 345
233int mtar_seek(mtar_t *tar, unsigned pos) { 346int mtar_is_open(mtar_t* tar)
234 int err = tar->seek(tar, pos); 347{
235 tar->pos = pos; 348 return (tar->ops != NULL) ? 1 : 0;
236 return err;
237} 349}
238 350
351mtar_header_t* mtar_get_header(mtar_t* tar)
352{
353 if(tar->state & S_HEADER_VALID)
354 return &tar->header;
355 else
356 return NULL;
357}
239 358
240int mtar_rewind(mtar_t *tar) { 359int mtar_access_mode(const mtar_t* tar)
241 tar->remaining_data = 0; 360{
242 tar->last_header = 0; 361 return tar->access;
243 return mtar_seek(tar, 0);
244} 362}
245 363
364int mtar_rewind(mtar_t* tar)
365{
366#ifndef MICROTAR_DISABLE_API_CHECKS
367 if(tar->access != MTAR_READ)
368 return MTAR_EAPI;
369#endif
246 370
247int mtar_next(mtar_t *tar) { 371 int err = tseek(tar, 0);
248 int err, n; 372 tar->state = 0;
249 /* Load header */
250 err = mtar_read_header(tar, &tar->header);
251 if (err) {
252 return err; 373 return err;
253 }
254 /* Seek to next record */
255 n = round_up(tar->header.size, 512) + sizeof(mtar_raw_header_t);
256 return mtar_seek(tar, tar->pos + n);
257} 374}
258 375
376int mtar_next(mtar_t* tar)
377{
378#ifndef MICROTAR_DISABLE_API_CHECKS
379 if(tar->access != MTAR_READ)
380 return MTAR_EACCESS;
381#endif
259 382
260int mtar_find(mtar_t *tar, const char *name, mtar_header_t *h) { 383 if(tar->state & S_HEADER_VALID) {
261 int err; 384 tar->state &= ~S_HEADER_VALID;
262 /* Start at beginning */ 385
263 err = mtar_rewind(tar); 386 /* seek to the next header */
264 if (err) { 387 int err = tseek(tar, round_up_512(data_end_pos(tar)));
265 return err; 388 if(err)
266 } 389 return err;
267 /* Iterate all files until we hit an error or find the file */
268 while ( (err = mtar_read_header(tar, &tar->header)) == MTAR_ESUCCESS ) {
269 if ( !strcmp(tar->header.name, name) ) {
270 if (h) {
271 *h = tar->header;
272 }
273 return MTAR_ESUCCESS;
274 }
275 err = mtar_next(tar);
276 if (err) {
277 return err;
278 } 390 }
279 } 391
280 /* Return error */ 392 return ensure_header(tar);
281 if (err == MTAR_ENULLRECORD) { 393}
282 err = MTAR_ENOTFOUND; 394
283 } 395int mtar_foreach(mtar_t* tar, mtar_foreach_cb cb, void* arg)
284 return err; 396{
285} 397#ifndef MICROTAR_DISABLE_API_CHECKS
286 398 if(tar->access != MTAR_READ)
287 399 return MTAR_EACCESS;
288int mtar_read_header(mtar_t *tar, mtar_header_t *h) { 400#endif
289 int err; 401
290 /* Save header position */ 402 int err = mtar_rewind(tar);
291 tar->last_header = tar->pos; 403 if(err)
292 /* Read raw header */ 404 return err;
293 err = tread(tar, &tar->raw_header, sizeof(tar->raw_header)); 405
294 if (err) { 406 while((err = mtar_next(tar)) == MTAR_ESUCCESS)
407 if((err = cb(tar, &tar->header, arg)) != 0)
408 return err;
409
410 if(err == MTAR_ENULLRECORD)
411 err = MTAR_ESUCCESS;
412
295 return err; 413 return err;
296 } 414}
297 /* Seek back to start of header */ 415
298 err = mtar_seek(tar, tar->last_header); 416static int find_foreach_cb(mtar_t* tar, const mtar_header_t* h, void* arg)
299 if (err) { 417{
418 (void)tar;
419 const char* name = (const char*)arg;
420 return strcmp(name, h->name) ? 0 : 1;
421}
422
423int mtar_find(mtar_t* tar, const char* name)
424{
425 int err = mtar_foreach(tar, find_foreach_cb, (void*)name);
426 if(err == 1)
427 err = MTAR_ESUCCESS;
428 else if(err == MTAR_ESUCCESS)
429 err = MTAR_ENOTFOUND;
430
300 return err; 431 return err;
301 }
302 /* Load raw header into header struct and return */
303 return raw_to_header(h, &tar->raw_header);
304} 432}
305 433
434int mtar_read_data(mtar_t* tar, void* ptr, unsigned size)
435{
436#ifndef MICROTAR_DISABLE_API_CHECKS
437 if(!(tar->state & S_HEADER_VALID))
438 return MTAR_EAPI;
439#endif
306 440
307int mtar_read_data(mtar_t *tar, void *ptr, unsigned size) { 441 /* have we reached end of file? */
308 int err; 442 unsigned data_end = data_end_pos(tar);
309 /* If we have no remaining data then this is the first read, we get the size, 443 if(tar->pos >= data_end)
310 * set the remaining data and seek to the beginning of the data */ 444 return 0;
311 if (tar->remaining_data == 0) { 445
312 /* Read header */ 446 /* truncate the read if it would go beyond EOF */
313 err = mtar_read_header(tar, &tar->header); 447 unsigned data_left = data_end - tar->pos;
314 if (err) { 448 if(data_left < size)
315 return err; 449 size = data_left;
316 } 450
317 /* Seek past header and init remaining data */ 451 return tread(tar, ptr, size);
318 err = mtar_seek(tar, tar->pos + sizeof(mtar_raw_header_t)); 452}
319 if (err) { 453
320 return err; 454int mtar_seek_data(mtar_t* tar, int offset, int whence)
455{
456#ifndef MICROTAR_DISABLE_API_CHECKS
457 if(!(tar->state & S_HEADER_VALID))
458 return MTAR_EAPI;
459#endif
460
461 unsigned data_beg = data_beg_pos(tar);
462 unsigned data_end = data_end_pos(tar);
463 unsigned newpos;
464
465 switch(whence) {
466 case SEEK_SET:
467 if(offset < 0)
468 return MTAR_ESEEKRANGE;
469
470 newpos = data_beg + offset;
471 break;
472
473 case SEEK_CUR:
474 if((offset > 0 && (unsigned) offset > data_end - tar->pos) ||
475 (offset < 0 && (unsigned)-offset > tar->pos - data_beg))
476 return MTAR_ESEEKRANGE;
477
478 newpos = tar->pos + offset;
479 break;
480
481 case SEEK_END:
482 if(offset > 0)
483 return MTAR_ESEEKRANGE;
484
485 newpos = data_end + offset;
486 break;
487
488 default:
489 return MTAR_EAPI;
321 } 490 }
322 tar->remaining_data = tar->header.size; 491
323 } 492 return tseek(tar, newpos);
324 /* Ensure caller does not read too much */ 493}
325 if(size > tar->remaining_data) 494
326 return MTAR_EOVERFLOW; 495unsigned mtar_tell_data(mtar_t* tar)
327 /* Read data */ 496{
328 err = tread(tar, ptr, size); 497#ifndef MICROTAR_DISABLE_API_CHECKS
329 if (err) { 498 if(!(tar->state & S_HEADER_VALID))
330 return err; 499 return MTAR_EAPI;
331 } 500#endif
332 tar->remaining_data -= size; 501
333 /* If there is no remaining data we've finished reading and seek back to the 502 return tar->pos - data_beg_pos(tar);
334 * header */
335 if (tar->remaining_data == 0) {
336 return mtar_seek(tar, tar->last_header);
337 }
338 return MTAR_ESUCCESS;
339} 503}
340 504
505int mtar_eof_data(mtar_t* tar)
506{
507 /* API usage error, but just claim EOF. */
508 if(!(tar->state & S_HEADER_VALID))
509 return 1;
341 510
342int mtar_write_header(mtar_t *tar, const mtar_header_t *h) { 511 return tar->pos >= data_end_pos(tar) ? 1 : 0;
343 /* Build raw header and write */
344 header_to_raw(&tar->raw_header, h);
345 tar->remaining_data = h->size;
346 return twrite(tar, &tar->raw_header, sizeof(tar->raw_header));
347} 512}
348 513
514int mtar_write_header(mtar_t* tar, const mtar_header_t* h)
515{
516#ifndef MICROTAR_DISABLE_API_CHECKS
517 if(tar->access != MTAR_WRITE)
518 return MTAR_EACCESS;
519 if(((tar->state & S_WROTE_DATA) && !(tar->state & S_WROTE_DATA_EOF)) ||
520 (tar->state & S_WROTE_FINALIZE))
521 return MTAR_EAPI;
522#endif
523
524 tar->state &= ~(S_HEADER_VALID | S_WROTE_HEADER |
525 S_WROTE_DATA | S_WROTE_DATA_EOF);
526
527 /* ensure we have enough space to write the declared amount of data */
528 if(tar->pos > UINT_MAX - HEADER_LEN - round_up_512(h->size))
529 return MTAR_EOVERFLOW;
530
531 tar->header_pos = tar->pos;
532 tar->end_pos = data_beg_pos(tar);
533
534 if(h != &tar->header)
535 tar->header = *h;
536
537 int err = header_to_raw(tar->buffer, &tar->header);
538 if(err)
539 return err;
540
541 int ret = twrite(tar, tar->buffer, HEADER_LEN);
542 if(ret < 0)
543 return ret;
544 if(ret != HEADER_LEN)
545 return MTAR_EWRITEFAIL;
349 546
350int mtar_write_file_header(mtar_t *tar, const char *name, unsigned size) { 547 tar->state |= (S_HEADER_VALID | S_WROTE_HEADER);
351 /* Build header */ 548 return MTAR_ESUCCESS;
352 memset(&tar->header, 0, sizeof(tar->header));
353 /* Ensure name fits within header */
354 if(strlen(name) > sizeof(tar->header.name))
355 return MTAR_EOVERFLOW;
356 strncpy(tar->header.name, name, sizeof(tar->header.name));
357 tar->header.size = size;
358 tar->header.type = MTAR_TREG;
359 tar->header.mode = 0664;
360 /* Write header */
361 return mtar_write_header(tar, &tar->header);
362} 549}
363 550
551int mtar_update_header(mtar_t* tar, const mtar_header_t* h)
552{
553#ifndef MICROTAR_DISABLE_API_CHECKS
554 if(tar->access != MTAR_WRITE)
555 return MTAR_EACCESS;
556 if(!(tar->state & S_WROTE_HEADER) ||
557 (tar->state & S_WROTE_DATA_EOF) ||
558 (tar->state & S_WROTE_FINALIZE))
559 return MTAR_EAPI;
560#endif
561
562 unsigned beg_pos = data_beg_pos(tar);
563 if(beg_pos > UINT_MAX - h->size)
564 return MTAR_EOVERFLOW;
565
566 unsigned old_pos = tar->pos;
567 int err = tseek(tar, tar->header_pos);
568 if(err)
569 return err;
570
571 if(h != &tar->header)
572 tar->header = *h;
364 573
365int mtar_write_dir_header(mtar_t *tar, const char *name) { 574 err = header_to_raw(tar->buffer, &tar->header);
366 /* Build header */ 575 if(err)
367 memset(&tar->header, 0, sizeof(tar->header)); 576 return err;
368 /* Ensure name fits within header */ 577
369 if(strlen(name) > sizeof(tar->header.name)) 578 int len = twrite(tar, tar->buffer, HEADER_LEN);
370 return MTAR_EOVERFLOW; 579 if(len < 0)
371 strncpy(tar->header.name, name, sizeof(tar->header.name)); 580 return len;
372 tar->header.type = MTAR_TDIR; 581 if(len != HEADER_LEN)
373 tar->header.mode = 0775; 582 return MTAR_EWRITEFAIL;
374 /* Write header */ 583
375 return mtar_write_header(tar, &tar->header); 584 return tseek(tar, old_pos);
376} 585}
377 586
587int mtar_write_file_header(mtar_t* tar, const char* name, unsigned size)
588{
589#ifndef MICROTAR_DISABLE_API_CHECKS
590 if(tar->access != MTAR_WRITE)
591 return MTAR_EACCESS;
592 if(((tar->state & S_WROTE_DATA) && !(tar->state & S_WROTE_DATA_EOF)) ||
593 (tar->state & S_WROTE_FINALIZE))
594 return MTAR_EAPI;
595#endif
378 596
379int mtar_write_data(mtar_t *tar, const void *data, unsigned size) { 597 size_t namelen = strlen(name);
380 int err; 598 if(namelen > NAME_LEN)
599 return MTAR_ENAMETOOLONG;
381 600
382 /* Ensure we are writing the correct amount of data */ 601 tar->header.mode = 0644;
383 if(size > tar->remaining_data) 602 tar->header.owner = 0;
384 return MTAR_EOVERFLOW; 603 tar->header.group = 0;
604 tar->header.size = size;
605 tar->header.mtime = 0;
606 tar->header.type = MTAR_TREG;
607 memcpy(tar->header.name, name, namelen + 1);
608 tar->header.linkname[0] = '\0';
609
610 return mtar_write_header(tar, &tar->header);
611}
612
613int mtar_write_dir_header(mtar_t* tar, const char* name)
614{
615#ifndef MICROTAR_DISABLE_API_CHECKS
616 if(tar->access != MTAR_WRITE)
617 return MTAR_EACCESS;
618 if(((tar->state & S_WROTE_DATA) && !(tar->state & S_WROTE_DATA_EOF)) ||
619 (tar->state & S_WROTE_FINALIZE))
620 return MTAR_EAPI;
621#endif
622
623 size_t namelen = strlen(name);
624 if(namelen > NAME_LEN)
625 return MTAR_ENAMETOOLONG;
626
627 tar->header.mode = 0755;
628 tar->header.owner = 0;
629 tar->header.group = 0;
630 tar->header.size = 0;
631 tar->header.mtime = 0;
632 tar->header.type = MTAR_TDIR;
633 memcpy(tar->header.name, name, namelen + 1);
634 tar->header.linkname[0] = '\0';
635
636 return mtar_write_header(tar, &tar->header);
637}
638
639int mtar_write_data(mtar_t* tar, const void* ptr, unsigned size)
640{
641#ifndef MICROTAR_DISABLE_API_CHECKS
642 if(tar->access != MTAR_WRITE)
643 return MTAR_EACCESS;
644 if(!(tar->state & S_WROTE_HEADER) ||
645 (tar->state & S_WROTE_DATA_EOF) ||
646 (tar->state & S_WROTE_FINALIZE))
647 return MTAR_EAPI;
648#endif
649
650 tar->state |= S_WROTE_DATA;
651
652 int err = twrite(tar, ptr, size);
653 if(tar->pos > tar->end_pos)
654 tar->end_pos = tar->pos;
385 655
386 /* Write data */
387 err = twrite(tar, data, size);
388 if (err) {
389 return err; 656 return err;
390 }
391 tar->remaining_data -= size;
392 /* Write padding if we've written all the data for this file */
393 if (tar->remaining_data == 0) {
394 return write_null_bytes(tar, round_up(tar->pos, 512) - tar->pos);
395 }
396 return MTAR_ESUCCESS;
397} 657}
398 658
659int mtar_update_file_size(mtar_t* tar)
660{
661#ifndef MICROTAR_DISABLE_API_CHECKS
662 if(tar->access != MTAR_WRITE)
663 return MTAR_EACCESS;
664 if(!(tar->state & S_WROTE_HEADER) ||
665 (tar->state & S_WROTE_DATA_EOF) ||
666 (tar->state & S_WROTE_FINALIZE))
667 return MTAR_EAPI;
668#endif
669
670 unsigned new_size = data_end_pos(tar) - data_beg_pos(tar);
671 if(new_size == tar->header.size)
672 return MTAR_ESUCCESS;
673 else {
674 tar->header.size = new_size;
675 return mtar_update_header(tar, &tar->header);
676 }
677}
678
679int mtar_end_data(mtar_t* tar)
680{
681#ifndef MICROTAR_DISABLE_API_CHECKS
682 if(tar->access != MTAR_WRITE)
683 return MTAR_EACCESS;
684 if(!(tar->state & S_WROTE_HEADER) ||
685 (tar->state & S_WROTE_DATA_EOF) ||
686 (tar->state & S_WROTE_FINALIZE))
687 return MTAR_EAPI;
688#endif
689
690 int err;
691
692 /* ensure the caller wrote the correct amount of data */
693 unsigned expected_end = data_beg_pos(tar) + tar->header.size;
694 if(tar->end_pos != expected_end)
695 return MTAR_EWRONGSIZE;
696
697 /* ensure we're positioned at the end of the stream */
698 if(tar->pos != tar->end_pos) {
699 err = tseek(tar, tar->end_pos);
700 if(err)
701 return err;
702 }
703
704 /* write remainder of the 512-byte record */
705 err = write_null_bytes(tar, round_up_512(tar->pos) - tar->pos);
706 if(err)
707 return err;
708
709 tar->state |= S_WROTE_DATA_EOF;
710 return MTAR_ESUCCESS;
711}
712
713int mtar_finalize(mtar_t* tar)
714{
715#ifndef MICROTAR_DISABLE_API_CHECKS
716 if(tar->access != MTAR_WRITE)
717 return MTAR_EACCESS;
718 if(((tar->state & S_WROTE_DATA) && !(tar->state & S_WROTE_DATA_EOF)) ||
719 (tar->state & S_WROTE_FINALIZE))
720 return MTAR_EAPI;
721#endif
399 722
400int mtar_finalize(mtar_t *tar) { 723 tar->state |= S_WROTE_FINALIZE;
401 /* Write two NULL records */ 724 return write_null_bytes(tar, 1024);
402 return write_null_bytes(tar, sizeof(mtar_raw_header_t) * 2);
403} 725}
diff --git a/lib/microtar/src/microtar.h b/lib/microtar/src/microtar.h
index d30c829521..7a9be2ac92 100644
--- a/lib/microtar/src/microtar.h
+++ b/lib/microtar/src/microtar.h
@@ -1,105 +1,132 @@
1/** 1/*
2 * Copyright (c) 2017 rxi 2 * Copyright (c) 2017 rxi
3 * Copyright (c) 2021 Aidan MacDonald
3 * 4 *
4 * This library is free software; you can redistribute it and/or modify it 5 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * under the terms of the MIT license. See `microtar.c` for details. 6 * of this software and associated documentation files (the "Software"), to
7 * deal in the Software without restriction, including without limitation the
8 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 * sell copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included in
13 * all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 * IN THE SOFTWARE.
6 */ 22 */
7 23
8#ifndef MICROTAR_H 24#ifndef MICROTAR_H
9#define MICROTAR_H 25#define MICROTAR_H
10 26
11#ifdef __cplusplus 27#ifdef __cplusplus
12extern "C" 28extern "C" {
13{
14#endif 29#endif
15 30
16#include <stdio.h> 31#include <stdio.h> /* SEEK_SET et al. */
17#include <stdlib.h> 32
18 33enum mtar_error {
19#define MTAR_VERSION "0.1.0" 34 MTAR_ESUCCESS = 0,
20 35 MTAR_EFAILURE = -1,
21enum { 36 MTAR_EOPENFAIL = -2,
22 MTAR_ESUCCESS = 0, 37 MTAR_EREADFAIL = -3,
23 MTAR_EFAILURE = -1, 38 MTAR_EWRITEFAIL = -4,
24 MTAR_EOPENFAIL = -2, 39 MTAR_ESEEKFAIL = -5,
25 MTAR_EREADFAIL = -3, 40 MTAR_ESEEKRANGE = -6,
26 MTAR_EWRITEFAIL = -4, 41 MTAR_EBADCHKSUM = -7,
27 MTAR_ESEEKFAIL = -5, 42 MTAR_ENULLRECORD = -8,
28 MTAR_EBADCHKSUM = -6, 43 MTAR_ENOTFOUND = -9,
29 MTAR_ENULLRECORD = -7, 44 MTAR_EOVERFLOW = -10,
30 MTAR_ENOTFOUND = -8, 45 MTAR_EAPI = -11,
31 MTAR_EOVERFLOW = -9, 46 MTAR_ENAMETOOLONG = -12,
47 MTAR_EWRONGSIZE = -13,
48 MTAR_EACCESS = -14,
49};
50
51enum mtar_type {
52 MTAR_TREG = '0',
53 MTAR_TLNK = '1',
54 MTAR_TSYM = '2',
55 MTAR_TCHR = '3',
56 MTAR_TBLK = '4',
57 MTAR_TDIR = '5',
58 MTAR_TFIFO = '6',
32}; 59};
33 60
34enum { 61enum mtar_access {
35 MTAR_TREG = '0', 62 MTAR_READ,
36 MTAR_TLNK = '1', 63 MTAR_WRITE,
37 MTAR_TSYM = '2',
38 MTAR_TCHR = '3',
39 MTAR_TBLK = '4',
40 MTAR_TDIR = '5',
41 MTAR_TFIFO = '6'
42}; 64};
43 65
44typedef struct { 66typedef struct mtar_header mtar_header_t;
45 unsigned mode; 67typedef struct mtar mtar_t;
46 unsigned owner; 68typedef struct mtar_ops mtar_ops_t;
47 unsigned group; 69
48 unsigned size; 70typedef int(*mtar_foreach_cb)(mtar_t*, const mtar_header_t*, void*);
49 unsigned mtime; 71
50 unsigned type; 72struct mtar_header {
51 char name[101]; /* +1 byte in order to ensure null termination */ 73 unsigned mode;
52 char linkname[101]; 74 unsigned owner;
53} mtar_header_t; 75 unsigned group;
54 76 unsigned size;
55 77 unsigned mtime;
56typedef struct { 78 unsigned type;
57 char name[100]; 79 char name[101]; /* +1 byte in order to ensure null termination */
58 char mode[8]; 80 char linkname[101];
59 char owner[8];
60 char group[8];
61 char size[12];
62 char mtime[12];
63 char checksum[8];
64 char type;
65 char linkname[100];
66 char _padding[255];
67} mtar_raw_header_t;
68
69
70typedef struct mtar_t mtar_t;
71
72struct mtar_t {
73 int (*read)(mtar_t *tar, void *data, unsigned size);
74 int (*write)(mtar_t *tar, const void *data, unsigned size);
75 int (*seek)(mtar_t *tar, unsigned pos);
76 int (*close)(mtar_t *tar);
77 void *stream;
78 unsigned pos;
79 unsigned remaining_data;
80 unsigned last_header;
81 mtar_header_t header;
82 mtar_raw_header_t raw_header;
83}; 81};
84 82
83struct mtar_ops {
84 int(*read)(void* stream, void* data, unsigned size);
85 int(*write)(void* stream, const void* data, unsigned size);
86 int(*seek)(void* stream, unsigned pos);
87 int(*close)(void* stream);
88};
89
90struct mtar {
91 char buffer[512]; /* IO buffer, put first to allow library users to
92 * control its alignment */
93 int state; /* Used to simplify the API and verify API usage */
94 int access; /* Access mode */
95 unsigned pos; /* Current position in file */
96 unsigned end_pos; /* End position of the current file */
97 unsigned header_pos; /* Position of the current header */
98 mtar_header_t header; /* Most recently parsed header */
99 const mtar_ops_t* ops; /* Stream operations */
100 void* stream; /* Stream handle */
101};
85 102
86const char* mtar_strerror(int err); 103const char* mtar_strerror(int err);
87 104
88int mtar_open(mtar_t *tar, const char *filename, const char *mode); 105void mtar_init(mtar_t* tar, int access, const mtar_ops_t* ops, void* stream);
89int mtar_close(mtar_t *tar); 106int mtar_close(mtar_t* tar);
90 107int mtar_is_open(mtar_t* tar);
91int mtar_seek(mtar_t *tar, unsigned pos); 108
92int mtar_rewind(mtar_t *tar); 109mtar_header_t* mtar_get_header(mtar_t* tar);
93int mtar_next(mtar_t *tar); 110int mtar_access_mode(const mtar_t* tar);
94int mtar_find(mtar_t *tar, const char *name, mtar_header_t *h); 111
95int mtar_read_header(mtar_t *tar, mtar_header_t *h); 112int mtar_rewind(mtar_t* tar);
96int mtar_read_data(mtar_t *tar, void *ptr, unsigned size); 113int mtar_next(mtar_t* tar);
97 114int mtar_foreach(mtar_t* tar, mtar_foreach_cb cb, void* arg);
98int mtar_write_header(mtar_t *tar, const mtar_header_t *h); 115int mtar_find(mtar_t* tar, const char* name);
99int mtar_write_file_header(mtar_t *tar, const char *name, unsigned size); 116
100int mtar_write_dir_header(mtar_t *tar, const char *name); 117int mtar_read_data(mtar_t* tar, void* ptr, unsigned size);
101int mtar_write_data(mtar_t *tar, const void *data, unsigned size); 118int mtar_seek_data(mtar_t* tar, int offset, int whence);
102int mtar_finalize(mtar_t *tar); 119unsigned mtar_tell_data(mtar_t* tar);
120int mtar_eof_data(mtar_t* tar);
121
122int mtar_write_header(mtar_t* tar, const mtar_header_t* h);
123int mtar_update_header(mtar_t* tar, const mtar_header_t* h);
124int mtar_write_file_header(mtar_t* tar, const char* name, unsigned size);
125int mtar_write_dir_header(mtar_t* tar, const char* name);
126int mtar_write_data(mtar_t* tar, const void* ptr, unsigned size);
127int mtar_update_file_size(mtar_t* tar);
128int mtar_end_data(mtar_t* tar);
129int mtar_finalize(mtar_t* tar);
103 130
104#ifdef __cplusplus 131#ifdef __cplusplus
105} 132}