Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/amachronic/microtar.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAidan MacDonald <amachronic@protonmail.com>2021-11-07 16:36:07 +0300
committerAidan MacDonald <amachronic@protonmail.com>2021-11-25 23:29:03 +0300
commit6252ed2174b6c5e4c985af0e1b03ac76a52115c0 (patch)
treeafba328d58b1398ab2f35d842f67c13795ae93e3
parentd7015e92a719c9a8cf46217d2901eceeff6ffbbc (diff)
Allow seek during writes
This allows library users to seek within the written portion of a file and re-write previously written data. This also includes the header, which removes the need to specify file size in advance if the application can tolerate seeking back to update the header at the end of writing, once the actual file size is known.
-rw-r--r--README.md43
-rw-r--r--src/microtar.c146
-rw-r--r--src/microtar.h5
3 files changed, 140 insertions, 54 deletions
diff --git a/README.md b/README.md
index 7526ef0..109626c 100644
--- a/README.md
+++ b/README.md
@@ -170,30 +170,39 @@ data with a simple POSIX-like API.
### Writing archives
-If you have opened an archive for writing, your options are a bit more
-limited than with reading as you need to generate the whole archive in
-a single pass. Seeking around and rewriting previously written data is
-not allowed. Support for this wouldn't be hard to add, but it was not
-included in the interest of simplicity.
-
-The main functions are:
+Microtar has limited support for creating archives. When an archive is opened
+for writing, you can add new members using `mtar_write_header()`.
- `mtar_write_header(tar, header)` writes out the header for a new member.
- The amount of data that follows is dictated by `header->size` and you
- will have to write it out before moving to the next member.
+ The amount of data that follows is dictated by `header->size`, though if
+ the underlying stream supports seeking and re-writing data, this size can
+ be updated later with `mtar_update_header()` or `mtar_update_file_size()`.
+
+- `mtar_update_header(tar, header)` will re-write the previously written
+ header. This may be used to change any header field. The underlying stream
+ must support seeking. On a successful return the stream will be returned
+ to the position it was at before the call.
+
+File data can be written with `mtar_write_data()`, and if the underlying stream
+supports seeking, you can seek with `mtar_seek_data()` and read back previously
+written data with `mtar_read_data()`. Note that it is not possible to truncate
+the file stream by any means.
- `mtar_write_data(tar, buf, count)` will write up to `count` bytes from
`buf` to the current member's data. Returns the number of bytes actually
- written or a negative error code. If you provide too much data, a short
- count is returned.
+ written or a negative error code.
+
+- `mtar_update_file_size(tar)` will update the header size to reflect the
+ actual amount of written data. This is intended to be called right before
+ `mtar_end_data()` if you are not declaring file sizes in advance.
-- `mtar_end_data(tar)` will end the current member. It will complain
- if you did not write the correct amount data provided in the header.
- This must be called before writing the next header.
+- `mtar_end_data(tar)` will end the current member. It will complain if you
+ did not write the correct amount data provided in the header. This must be
+ called before writing the next header.
-- `mtar_finalize(tar)` is called after you have written all members to
- the archive. It writes out some null records which mark the end of the
- archive, so you cannot write any more archive members after this.
+- `mtar_finalize(tar)` is called after you have written all members to the
+ archive. It writes out some null records which mark the end of the archive,
+ so you cannot write any more archive members after this.
Note that `mtar_close()` can fail if there was a problem flushing buffered
data to disk, so its return value should always be checked.
diff --git a/src/microtar.c b/src/microtar.c
index 2cb9733..21389cd 100644
--- a/src/microtar.c
+++ b/src/microtar.c
@@ -248,6 +248,16 @@ static int header_to_raw(char* raw, const mtar_header_t* h)
return MTAR_ESUCCESS;
}
+static unsigned data_beg_pos(const mtar_t* tar)
+{
+ return tar->header_pos + HEADER_LEN;
+}
+
+static unsigned data_end_pos(const mtar_t* tar)
+{
+ return tar->end_pos;
+}
+
static int ensure_header(mtar_t* tar)
{
int ret, err;
@@ -255,7 +265,12 @@ static int ensure_header(mtar_t* tar)
if(tar->state & S_HEADER_VALID)
return MTAR_ESUCCESS;
+ if(tar->pos > UINT_MAX - HEADER_LEN)
+ return MTAR_EOVERFLOW;
+
tar->header_pos = tar->pos;
+ tar->end_pos = data_beg_pos(tar);
+
ret = tread(tar, tar->buffer, HEADER_LEN);
if(ret < 0)
return ret;
@@ -266,20 +281,14 @@ static int ensure_header(mtar_t* tar)
if(err)
return err;
+ if(tar->end_pos > UINT_MAX - tar->header.size)
+ return MTAR_EOVERFLOW;
+ tar->end_pos += tar->header.size;
+
tar->state |= S_HEADER_VALID;
return MTAR_ESUCCESS;
}
-static unsigned data_beg_pos(const mtar_t* tar)
-{
- return tar->header_pos + HEADER_LEN;
-}
-
-static unsigned data_end_pos(const mtar_t* tar)
-{
- return data_beg_pos(tar) + tar->header.size;
-}
-
const char* mtar_strerror(int err)
{
switch(err) {
@@ -296,7 +305,7 @@ const char* mtar_strerror(int err)
case MTAR_EOVERFLOW: return "overflow";
case MTAR_EAPI: return "API usage error";
case MTAR_ENAMETOOLONG: return "name too long";
- case MTAR_ETOOSHORT: return "file too short";
+ case MTAR_EWRONGSIZE: return "wrong amount of data written";
case MTAR_EACCESS: return "wrong access mode";
default: return "unknown error";
}
@@ -431,8 +440,6 @@ int mtar_read_data(mtar_t* tar, void* ptr, unsigned size)
int mtar_seek_data(mtar_t* tar, int offset, int whence)
{
#ifndef MICROTAR_DISABLE_API_CHECKS
- if(tar->access != MTAR_READ)
- return MTAR_EACCESS;
if(!(tar->state & S_HEADER_VALID))
return MTAR_EAPI;
#endif
@@ -474,8 +481,6 @@ int mtar_seek_data(mtar_t* tar, int offset, int whence)
unsigned mtar_tell_data(mtar_t* tar)
{
#ifndef MICROTAR_DISABLE_API_CHECKS
- if(tar->access != MTAR_READ)
- return MTAR_EACCESS;
if(!(tar->state & S_HEADER_VALID))
return MTAR_EAPI;
#endif
@@ -485,9 +490,7 @@ unsigned mtar_tell_data(mtar_t* tar)
int mtar_eof_data(mtar_t* tar)
{
- /* API usage errors, but just claim EOF. */
- if(tar->access != MTAR_READ)
- return 1;
+ /* API usage error, but just claim EOF. */
if(!(tar->state & S_HEADER_VALID))
return 1;
@@ -504,12 +507,20 @@ int mtar_write_header(mtar_t* tar, const mtar_header_t* h)
return MTAR_EAPI;
#endif
- tar->state &= ~(S_WROTE_HEADER | S_WROTE_DATA | S_WROTE_DATA_EOF);
+ tar->state &= ~(S_HEADER_VALID | S_WROTE_HEADER |
+ S_WROTE_DATA | S_WROTE_DATA_EOF);
+
+ /* ensure we have enough space to write the declared amount of data */
+ if(tar->pos > UINT_MAX - HEADER_LEN - round_up_512(h->size))
+ return MTAR_EOVERFLOW;
+
tar->header_pos = tar->pos;
+ tar->end_pos = data_beg_pos(tar);
+
if(h != &tar->header)
tar->header = *h;
- int err = header_to_raw(tar->buffer, h);
+ int err = header_to_raw(tar->buffer, &tar->header);
if(err)
return err;
@@ -519,10 +530,46 @@ int mtar_write_header(mtar_t* tar, const mtar_header_t* h)
if(ret != HEADER_LEN)
return MTAR_EWRITEFAIL;
- tar->state |= S_WROTE_HEADER;
+ tar->state |= (S_HEADER_VALID | S_WROTE_HEADER);
return MTAR_ESUCCESS;
}
+int mtar_update_header(mtar_t* tar, const mtar_header_t* h)
+{
+#ifndef MICROTAR_DISABLE_API_CHECKS
+ if(tar->access != MTAR_WRITE)
+ return MTAR_EACCESS;
+ if(!(tar->state & S_WROTE_HEADER) ||
+ (tar->state & S_WROTE_DATA_EOF) ||
+ (tar->state & S_WROTE_FINALIZE))
+ return MTAR_EAPI;
+#endif
+
+ unsigned beg_pos = data_beg_pos(tar);
+ if(beg_pos > UINT_MAX - h->size)
+ return MTAR_EOVERFLOW;
+
+ unsigned old_pos = tar->pos;
+ int err = tseek(tar, tar->header_pos);
+ if(err)
+ return err;
+
+ if(h != &tar->header)
+ tar->header = *h;
+
+ err = header_to_raw(tar->buffer, &tar->header);
+ if(err)
+ return err;
+
+ int len = twrite(tar, tar->buffer, HEADER_LEN);
+ if(len < 0)
+ return len;
+ if(len != HEADER_LEN)
+ return MTAR_EWRITEFAIL;
+
+ return tseek(tar, old_pos);
+}
+
int mtar_write_file_header(mtar_t* tar, const char* name, unsigned size)
{
size_t namelen = strlen(name);
@@ -570,18 +617,33 @@ int mtar_write_data(mtar_t* tar, const void* ptr, unsigned size)
return MTAR_EAPI;
#endif
- /* don't allow writing more than was specified in the header,
- * as this would require seeking back & updating it */
- unsigned data_end = data_end_pos(tar);
- if(tar->pos >= data_end)
- return 0;
+ tar->state |= S_WROTE_DATA;
- unsigned data_left = data_end - tar->pos;
- if(size > data_left)
- size = data_left;
+ int err = twrite(tar, ptr, size);
+ if(tar->pos > tar->end_pos)
+ tar->end_pos = tar->pos;
- tar->state |= S_WROTE_DATA;
- return twrite(tar, ptr, size);
+ return err;
+}
+
+int mtar_update_file_size(mtar_t* tar)
+{
+#ifndef MICROTAR_DISABLE_API_CHECKS
+ if(tar->access != MTAR_WRITE)
+ return MTAR_EACCESS;
+ if(!(tar->state & S_WROTE_HEADER) ||
+ (tar->state & S_WROTE_DATA_EOF) ||
+ (tar->state & S_WROTE_FINALIZE))
+ return MTAR_EAPI;
+#endif
+
+ unsigned new_size = data_end_pos(tar) - data_beg_pos(tar);
+ if(new_size == tar->header.size)
+ return MTAR_ESUCCESS;
+ else {
+ tar->header.size = new_size;
+ return mtar_update_header(tar, &tar->header);
+ }
}
int mtar_end_data(mtar_t* tar)
@@ -589,16 +651,28 @@ int mtar_end_data(mtar_t* tar)
#ifndef MICROTAR_DISABLE_API_CHECKS
if(tar->access != MTAR_WRITE)
return MTAR_EACCESS;
- if((tar->state & S_WROTE_DATA_EOF) ||
+ if(!(tar->state & S_WROTE_HEADER) ||
+ (tar->state & S_WROTE_DATA_EOF) ||
(tar->state & S_WROTE_FINALIZE))
return MTAR_EAPI;
#endif
- /* make sure the caller wrote out the expected amount of data */
- if(tar->pos < data_end_pos(tar))
- return MTAR_ETOOSHORT;
+ int err;
+
+ /* ensure the caller wrote the correct amount of data */
+ unsigned expected_end = data_beg_pos(tar) + tar->header.size;
+ if(tar->end_pos != expected_end)
+ return MTAR_EWRONGSIZE;
+
+ /* ensure we're positioned at the end of the stream */
+ if(tar->pos != tar->end_pos) {
+ err = tseek(tar, tar->end_pos);
+ if(err)
+ return err;
+ }
- int err = write_null_bytes(tar, round_up_512(tar->pos) - tar->pos);
+ /* write remainder of the 512-byte record */
+ err = write_null_bytes(tar, round_up_512(tar->pos) - tar->pos);
if(err)
return err;
diff --git a/src/microtar.h b/src/microtar.h
index 0feb5ad..fe95619 100644
--- a/src/microtar.h
+++ b/src/microtar.h
@@ -44,7 +44,7 @@ enum mtar_error {
MTAR_EOVERFLOW = -10,
MTAR_EAPI = -11,
MTAR_ENAMETOOLONG = -12,
- MTAR_ETOOSHORT = -13,
+ MTAR_EWRONGSIZE = -13,
MTAR_EACCESS = -14,
MTAR_ELAST = MTAR_ETOOSHORT,
};
@@ -94,6 +94,7 @@ struct mtar {
int state; /* Used to simplify the API and verify API usage */
int access; /* Access mode */
unsigned pos; /* Current position in file */
+ unsigned end_pos; /* End position of the current file */
unsigned header_pos; /* Position of the current header */
mtar_header_t header; /* Most recently parsed header */
const mtar_ops_t* ops; /* Stream operations */
@@ -120,9 +121,11 @@ unsigned mtar_tell_data(mtar_t* tar);
int mtar_eof_data(mtar_t* tar);
int mtar_write_header(mtar_t* tar, const mtar_header_t* h);
+int mtar_update_header(mtar_t* tar, const mtar_header_t* h);
int mtar_write_file_header(mtar_t* tar, const char* name, unsigned size);
int mtar_write_dir_header(mtar_t* tar, const char* name);
int mtar_write_data(mtar_t* tar, const void* ptr, unsigned size);
+int mtar_update_file_size(mtar_t* tar);
int mtar_end_data(mtar_t* tar);
int mtar_finalize(mtar_t* tar);