Verified Commit dcb7a8cf authored by Karel Koci's avatar Karel Koci 🤘

Add multiwrite

This is simple wrapper on top of multiple opened files. Idea is that you
can open additional files and all subsequent writes are written to all
of those files.
The intended use is for collecting CA and CRL certificates to single
file. In that implementation we can have multiple nodes reusing same
base list of CAs and CRLs. To allows deallocation of deployed
certificates we create files that can be read later when ever we detect
such reuse. This simple code makes this operation as easy as writing to
single file descriptor.
parent d709dc6f
......@@ -31,6 +31,7 @@ libupdater_MODULES := \
picosat \
util \
syscnf \
multiwrite \
logging
ifdef COV
libupdater_MODULES += lcoverage.embed
......
/*
* Copyright 2019, CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This file is part of the turris updater.
*
* Updater is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Updater is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Updater. If not, see <http://www.gnu.org/licenses/>.
*/
#define _GNU_SOURCE
#include "multiwrite.h"
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
void mwrite_init(struct mwrite* mw) {
memset(mw, 0, sizeof *mw);
}
bool mwrite_add(struct mwrite *mw, int fd) {
if (fd == -1) // open failed (errno is set by open)
return false;
mw->count++;
mw->fds = realloc(mw->fds, mw->count * sizeof *mw->fds);
mw->fds[mw->count - 1] = fd;
return true;
}
bool mwrite_open(struct mwrite *mw, const char *pathname, int flags) {
int fd = open(pathname, flags, O_WRONLY);
return mwrite_add(mw, fd);
}
bool mwrite_mkstemp(struct mwrite *mw, char *template, int flags) {
int fd = mkostemp(template, flags);
return mwrite_add(mw, fd);
}
enum mwrite_result mwrite_write(struct mwrite *mw, const void *buf, size_t count) {
for (size_t i = 0; i < mw->count; i++) {
const void *lbuf = buf;
size_t tow = count;
do {
int ret = write(mw->fds[i], lbuf, tow);
if (ret < 0) {
if (errno != EINTR)
continue; // just try again
else
return MWRITE_R_STD_ERROR;
}
if (ret == 0)
return MWRITE_R_UNABLE_TO_WRITE;
tow -= ret;
} while (tow > 0);
}
return MWRITE_R_OK;
}
enum mwrite_result mwrite_str_write(struct mwrite *mw, const char *str) {
return mwrite_write(mw, str, strlen(str) * sizeof *str);
}
bool mwrite_close(struct mwrite *mw) {
for (size_t i = 0; i < mw->count; i++) {
int res;
while ((res = close(mw->fds[i])) != 0 && errno == EINTR);
if (res)
return false;
}
free(mw->fds);
mwrite_init(mw);
return true;
}
/*
* Copyright 2019, CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This file is part of the Turris Updater.
*
* Updater is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Updater is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Updater. If not, see <http://www.gnu.org/licenses/>.
*/
/* This implements a way to write to multiple files at once. It is not optimal in
* any way. It opens FD for every file and writes data in loop. There seems to be
* no existing approach on how to open multiple files and write to them all at
* once (something like having multiple files under one file descriptor). If there
* is such API or approach possible then this code should be dropped and all usage
* should be replaced with given API.
*/
#ifndef UPDATER_MULTIWRITE_H
#define UPDATER_MULTIWRITE_H
#include <stdlib.h>
#include <stdbool.h>
// MultiWrite handler
struct mwrite {
size_t count;
int *fds;
};
// Result of mwrite_write function
enum mwrite_result {
MWRITE_R_OK = 0, // Write was successful
MWRITE_R_STD_ERROR, // There was an standard error (use errno)
MWRITE_R_UNABLE_TO_WRITE, // Write is unable to proceed (zero bytes written)
};
// Handler initialization function. Please call this before any other function.
void mwrite_init(struct mwrite*);
// Open pathname for writing. All subsequent calls to mwrite_write would write
// also to this file if open is successful.
// You can provide additional flags. These flags are same as in case of open.
// It returns false if error occurred (in such case errno is set), otherwise true
// is returned.
bool mwrite_open(struct mwrite*, const char *pathname, int flags);
// This is same as mwrite_open but instead of using open it uses mkostemp to open
// file descriptor.
bool mwrite_mkstemp(struct mwrite*, char *template, int flags);
// Write data to mwrite
// This is pretty much same as standard write. The only difference is that this
// implementation always writes all provided data unless error is detected.
// This returns MWRITE_R_OK if write was successful. MWRITE_R_STD_ERROR is
// returned when standard error is detected and MWRITE_R_UNABLE_TO_WRITE is
// returned if write is unable to proceed (probably because of not enough space).
// Note that if error is detected that some writes can be completed and others
// might not be. This means that on error there are no guaranties on state of all
// written files.
enum mwrite_result mwrite_write(struct mwrite*, const void *buf, size_t count);
// Same as mwrite_write but calculates size of string using strlen.
enum mwrite_result mwrite_str_write(struct mwrite*, const char *str);
// Close all previously opened files. This effectively returns handler to same
// state as it is after mwrite_init call.
// Returns false if error occurred (in such case errno is set), otherwise true is
// returned. Note that on error not all file descriptors are closed and that there
// is currently no recovery way. You should exit program instead.
bool mwrite_close(struct mwrite*);
#endif
......@@ -10,6 +10,7 @@ C_TESTS := \
events \
util \
syscnf \
multiwrite \
subprocess \
download \
interpreter
......
/*
* Copyright 2019, CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This file is part of the turris updater.
*
* Updater is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Updater is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Updater. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ctest.h"
#include <string.h>
#include "../src/lib/multiwrite.h"
#include "../src/lib/util.h"
static const char *lorem_ipsum[] = {
"Lorem\n",
"ipsum\n",
"dolor\n",
"sit\n",
"amet\n",
"consectetur\n",
"adipiscing\n",
"elit\n",
"sed\n",
"do\n",
"eiusmod\n",
"tempor\n",
"incididunt\n",
"ut\n",
"labore\n",
"et\n",
"dolore\n",
"magna\n",
"aliqua"
};
static const int lorem_ipsum_size = sizeof(lorem_ipsum) / sizeof(lorem_ipsum[0]);
START_TEST(mwrite_lorem) {
char *tmpdir = getenv("TMPDIR");
if (!tmpdir)
tmpdir = "/tmp";
struct mwrite mw;
char *files[lorem_ipsum_size];
mwrite_init(&mw);
for (int i = 0; i < lorem_ipsum_size; i++) {
files[i] = aprintf("%s/updater-mwrite-%s-XXXXXX", tmpdir, lorem_ipsum[i]);
ck_assert(mwrite_mkstemp(&mw, files[i], 0));
ck_assert_int_eq(MWRITE_R_OK, mwrite_str_write(&mw, lorem_ipsum[i]));
}
ck_assert(mwrite_close(&mw));
for (int i = 0; i < lorem_ipsum_size; i++) {
FILE *f = fopen(files[i], "r");
char *line = NULL;
size_t len = 0;
for (int y = 0; y < (lorem_ipsum_size - i); y++) {
ck_assert(getline(&line, &len, f) != -1);
ck_assert_str_eq(lorem_ipsum[y+i], line);
}
free(line);
fclose(f);
unlink(files[i]);
}
}
END_TEST
Suite *gen_test_suite(void) {
Suite *result = suite_create("MultiWrite");
TCase *mwrite = tcase_create("mwrite");
tcase_set_timeout(mwrite, 30);
tcase_add_test(mwrite, mwrite_lorem);
suite_add_tcase(result, mwrite);
return result;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment