diff options
Diffstat (limited to 'ubi-utils/src/libubigen.c')
-rw-r--r-- | ubi-utils/src/libubigen.c | 665 |
1 files changed, 245 insertions, 420 deletions
diff --git a/ubi-utils/src/libubigen.c b/ubi-utils/src/libubigen.c index 6aef291..c3a0f9c 100644 --- a/ubi-utils/src/libubigen.c +++ b/ubi-utils/src/libubigen.c @@ -1,5 +1,6 @@ /* * Copyright (c) International Business Machines Corp., 2006 + * Copyright (C) 2008 Nokia Corporation * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -14,473 +15,297 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * Generating UBI images. * - * Author: Oliver Lohmann - * - * Add UBI headers to binary data. + * Authors: Oliver Lohmann + * Artem Bityutskiy */ #include <stdlib.h> #include <stdint.h> #include <stdio.h> -#include <errno.h> +#include <unistd.h> #include <string.h> -#include <mtd/ubi-header.h> -#include "config.h" -#include "ubigen.h" +#include <mtd/ubi-header.h> +#include <libubigen.h> #include "crc32.h" +#include "common.h" -#define UBI_NAME_SIZE 256 -#define DEFAULT_VID_OFFSET ((DEFAULT_PAGESIZE) - (UBI_VID_HDR_SIZE)) -#define MIN(a,b) ((a) < (b) ? (a) : (b)) - -static uint32_t crc32_table[256]; - -struct ubi_info { - struct ubi_vid_hdr* v; /* Volume ID header */ - struct ubi_ec_hdr* ec; /* Erase count header */ - - FILE* fp_in; /* Input Stream */ - FILE* fp_out; /* Output stream */ - - size_t peb_size; /* Physical EB size in bytes */ - size_t leb_size; /* Size of a logical EB in a physical EB */ - size_t leb_total; /* Total input size in logical EB */ - size_t alignment; /* Block alignment */ - size_t data_pad; /* Size of padding in each physical EB */ - - size_t bytes_total; /* Total input size in bytes */ - size_t bytes_read; /* Nymber of read bytes (total) */ - - uint32_t blks_written; /* Number of written logical EB */ - - uint8_t* buf; /* Allocated buffer */ - uint8_t* ptr_ec_hdr; /* Pointer to EC hdr in buf */ - uint8_t* ptr_vid_hdr; /* Pointer to VID hdr in buf */ - uint8_t* ptr_data; /* Pointer to data region in buf */ -}; - - -static uint32_t -byte_to_blk(uint64_t byte, uint32_t peb_size) -{ - return (byte % peb_size) == 0 - ? (byte / peb_size) - : (byte / peb_size) + 1; -} +#define PROGRAM_NAME "libubigen" -static int -validate_ubi_info(ubi_info_t u) -{ - if ((u->v->vol_type != UBI_VID_DYNAMIC) && - (u->v->vol_type != UBI_VID_STATIC)) { - return EUBIGEN_INVALID_TYPE; - } - - if (__be32_to_cpu(u->ec->vid_hdr_offset) < UBI_VID_HDR_SIZE) { - return EUBIGEN_INVALID_HDR_OFFSET; - } - - return 0; -} - -static int -skip_blks(ubi_info_t u, uint32_t blks) -{ - uint32_t i; - size_t read = 0, to_read = 0; - - /* Step to a maximum of leb_total - 1 to keep the - restrictions. */ - for (i = 0; i < MIN(blks, u->leb_total-1); i++) { - /* Read in data */ - to_read = MIN(u->leb_size, - (u->bytes_total - u->bytes_read)); - read = fread(u->ptr_data, 1, to_read, u->fp_in); - if (read != to_read) { - return -EIO; - } - u->bytes_read += read; - u->blks_written++; - } - - return 0; -} - -static void -clear_buf(ubi_info_t u) -{ - memset(u->buf, 0xff, u->peb_size); -} - -static void -write_ec_hdr(ubi_info_t u) +/** + * ubigen_create_empty_vtbl - creates empty volume table. + * @size: physical eraseblock size on input, size of the volume table on output + * + * This function creates an empty volume table and returns a pointer to it in + * case of success and %NULL in case of failure. The volume table size is + * returned in @size which has to contain PEB size on input. + */ +struct ubi_vtbl_record *ubigen_create_empty_vtbl(int *size) { - memcpy(u->ptr_ec_hdr, u->ec, UBI_EC_HDR_SIZE); -} + struct ubi_vtbl_record *vtbl; + int i; -static int -fill_data_buffer_from_file(ubi_info_t u, size_t* read) -{ - size_t to_read = 0; + if (*size > UBI_MAX_VOLUMES * UBI_VTBL_RECORD_SIZE) + *size = UBI_MAX_VOLUMES * UBI_VTBL_RECORD_SIZE; - if (u-> fp_in == NULL) - return -EIO; + vtbl = calloc(1, *size); + if (!vtbl) + return NULL; - to_read = MIN(u->leb_size, (u->bytes_total - u->bytes_read)); - *read = fread(u->ptr_data, 1, to_read, u->fp_in); - if (*read != to_read) { - return -EIO; + for (i = 0; i < UBI_MAX_VOLUMES; i++) { + uint32_t crc = crc32(UBI_CRC32_INIT, &vtbl[i], + UBI_VTBL_RECORD_SIZE_CRC); + vtbl[i].crc = __cpu_to_be32(crc); } - return 0; -} -static void -add_static_info(ubi_info_t u, size_t data_size, ubigen_action_t action) -{ - uint32_t crc = clc_crc32(crc32_table, UBI_CRC32_INIT, - u->ptr_data, data_size); - - u->v->data_size = __cpu_to_be32(data_size); - u->v->data_crc = __cpu_to_be32(crc); - - if (action & BROKEN_DATA_CRC) { - u->v->data_crc = - __cpu_to_be32(__be32_to_cpu(u->v->data_crc) + 1); - } - if (action & BROKEN_DATA_SIZE) { - u->v->data_size = - __cpu_to_be32(__be32_to_cpu(u->v->data_size) + 1); - } + return vtbl; } -static void -write_vid_hdr(ubi_info_t u, ubigen_action_t action) +/** + * ubigen_info_init - initialize libubigen. + * @ui: libubigen information + * @peb_size: flash physical eraseblock size + * @min_io_size: flash minimum input/output unit size + * @subpage_size: flash sub-page, if present (has to be equivalent to + * @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) { - uint32_t crc = clc_crc32(crc32_table, UBI_CRC32_INIT, - u->v, UBI_VID_HDR_SIZE_CRC); - /* Write VID header */ - u->v->hdr_crc = __cpu_to_be32(crc); - if (action & BROKEN_HDR_CRC) { - u->v->hdr_crc = __cpu_to_be32(__be32_to_cpu(u->v->hdr_crc) + 1); - } - memcpy(u->ptr_vid_hdr, u->v, UBI_VID_HDR_SIZE); + if (!vid_hdr_offs) + vid_hdr_offs = subpage_size; + + ui->peb_size = peb_size; + ui->min_io_size = min_io_size; + ui->vid_hdr_offs = vid_hdr_offs; + ui->data_offs = vid_hdr_offs + UBI_VID_HDR_SIZE + min_io_size - 1; + ui->data_offs /= 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; } -static int -write_to_output_stream(ubi_info_t u) +/** + * ubigen_add_volume - add a volume to the volume table. + * @vol_id: volume ID + * @bytes: volume size in bytes + * @alignment: volume alignment + * @vol_type: volume type (%UBI_DYNAMIC_VOLUME or %UBI_STATIC_VOLUME) + * @name: volume name + * @ui: libubigen information + * @vtbl: volume table to add to + * + * This function adds volume described by input parameters to the volume table + * @vtbl. + */ +void ubigen_add_volume(const struct ubigen_info *ui, + const struct ubigen_vol_info *vi, + struct ubi_vtbl_record *vtbl) { - size_t written; - - written = fwrite(u->buf, 1, u->peb_size, u->fp_out); - if (written != u->peb_size) { - return -EIO; - } - return 0; + struct ubi_vtbl_record *vtbl_rec = &vtbl[vi->id]; + uint32_t tmp; + + memset(vtbl_rec, '\0', sizeof(struct ubi_vtbl_record)); + tmp = (vi->bytes + ui->leb_size - 1) / ui->leb_size; + vtbl_rec->reserved_pebs = __cpu_to_be32(tmp); + vtbl_rec->alignment = __cpu_to_be32(vi->alignment); + vtbl_rec->vol_type = vi->type; + tmp = ui->leb_size % vi->alignment; + vtbl_rec->data_pad = __cpu_to_be32(tmp); + + memcpy(vtbl_rec->name, vi->name, vi->name_len); + vtbl_rec->name[vi->name_len] = '\0'; + vtbl_rec->name_len = __cpu_to_be16(vi->name_len); + + tmp = crc32(UBI_CRC32_INIT, vtbl_rec, UBI_VTBL_RECORD_SIZE_CRC); + vtbl_rec->crc = __cpu_to_be32(tmp); } -int -ubigen_write_leb(ubi_info_t u, ubigen_action_t action) +/** + * init_ec_hdr - initialize EC header. + * @ui: libubigen information + * @hdr: the EC header to initialize + */ +static void init_ec_hdr(const struct ubigen_info *ui, + struct ubi_ec_hdr *hdr) { - int rc = 0; - size_t read = 0; - - clear_buf(u); - write_ec_hdr(u); - - rc = fill_data_buffer_from_file(u, &read); - if (rc != 0) - return rc; - - if (u->v->vol_type == UBI_VID_STATIC) { - add_static_info(u, read, action); - } + uint32_t crc; - u->v->lnum = __cpu_to_be32(u->blks_written); + memset(hdr, '\0', sizeof(struct ubi_ec_hdr)); - if (action & MARK_AS_UPDATE) { - u->v->copy_flag = (u->v->copy_flag)++; - } + hdr->magic = __cpu_to_be32(UBI_EC_HDR_MAGIC); + hdr->version = ui->ubi_ver; + hdr->ec = __cpu_to_be64(ui->ec); + hdr->vid_hdr_offset = __cpu_to_be32(ui->vid_hdr_offs); - write_vid_hdr(u, action); - rc = write_to_output_stream(u); - if (rc != 0) - return rc; + hdr->data_offset = __cpu_to_be32(ui->data_offs); - /* Update current handle */ - u->bytes_read += read; - u->blks_written++; - return 0; + crc = crc32(UBI_CRC32_INIT, hdr, UBI_EC_HDR_SIZE_CRC); + hdr->hdr_crc = __cpu_to_be32(crc); } -int -ubigen_write_complete(ubi_info_t u) +/** + * init_vid_hdr - initialize VID header. + * @ui: libubigen information + * @vi: volume information + * @hdr: the VID header to initialize + * @lnum: logical eraseblock number + * @data: the contents of the LEB (static volumes only) + * @data_size: amount of data in this LEB (static volumes only) + * + * Note, @used_ebs, @data and @data_size are ignored in case of dynamic + * volumes. + */ +static void init_vid_hdr(const struct ubigen_info *ui, + const struct ubigen_vol_info *vi, + struct ubi_vid_hdr *hdr, int lnum, + const void *data, int data_size) { - size_t i; - int rc = 0; + uint32_t crc; - for (i = 0; i < u->leb_total; i++) { - rc = ubigen_write_leb(u, NO_ERROR); - if (rc != 0) - return rc; + memset(hdr, '\0', sizeof(struct ubi_vid_hdr)); + + hdr->magic = __cpu_to_be32(UBI_VID_HDR_MAGIC); + hdr->version = ui->ubi_ver; + hdr->vol_type = vi->type; + hdr->vol_id = __cpu_to_be32(vi->id); + hdr->lnum = __cpu_to_be32(lnum); + hdr->data_pad = __cpu_to_be32(vi->data_pad); + hdr->compat = vi->compat; + + if (vi->type == UBI_VID_STATIC) { + hdr->data_size = __cpu_to_be32(data_size); + hdr->used_ebs = __cpu_to_be32(vi->used_ebs); + crc = crc32(UBI_CRC32_INIT, data, data_size); + hdr->data_crc = __cpu_to_be32(crc); } - return 0; + crc = crc32(UBI_CRC32_INIT, hdr, UBI_VID_HDR_SIZE_CRC); + hdr->hdr_crc = __cpu_to_be32(crc); } -int -ubigen_write_broken_update(ubi_info_t u, uint32_t blk) +/** + * ubigen_write_volume - write UBI volume. + * @ui: libubigen information + * @vi: volume information + * @bytes: volume size in bytes + * @in: input file descriptor (has to be properly seeked) + * @out: output file descriptor + * + * This function reads the contents of the volume from the input file @in and + * writes the UBI volume to the output file @out. Returns zero on success and + * %-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) { - int rc = 0; - - rc = skip_blks(u, blk); - if (rc != 0) - return rc; - - rc = ubigen_write_leb(u, MARK_AS_UPDATE | BROKEN_DATA_CRC); - if (rc != 0) - return rc; - - - return 0; -} + int len = vi->usable_leb_size, rd, lnum = 0; + char inbuf[ui->leb_size], outbuf[ui->peb_size]; + + memset(outbuf, 0xFF, ui->data_offs); + init_ec_hdr(ui, (struct ubi_ec_hdr *)outbuf); + + while (bytes) { + int l; + struct ubi_vid_hdr *vid_hdr; + + if (bytes < len) + len = bytes; + bytes -= len; + + l = len; + do { + rd = fread(inbuf + len - l, 1, l, in); + if (rd == 0) { + if (ferror(in)) + errmsg("cannot read %d bytes from the input" + " file", l); + else + errmsg("not enough data in the input file"); + return -1; + } + + l -= rd; + } while (l); + + vid_hdr = (struct ubi_vid_hdr *)(&outbuf[ui->vid_hdr_offs]); + init_vid_hdr(ui, vi, vid_hdr, lnum, inbuf, len); + + memcpy(outbuf + ui->data_offs, inbuf, len); + 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) { + errmsg("cannot write %d bytes from the output" + " file", l); + return -1; + } -void -dump_info(ubi_info_t u ubi_unused) -{ -#ifdef DEBUG - int err = 0; - if (!u) { - fprintf(stderr, "<empty>"); - return; - } - if (!u->ec) { - fprintf(stderr, "<ec-empty>"); - err = 1; - } - if (!u->v) { - fprintf(stderr, "<v-empty>"); - err = 1; + lnum += 1; } - if (err) return; - - fprintf(stderr, "ubi volume\n"); - fprintf(stderr, "version : %8d\n", u->v->version); - fprintf(stderr, "vol_id : %8d\n", __be32_to_cpu(u->v->vol_id)); - fprintf(stderr, "vol_type : %8s\n", - u->v->vol_type == UBI_VID_STATIC ? - "static" : "dynamic"); - fprintf(stderr, "used_ebs : %8d\n", - __be32_to_cpu(u->v->used_ebs)); - fprintf(stderr, "peb_size : 0x%08x\n", u->peb_size); - fprintf(stderr, "leb_size : 0x%08x\n", u->leb_size); - fprintf(stderr, "data_pad : 0x%08x\n", - __be32_to_cpu(u->v->data_pad)); - fprintf(stderr, "leb_total : %8d\n", u->leb_total); - fprintf(stderr, "header offs : 0x%08x\n", - __be32_to_cpu(u->ec->vid_hdr_offset)); - fprintf(stderr, "bytes_total : %8d\n", u->bytes_total); - fprintf(stderr, " + in MiB : %8.2f M\n", - ((float)(u->bytes_total)) / 1024 / 1024); - fprintf(stderr, "-------------------------------\n\n"); -#else - return; -#endif -} -int -ubigen_destroy(ubi_info_t *u) -{ - if (u == NULL) - return -EINVAL; - - ubi_info_t tmp = *u; - - if (tmp) { - if (tmp->v) - free(tmp->v); - if (tmp->ec) - free(tmp->ec); - if (tmp->buf) - free(tmp->buf); - free(tmp); - } - *u = NULL; return 0; } -void -ubigen_init(void) -{ - init_crc32_table(crc32_table); -} - -int -ubigen_create(ubi_info_t* u, uint32_t vol_id, uint8_t vol_type, - uint32_t peb_size, uint64_t ec, uint32_t alignment, - uint8_t version, uint32_t vid_hdr_offset, uint8_t compat_flag, - size_t data_size, FILE* fp_in, FILE* fp_out) +/** + * ubigen_write_layout_vol - write UBI layout volume + * @ui: libubigen information + * @vtbl: volume table + * @out: output file stream + * + * 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 rc = 0; - ubi_info_t res = NULL; - uint32_t crc; - uint32_t data_offset; - - if (alignment == 0) { - rc = EUBIGEN_INVALID_ALIGNMENT; - goto ubigen_create_err; - } - if ((fp_in == NULL) || (fp_out == NULL)) { - rc = -EINVAL; - goto ubigen_create_err; - } - - res = (ubi_info_t) calloc(1, sizeof(struct ubi_info)); - if (res == NULL) { - rc = -ENOMEM; - goto ubigen_create_err; - } - - res->v = (struct ubi_vid_hdr*) calloc(1, sizeof(struct ubi_vid_hdr)); - if (res->v == NULL) { - rc = -ENOMEM; - goto ubigen_create_err; - } - - res->ec = (struct ubi_ec_hdr*) calloc(1, sizeof(struct ubi_ec_hdr)); - if (res->ec == NULL) { - rc = -ENOMEM; - goto ubigen_create_err; - } - - /* data which is needed in the general process */ - vid_hdr_offset = vid_hdr_offset ? vid_hdr_offset : DEFAULT_VID_OFFSET; - data_offset = vid_hdr_offset + UBI_VID_HDR_SIZE; - res->bytes_total = data_size; - res->peb_size = peb_size ? peb_size : DEFAULT_BLOCKSIZE; - res->data_pad = (res->peb_size - data_offset) % alignment; - res->leb_size = res->peb_size - data_offset - res->data_pad; - res->leb_total = byte_to_blk(data_size, res->leb_size); - res->alignment = alignment; - - if ((res->peb_size < (vid_hdr_offset + UBI_VID_HDR_SIZE))) { - rc = EUBIGEN_TOO_SMALL_EB; - goto ubigen_create_err; - } - res->fp_in = fp_in; - res->fp_out = fp_out; - - /* vid hdr data which doesn't change */ - res->v->magic = __cpu_to_be32(UBI_VID_HDR_MAGIC); - res->v->version = version ? version : UBI_VERSION; - res->v->vol_type = vol_type; - res->v->vol_id = __cpu_to_be32(vol_id); - res->v->compat = compat_flag; - res->v->data_pad = __cpu_to_be32(res->data_pad); - - /* static only: used_ebs */ - if (res->v->vol_type == UBI_VID_STATIC) { - res->v->used_ebs = __cpu_to_be32(byte_to_blk - (res->bytes_total, - res->leb_size)); - } - - /* ec hdr (fixed, doesn't change) */ - res->ec->magic = __cpu_to_be32(UBI_EC_HDR_MAGIC); - res->ec->version = version ? version : UBI_VERSION; - res->ec->ec = __cpu_to_be64(ec); - res->ec->vid_hdr_offset = __cpu_to_be32(vid_hdr_offset); - - res->ec->data_offset = __cpu_to_be32(data_offset); - - crc = clc_crc32(crc32_table, UBI_CRC32_INIT, res->ec, - UBI_EC_HDR_SIZE_CRC); - res->ec->hdr_crc = __cpu_to_be32(crc); - - /* prepare a read buffer */ - res->buf = (uint8_t*) malloc (res->peb_size * sizeof(uint8_t)); - if (res->buf == NULL) { - rc = -ENOMEM; - goto ubigen_create_err; - } - - /* point to distinct regions within the buffer */ - res->ptr_ec_hdr = res->buf; - res->ptr_vid_hdr = res->buf + __be32_to_cpu(res->ec->vid_hdr_offset); - res->ptr_data = res->buf + __be32_to_cpu(res->ec->vid_hdr_offset) - + UBI_VID_HDR_SIZE; - - rc = validate_ubi_info(res); - if (rc != 0) { - fprintf(stderr, "Volume validation failed: %d\n", rc); - goto ubigen_create_err; - } - - dump_info(res); - *u = res; - return rc; - -ubigen_create_err: - if (res) { - if (res->v) - free(res->v); - if (res->ec) - free(res->ec); - if (res->buf) - free(res->buf); - free(res); + int size = ui->leb_size; + 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; + + vi.bytes = ui->leb_size * UBI_LAYOUT_VOLUME_EBS; + vi.id = UBI_LAYOUT_VOL_ID; + vi.alignment = 1; + vi.data_pad = 0; + vi.usable_leb_size = ui->leb_size; + vi.type = UBI_VID_DYNAMIC; + vi.name = UBI_LAYOUT_VOLUME_NAME; + vi.name_len = strlen(UBI_LAYOUT_VOLUME_NAME); + vi.compat = UBI_LAYOUT_VOLUME_COMPAT; + + 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); + + 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) { + errmsg("cannot write %d bytes", ui->peb_size); + perror("write"); + return -1; + } } - *u = NULL; - return rc; -} - -int -ubigen_get_leb_size(ubi_info_t u, size_t* size) -{ - if (u == NULL) - return -EINVAL; - - *size = u->leb_size; - return 0; -} - - -int -ubigen_get_leb_total(ubi_info_t u, size_t* total) -{ - if (u == NULL) - return -EINVAL; - - *total = u->leb_total; - return 0; -} - -int -ubigen_set_lvol_rec(ubi_info_t u, size_t reserved_bytes, - const char* vol_name, struct ubi_vtbl_record *lvol_rec) -{ - uint32_t crc; - - if ((u == NULL) || (vol_name == NULL)) - return -EINVAL; - - memset(lvol_rec, 0x0, UBI_VTBL_RECORD_SIZE); - - lvol_rec->reserved_pebs = - __cpu_to_be32(byte_to_blk(reserved_bytes, u->leb_size)); - lvol_rec->alignment = __cpu_to_be32(u->alignment); - lvol_rec->data_pad = u->v->data_pad; - lvol_rec->vol_type = u->v->vol_type; - - lvol_rec->name_len = - __cpu_to_be16((uint16_t)strlen((const char*)vol_name)); - - memcpy(lvol_rec->name, vol_name, UBI_VOL_NAME_MAX + 1); - - crc = clc_crc32(crc32_table, UBI_CRC32_INIT, - lvol_rec, UBI_VTBL_RECORD_SIZE_CRC); - lvol_rec->crc = __cpu_to_be32(crc); return 0; } |