From 1e9b0691a9c52f8b56abc10a214a8aaa228b361b Mon Sep 17 00:00:00 2001 From: Artem Bityutskiy Date: Sun, 20 Apr 2008 18:14:37 +0300 Subject: ubi-utils: various clean-ups and preparations This patch introduces many clean-ups, nicifications and preparations to the following ubiformat utility. There are also fixes. Signed-off-by: Artem Bityutskiy --- ubi-utils/new-utils/include/libubi.h | 11 ++++ ubi-utils/new-utils/include/libubigen.h | 18 +++--- ubi-utils/new-utils/src/common.h | 25 ++++---- ubi-utils/new-utils/src/libubi.c | 11 ++-- ubi-utils/new-utils/src/libubigen.c | 100 +++++++++++++++++--------------- ubi-utils/new-utils/src/ubiattach.c | 4 +- ubi-utils/new-utils/src/ubinize.c | 72 +++++++++++------------ 7 files changed, 131 insertions(+), 110 deletions(-) diff --git a/ubi-utils/new-utils/include/libubi.h b/ubi-utils/new-utils/include/libubi.h index 3e8c55a..0f5c9c8 100644 --- a/ubi-utils/new-utils/include/libubi.h +++ b/ubi-utils/new-utils/include/libubi.h @@ -194,6 +194,17 @@ void libubi_close(libubi_t desc); */ int ubi_get_info(libubi_t desc, struct ubi_info *info); +/** + * mtd_num2ubi_dev - find UBI device by attached MTD device. + * @@desc: UBI library descriptor + * @mtd_num: MTD device number + * @dev_num: UBI device number is returned here + * + * This function finds UBI device to which MTD device @mtd_num is attached. + * Returns %0 if the UBI device was found and %-1 if not. + */ +int mtd_num2ubi_dev(libubi_t desc, int mtd_num, int *dev_num); + /** * ubi_attach_mtd - attach MTD device to UBI. * @desc: UBI library descriptor diff --git a/ubi-utils/new-utils/include/libubigen.h b/ubi-utils/new-utils/include/libubigen.h index 058cf8a..c2b95b0 100644 --- a/ubi-utils/new-utils/include/libubigen.h +++ b/ubi-utils/new-utils/include/libubigen.h @@ -26,8 +26,6 @@ #define __LIBUBIGEN_H__ #include -#include -#include #ifdef __cplusplus extern "C" { @@ -41,7 +39,6 @@ extern "C" { * @vid_hdr_offs: offset of the VID header * @data_offs: data offset * @ubi_ver: UBI version - * @ec: initial erase counter * @vtbl_size: volume table size * @max_volumes: maximum amount of volumes */ @@ -53,7 +50,6 @@ struct ubigen_info int vid_hdr_offs; int data_offs; int ubi_ver; - long long ec; int vtbl_size; int max_volumes; }; @@ -92,18 +88,20 @@ struct ubigen_vol_info }; void ubigen_info_init(struct ubigen_info *ui, int peb_size, int min_io_size, - int subpage_size, int vid_hdr_offs, int ubi_ver, - long long ec); + int subpage_size, int vid_hdr_offs, int ubi_ver); struct ubi_vtbl_record *ubigen_create_empty_vtbl(const struct ubigen_info *ui); +void ubigen_init_ec_hdr(const struct ubigen_info *ui, + struct ubi_ec_hdr *hdr, long long ec); int ubigen_get_vtbl_size(const struct ubigen_info *ui); int ubigen_add_volume(const struct ubigen_info *ui, const struct ubigen_vol_info *vi, struct ubi_vtbl_record *vtbl); int ubigen_write_volume(const struct ubigen_info *ui, - const struct ubigen_vol_info *vi, - long long bytes, FILE *in, FILE *out); -int ubigen_write_layout_vol(const struct ubigen_info *ui, - struct ubi_vtbl_record *vtbl, FILE *out); + const struct ubigen_vol_info *vi, long long ec, + long long bytes, int in, int out); +int ubigen_write_layout_vol(const struct ubigen_info *ui, int peb1, int peb2, + long long ec1, long long ec2, + struct ubi_vtbl_record *vtbl, int fd); #ifdef __cplusplus } diff --git a/ubi-utils/new-utils/src/common.h b/ubi-utils/new-utils/src/common.h index bd99341..720dec1 100644 --- a/ubi-utils/new-utils/src/common.h +++ b/ubi-utils/new-utils/src/common.h @@ -43,24 +43,29 @@ extern "C" { #define normsg_cont(fmt, ...) do { \ printf(PROGRAM_NAME ": " fmt, ##__VA_ARGS__); \ } while(0) +#define normsg_cont(fmt, ...) do { \ + printf(PROGRAM_NAME ": " fmt, ##__VA_ARGS__); \ +} while(0) /* Error messages */ -#define errmsg(fmt, ...) ({ \ - fprintf(stderr, PROGRAM_NAME " error: " fmt "\n", ##__VA_ARGS__); \ - -1; \ +#define errmsg(fmt, ...) ({ \ + fprintf(stderr, PROGRAM_NAME ": error!: " fmt "\n", ##__VA_ARGS__); \ + -1; \ }) /* System error messages */ -#define sys_errmsg(fmt, ...) ({ \ - int _err = errno; \ - fprintf(stderr, PROGRAM_NAME " error: " fmt "\n", ##__VA_ARGS__); \ - fprintf(stderr, "error %d (%s)\n", _err, strerror(_err)); \ - -1; \ +#define sys_errmsg(fmt, ...) ({ \ + int _err = errno, _i; \ + fprintf(stderr, PROGRAM_NAME ": error!: " fmt "\n", ##__VA_ARGS__); \ + for (_i = 0; _i < sizeof(PROGRAM_NAME) + 1; _i++) \ + fprintf(stderr, " "); \ + fprintf(stderr, "error %d (%s)\n", _err, strerror(_err)); \ + -1; \ }) /* Warnings */ -#define warnmsg(fmt, ...) do { \ - fprintf(stderr, PROGRAM_NAME " warning: " fmt "\n", ##__VA_ARGS__); \ +#define warnmsg(fmt, ...) do { \ + fprintf(stderr, PROGRAM_NAME ": warning!: " fmt "\n", ##__VA_ARGS__); \ } while(0) long long ubiutils_get_bytes(const char *str); diff --git a/ubi-utils/new-utils/src/libubi.c b/ubi-utils/new-utils/src/libubi.c index 5e6f9b1..8f95108 100644 --- a/ubi-utils/new-utils/src/libubi.c +++ b/ubi-utils/new-utils/src/libubi.c @@ -477,12 +477,13 @@ static int dev_node2num(struct libubi *lib, const char *node, int *dev_num) return -1; } -static int mtd_num2ubi_dev(struct libubi *lib, int mtd_num, int *dev_num) +int mtd_num2ubi_dev(libubi_t desc, int mtd_num, int *dev_num) { struct ubi_info info; int i, ret, mtd_num1; + struct libubi *lib = desc; - if (ubi_get_info((libubi_t *)lib, &info)) + if (ubi_get_info(desc, &info)) return -1; for (i = info.lowest_dev_num; i <= info.highest_dev_num; i++) { @@ -500,7 +501,7 @@ static int mtd_num2ubi_dev(struct libubi *lib, int mtd_num, int *dev_num) } } - errno = ENODEV; + errno = 0; return -1; } @@ -714,8 +715,10 @@ int ubi_detach_mtd(libubi_t desc, const char *node, int mtd_num) int ret, ubi_dev; ret = mtd_num2ubi_dev(desc, mtd_num, &ubi_dev); - if (ret == -1) + if (ret == -1) { + errno = ENODEV; return ret; + } return ubi_remove_dev(desc, node, ubi_dev); } diff --git a/ubi-utils/new-utils/src/libubigen.c b/ubi-utils/new-utils/src/libubigen.c index bee6f77..70ef0ca 100644 --- a/ubi-utils/new-utils/src/libubigen.c +++ b/ubi-utils/new-utils/src/libubigen.c @@ -26,11 +26,11 @@ #include #include -#include #include #include #include +#include #include #include "crc32.h" #include "common.h" @@ -46,11 +46,9 @@ * @min_io_size if does not exist) * @vid_hdr_offs: offset of the VID header * @ubi_ver: UBI version - * @ec: initial erase counter */ void ubigen_info_init(struct ubigen_info *ui, int peb_size, int min_io_size, - int subpage_size, int vid_hdr_offs, int ubi_ver, - long long ec) + int subpage_size, int vid_hdr_offs, int ubi_ver) { if (!vid_hdr_offs) { vid_hdr_offs = UBI_EC_HDR_SIZE + subpage_size - 1; @@ -66,12 +64,11 @@ void ubigen_info_init(struct ubigen_info *ui, int peb_size, int min_io_size, ui->data_offs *= min_io_size; ui->leb_size = peb_size - ui->data_offs; ui->ubi_ver = ubi_ver; - ui->ec = ec; - ui->vtbl_size = ui->leb_size; - if (ui->vtbl_size > UBI_MAX_VOLUMES * UBI_VTBL_RECORD_SIZE) - ui->vtbl_size = UBI_MAX_VOLUMES * UBI_VTBL_RECORD_SIZE; - ui->max_volumes = ui->vtbl_size / UBI_VTBL_RECORD_SIZE; + ui->max_volumes = ui->leb_size / UBI_VTBL_RECORD_SIZE; + if (ui->max_volumes > UBI_MAX_VOLUMES) + ui->max_volumes = UBI_MAX_VOLUMES; + ui->vtbl_size = ui->max_volumes * UBI_VTBL_RECORD_SIZE; } /** @@ -88,11 +85,11 @@ struct ubi_vtbl_record *ubigen_create_empty_vtbl(const struct ubigen_info *ui) vtbl = calloc(1, ui->vtbl_size); if (!vtbl) { - errmsg("cannot allocate %d bytes of memory", ui->vtbl_size); + sys_errmsg("cannot allocate %d bytes of memory", ui->vtbl_size); return NULL; } - for (i = 0; i < UBI_MAX_VOLUMES; i++) { + for (i = 0; i < ui->max_volumes; i++) { uint32_t crc = crc32(UBI_CRC32_INIT, &vtbl[i], UBI_VTBL_RECORD_SIZE_CRC); vtbl[i].crc = cpu_to_be32(crc); @@ -144,12 +141,13 @@ int ubigen_add_volume(const struct ubigen_info *ui, } /** - * init_ec_hdr - initialize EC header. + * ubigen_init_ec_hdr - initialize EC header. * @ui: libubigen information * @hdr: the EC header to initialize + * @ec: erase counter value */ -static void init_ec_hdr(const struct ubigen_info *ui, - struct ubi_ec_hdr *hdr) +void ubigen_init_ec_hdr(const struct ubigen_info *ui, + struct ubi_ec_hdr *hdr, long long ec) { uint32_t crc; @@ -157,7 +155,7 @@ static void init_ec_hdr(const struct ubigen_info *ui, hdr->magic = cpu_to_be32(UBI_EC_HDR_MAGIC); hdr->version = ui->ubi_ver; - hdr->ec = cpu_to_be64(ui->ec); + hdr->ec = cpu_to_be64(ec); hdr->vid_hdr_offset = cpu_to_be32(ui->vid_hdr_offs); hdr->data_offset = cpu_to_be32(ui->data_offs); @@ -210,6 +208,7 @@ static void init_vid_hdr(const struct ubigen_info *ui, * ubigen_write_volume - write UBI volume. * @ui: libubigen information * @vi: volume information + * @ec: erase coutner value to put to EC headers * @bytes: volume size in bytes * @in: input file descriptor (has to be properly seeked) * @out: output file descriptor @@ -219,8 +218,8 @@ static void init_vid_hdr(const struct ubigen_info *ui, * %-1 on failure. */ int ubigen_write_volume(const struct ubigen_info *ui, - const struct ubigen_vol_info *vi, - long long bytes, FILE *in, FILE *out) + const struct ubigen_vol_info *vi, long long ec, + long long bytes, int in, int out) { int len = vi->usable_leb_size, rd, lnum = 0; char inbuf[ui->leb_size], outbuf[ui->peb_size]; @@ -234,7 +233,7 @@ int ubigen_write_volume(const struct ubigen_info *ui, vi->alignment, ui->leb_size); memset(outbuf, 0xFF, ui->data_offs); - init_ec_hdr(ui, (struct ubi_ec_hdr *)outbuf); + ubigen_init_ec_hdr(ui, (struct ubi_ec_hdr *)outbuf, ec); while (bytes) { int l; @@ -246,13 +245,9 @@ int ubigen_write_volume(const struct ubigen_info *ui, l = len; do { - rd = fread(inbuf + len - l, 1, l, in); - if (rd == 0) { - if (ferror(in)) - return errmsg("cannot read %d bytes from the input file", l); - else - return errmsg("not enough data in the input file"); - } + rd = read(in, inbuf + len - l, l); + if (rd != l) + return sys_errmsg("cannot read %d bytes from the input file", l); l -= rd; } while (l); @@ -264,8 +259,8 @@ int ubigen_write_volume(const struct ubigen_info *ui, memset(outbuf + ui->data_offs + len, 0xFF, ui->peb_size - ui->data_offs - len); - if (fwrite(outbuf, 1, ui->peb_size, out) != ui->peb_size) - return errmsg("cannot write %d bytes from the output file", l); + if (write(out, outbuf, ui->peb_size) != ui->peb_size) + return sys_errmsg("cannot write %d bytes to the output file", ui->peb_size); lnum += 1; } @@ -276,27 +271,30 @@ int ubigen_write_volume(const struct ubigen_info *ui, /** * ubigen_write_layout_vol - write UBI layout volume * @ui: libubigen information + * @peb1: physical eraseblock number to write the first volume table copy + * @peb2: physical eraseblock number to write the second volume table copy + * @ec1: erase counter value for @peb1 + * @ec2: erase counter value for @peb1 * @vtbl: volume table - * @out: output file stream + * @fd: output file descriptor seeked to the proper position * * This function creates the UBI layout volume which contains 2 copies of the * volume table. Returns zero in case of success and %-1 in case of failure. */ -int ubigen_write_layout_vol(const struct ubigen_info *ui, - struct ubi_vtbl_record *vtbl, FILE *out) +int ubigen_write_layout_vol(const struct ubigen_info *ui, int peb1, int peb2, + long long ec1, long long ec2, + struct ubi_vtbl_record *vtbl, int fd) { - int size = ui->leb_size; + int ret; struct ubigen_vol_info vi; char outbuf[ui->peb_size]; struct ubi_vid_hdr *vid_hdr; - - if (size > UBI_MAX_VOLUMES * UBI_VTBL_RECORD_SIZE) - size = UBI_MAX_VOLUMES * UBI_VTBL_RECORD_SIZE; + off_t seek; vi.bytes = ui->leb_size * UBI_LAYOUT_VOLUME_EBS; vi.id = UBI_LAYOUT_VOLUME_ID; vi.alignment = UBI_LAYOUT_VOLUME_ALIGN; - vi.data_pad = ui->leb_size % UBI_LAYOUT_VOLUME_ALIGN; + vi.data_pad = ui->leb_size % UBI_LAYOUT_VOLUME_ALIGN; vi.usable_leb_size = ui->leb_size - vi.data_pad; vi.data_pad = ui->leb_size - vi.usable_leb_size; vi.type = UBI_LAYOUT_VOLUME_TYPE; @@ -306,19 +304,27 @@ int ubigen_write_layout_vol(const struct ubigen_info *ui, memset(outbuf, 0xFF, ui->data_offs); vid_hdr = (struct ubi_vid_hdr *)(&outbuf[ui->vid_hdr_offs]); - init_ec_hdr(ui, (struct ubi_ec_hdr *)outbuf); - memcpy(outbuf + ui->data_offs, vtbl, size); - memset(outbuf + ui->data_offs + size, 0xFF, - ui->peb_size - ui->data_offs - size); - + memcpy(outbuf + ui->data_offs, vtbl, ui->vtbl_size); + memset(outbuf + ui->data_offs + ui->vtbl_size, 0xFF, + ui->peb_size - ui->data_offs - ui->vtbl_size); + + seek = peb1 * ui->peb_size; + if (lseek(fd, seek, SEEK_SET) != seek) + return sys_errmsg("cannot seek output file"); + ubigen_init_ec_hdr(ui, (struct ubi_ec_hdr *)outbuf, ec1); init_vid_hdr(ui, &vi, vid_hdr, 0, NULL, 0); - size = fwrite(outbuf, 1, ui->peb_size, out); - if (size == ui->peb_size) { - init_vid_hdr(ui, &vi, vid_hdr, 1, NULL, 0); - size = fwrite(outbuf, 1, ui->peb_size, out); - if (size != ui->peb_size) - return sys_errmsg("cannot write %d bytes", ui->peb_size); - } + ret = write(fd, outbuf, ui->peb_size); + if (ret != ui->peb_size) + return sys_errmsg("cannot write %d bytes", ui->peb_size); + + seek = peb2 * ui->peb_size; + if (lseek(fd, seek, SEEK_SET) != seek) + return sys_errmsg("cannot seek output file"); + ubigen_init_ec_hdr(ui, (struct ubi_ec_hdr *)outbuf, ec2); + init_vid_hdr(ui, &vi, vid_hdr, 1, NULL, 0); + ret = write(fd, outbuf, ui->peb_size); + if (ret != ui->peb_size) + return sys_errmsg("cannot write %d bytes", ui->peb_size); return 0; } diff --git a/ubi-utils/new-utils/src/ubiattach.c b/ubi-utils/new-utils/src/ubiattach.c index 04a2f9c..3e19eca 100644 --- a/ubi-utils/new-utils/src/ubiattach.c +++ b/ubi-utils/new-utils/src/ubiattach.c @@ -84,7 +84,7 @@ static int parse_opt(int argc, char * const argv[]) int key; char *endp; - key = getopt_long(argc, argv, "m:d:OhV", long_options, NULL); + key = getopt_long(argc, argv, "m:d:O:hV", long_options, NULL); if (key == -1) break; @@ -103,7 +103,7 @@ static int parse_opt(int argc, char * const argv[]) break; - case 'o': + case 'O': args.vidoffs = strtoul(optarg, &endp, 0); if (*endp != '\0' || endp == optarg || args.vidoffs <= 0) return errmsg("bad VID header offset: \"%s\"", optarg); diff --git a/ubi-utils/new-utils/src/ubinize.c b/ubi-utils/new-utils/src/ubinize.c index bd1b07b..dab224b 100644 --- a/ubi-utils/new-utils/src/ubinize.c +++ b/ubi-utils/new-utils/src/ubinize.c @@ -24,12 +24,14 @@ * Oliver Lohmann */ +#include +#include +#include #include #include -#include #include #include -#include +#include #include #include @@ -49,7 +51,7 @@ static const char *doc = PROGRAM_NAME " version " PROGRAM_VERSION "parameters, do not specify them and let the utility to use default values."; static const char *optionsstr = -"-o, --output= output file name (default is stdout)\n" +"-o, --output= output file name\n" "-p, --peb-size= size of the physical eraseblock of the flash\n" " this UBI image is created for in bytes,\n" " kilobytes (KiB), or megabytes (MiB)\n" @@ -61,14 +63,14 @@ static const char *optionsstr = " flash (equivalent to the minimum input/output\n" " unit size by default)\n" "-O, --vid-hdr-offset= offset if the VID header from start of the\n" -" physical eraseblock (default is the second\n" -" minimum I/O unit or sub-page, if it was\n" -" specified)\n" +" physical eraseblock (default is the next\n" +" minimum I/O unit or sub-page after the EC\n" +" header)\n" "-e, --erase-counter= the erase counter value to put to EC headers\n" " (default is 0)\n" "-x, --ubi-ver= UBI version number to put to EC headers\n" " (default is 1)\n" -"-v --verbose be verbose\n" +"-v, --verbose be verbose\n" "-h, --help print help message\n" "-V, --version print program version"; @@ -128,7 +130,7 @@ struct option long_options[] = { struct args { const char *f_in; const char *f_out; - FILE *fp_out; + int out_fd; int peb_size; int min_io_size; int subpage_size; @@ -140,14 +142,10 @@ struct args { }; static struct args args = { - .f_out = NULL, .peb_size = -1, .min_io_size = -1, .subpage_size = -1, - .vid_hdr_offs = 0, - .ec = 0, .ubi_ver = 1, - .verbose = 0, }; static int parse_opt(int argc, char * const argv[]) @@ -162,9 +160,10 @@ static int parse_opt(int argc, char * const argv[]) switch (key) { case 'o': - args.fp_out = fopen(optarg, "wb"); - if (!args.fp_out) - return errmsg("cannot open file \"%s\"", optarg); + args.out_fd = open(optarg, O_CREAT | O_TRUNC | O_WRONLY, + S_IWUSR | S_IRUSR | S_IRGRP | S_IWGRP | S_IROTH); + if (args.out_fd == -1) + return sys_errmsg("cannot open file \"%s\"", optarg); args.f_out = optarg; break; @@ -242,16 +241,14 @@ static int parse_opt(int argc, char * const argv[]) if (args.subpage_size < 0) args.subpage_size = args.min_io_size; - if (!args.f_out) { - args.f_out = "stdout"; - args.fp_out = stdout; - } + if (!args.f_out) + return errmsg("output file was not specified (use -h for help)"); return 0; } -int read_section(const char *sname, struct ubigen_vol_info *vi, - const char **img) +static int read_section(const char *sname, struct ubigen_vol_info *vi, + const char **img) { char buf[256]; const char *p; @@ -401,6 +398,7 @@ int main(int argc, char * const argv[]) int err = -1, sects, i, volumes; struct ubigen_info ui; struct ubi_vtbl_record *vtbl; + off_t seek; err = parse_opt(argc, argv); if (err) @@ -408,7 +406,7 @@ int main(int argc, char * const argv[]) ubigen_info_init(&ui, args.peb_size, args.min_io_size, args.subpage_size, args.vid_hdr_offs, - args.ubi_ver, args.ec); + args.ubi_ver); verbose(args.verbose, "LEB size: %d", ui.leb_size); verbose(args.verbose, "PEB size: %d", ui.peb_size); @@ -444,8 +442,9 @@ int main(int argc, char * const argv[]) * Skip 2 PEBs at the beginning of the file for the volume table which * will be written later. */ - if (fseek(args.fp_out, ui.peb_size * 2, SEEK_SET) == -1) { - errmsg("cannot seek file \"%s\"", args.f_out); + seek = ui.peb_size * 2; + if (lseek(args.out_fd, seek, SEEK_SET) != seek) { + sys_errmsg("cannot seek file \"%s\"", args.f_out); goto out_dict; } @@ -454,7 +453,7 @@ int main(int argc, char * const argv[]) struct ubigen_vol_info vi; const char *img = NULL; struct stat st; - FILE *f; + int fd; if (!sname) { errmsg("ini-file parsing error (iniparser_getsecname)"); @@ -472,6 +471,10 @@ int main(int argc, char * const argv[]) volumes += 1; init_vol_info(&ui, &vi); + if (vi.id >= ui.max_volumes) + return errmsg("too high volume ID %d, max. is %d", + vi.id, ui.max_volumes); + verbose(args.verbose, "adding volume %d", vi.id); err = ubigen_add_volume(&ui, &vi, vtbl); @@ -498,8 +501,8 @@ int main(int argc, char * const argv[]) goto out_dict; } - f = fopen(img, "r"); - if (!f) { + fd = open(img, O_RDONLY); + if (fd == -1) { sys_errmsg("cannot open \"%s\"", img); goto out_dict; } @@ -507,8 +510,8 @@ int main(int argc, char * const argv[]) verbose(args.verbose, "writing volume %d", vi.id); verbose(args.verbose, "image file: %s", img); - err = ubigen_write_volume(&ui, &vi, st.st_size, f, args.fp_out); - fclose(f); + err = ubigen_write_volume(&ui, &vi, args.ec, st.st_size, fd, args.out_fd); + close(fd); if (err) { errmsg("cannot write volume for section \"%s\"", sname); goto out_dict; @@ -520,12 +523,7 @@ int main(int argc, char * const argv[]) verbose(args.verbose, "writing layout volume"); - if (fseek(args.fp_out, 0, SEEK_SET) == -1) { - errmsg("cannot seek file \"%s\"", args.f_out); - goto out_dict; - } - - err = ubigen_write_layout_vol(&ui, vtbl, args.fp_out); + err = ubigen_write_layout_vol(&ui, 0, 1, args.ec, args.ec, vtbl, args.out_fd); if (err) { errmsg("cannot write layout volume"); goto out_dict; @@ -535,7 +533,7 @@ int main(int argc, char * const argv[]) iniparser_freedict(args.dict); free(vtbl); - fclose(args.fp_out); + close(args.out_fd); return 0; out_dict: @@ -543,7 +541,7 @@ out_dict: out_vtbl: free(vtbl); out: - fclose(args.fp_out); + close(args.out_fd); remove(args.f_out); return err; } -- cgit v1.2.3