| /* |
| * Copyright (c) 2007 Pawel Jakub Dawidek <pjd@FreeBSD.org> |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
| * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| * |
| * Links to Illumos.org for more information on kstat function: |
| * [1] https://illumos.org/man/1M/kstat |
| * [2] https://illumos.org/man/9f/kstat_create |
| */ |
| |
| #include <sys/cdefs.h> |
| __FBSDID("$FreeBSD$"); |
| |
| #include <sys/types.h> |
| #include <sys/param.h> |
| #include <sys/kernel.h> |
| #include <sys/systm.h> |
| #include <sys/malloc.h> |
| #include <sys/sysctl.h> |
| #include <sys/kstat.h> |
| #include <sys/sbuf.h> |
| #include <sys/zone.h> |
| |
| static MALLOC_DEFINE(M_KSTAT, "kstat_data", "Kernel statistics"); |
| |
| SYSCTL_ROOT_NODE(OID_AUTO, kstat, CTLFLAG_RW, 0, "Kernel statistics"); |
| |
| void |
| __kstat_set_raw_ops(kstat_t *ksp, |
| int (*headers)(char *buf, size_t size), |
| int (*data)(char *buf, size_t size, void *data), |
| void *(*addr)(kstat_t *ksp, loff_t index)) |
| { |
| ksp->ks_raw_ops.headers = headers; |
| ksp->ks_raw_ops.data = data; |
| ksp->ks_raw_ops.addr = addr; |
| } |
| |
| void |
| __kstat_set_seq_raw_ops(kstat_t *ksp, |
| int (*headers)(struct seq_file *f), |
| int (*data)(char *buf, size_t size, void *data), |
| void *(*addr)(kstat_t *ksp, loff_t index)) |
| { |
| ksp->ks_raw_ops.seq_headers = headers; |
| ksp->ks_raw_ops.data = data; |
| ksp->ks_raw_ops.addr = addr; |
| } |
| |
| static int |
| kstat_default_update(kstat_t *ksp, int rw) |
| { |
| ASSERT3P(ksp, !=, NULL); |
| |
| if (rw == KSTAT_WRITE) |
| return (EACCES); |
| |
| return (0); |
| } |
| |
| static int |
| kstat_resize_raw(kstat_t *ksp) |
| { |
| if (ksp->ks_raw_bufsize == KSTAT_RAW_MAX) |
| return (ENOMEM); |
| |
| free(ksp->ks_raw_buf, M_TEMP); |
| ksp->ks_raw_bufsize = MIN(ksp->ks_raw_bufsize * 2, KSTAT_RAW_MAX); |
| ksp->ks_raw_buf = malloc(ksp->ks_raw_bufsize, M_TEMP, M_WAITOK); |
| |
| return (0); |
| } |
| |
| static void * |
| kstat_raw_default_addr(kstat_t *ksp, loff_t n) |
| { |
| if (n == 0) |
| return (ksp->ks_data); |
| return (NULL); |
| } |
| |
| static int |
| kstat_sysctl(SYSCTL_HANDLER_ARGS) |
| { |
| kstat_t *ksp = arg1; |
| kstat_named_t *ksent; |
| uint64_t val; |
| |
| ksent = ksp->ks_data; |
| /* Select the correct element */ |
| ksent += arg2; |
| /* Update the aggsums before reading */ |
| (void) ksp->ks_update(ksp, KSTAT_READ); |
| val = ksent->value.ui64; |
| |
| return (sysctl_handle_64(oidp, &val, 0, req)); |
| } |
| |
| static int |
| kstat_sysctl_string(SYSCTL_HANDLER_ARGS) |
| { |
| kstat_t *ksp = arg1; |
| kstat_named_t *ksent = ksp->ks_data; |
| char *val; |
| uint32_t len = 0; |
| |
| /* Select the correct element */ |
| ksent += arg2; |
| /* Update the aggsums before reading */ |
| (void) ksp->ks_update(ksp, KSTAT_READ); |
| val = KSTAT_NAMED_STR_PTR(ksent); |
| len = KSTAT_NAMED_STR_BUFLEN(ksent); |
| val[len-1] = '\0'; |
| |
| return (sysctl_handle_string(oidp, val, len, req)); |
| } |
| |
| static int |
| kstat_sysctl_dataset(SYSCTL_HANDLER_ARGS) |
| { |
| kstat_t *ksp = arg1; |
| kstat_named_t *ksent; |
| kstat_named_t *ksent_ds; |
| uint64_t val; |
| char *ds_name; |
| uint32_t ds_len = 0; |
| |
| ksent_ds = ksent = ksp->ks_data; |
| ds_name = KSTAT_NAMED_STR_PTR(ksent_ds); |
| ds_len = KSTAT_NAMED_STR_BUFLEN(ksent_ds); |
| ds_name[ds_len-1] = '\0'; |
| |
| if (!zone_dataset_visible(ds_name, NULL)) { |
| return (EPERM); |
| } |
| |
| /* Select the correct element */ |
| ksent += arg2; |
| /* Update the aggsums before reading */ |
| (void) ksp->ks_update(ksp, KSTAT_READ); |
| val = ksent->value.ui64; |
| |
| return (sysctl_handle_64(oidp, &val, 0, req)); |
| } |
| |
| static int |
| kstat_sysctl_dataset_string(SYSCTL_HANDLER_ARGS) |
| { |
| kstat_t *ksp = arg1; |
| kstat_named_t *ksent = ksp->ks_data; |
| char *val; |
| uint32_t len = 0; |
| |
| /* Select the correct element */ |
| ksent += arg2; |
| val = KSTAT_NAMED_STR_PTR(ksent); |
| len = KSTAT_NAMED_STR_BUFLEN(ksent); |
| val[len-1] = '\0'; |
| |
| if (!zone_dataset_visible(val, NULL)) { |
| return (EPERM); |
| } |
| |
| return (sysctl_handle_string(oidp, val, len, req)); |
| } |
| |
| static int |
| kstat_sysctl_io(SYSCTL_HANDLER_ARGS) |
| { |
| struct sbuf *sb; |
| kstat_t *ksp = arg1; |
| kstat_io_t *kip = ksp->ks_data; |
| int rc; |
| |
| sb = sbuf_new_auto(); |
| if (sb == NULL) |
| return (ENOMEM); |
| /* Update the aggsums before reading */ |
| (void) ksp->ks_update(ksp, KSTAT_READ); |
| |
| /* though wlentime & friends are signed, they will never be negative */ |
| sbuf_printf(sb, |
| "%-8llu %-8llu %-8u %-8u %-8llu %-8llu " |
| "%-8llu %-8llu %-8llu %-8llu %-8u %-8u\n", |
| kip->nread, kip->nwritten, |
| kip->reads, kip->writes, |
| kip->wtime, kip->wlentime, kip->wlastupdate, |
| kip->rtime, kip->rlentime, kip->rlastupdate, |
| kip->wcnt, kip->rcnt); |
| rc = sbuf_finish(sb); |
| if (rc == 0) |
| rc = SYSCTL_OUT(req, sbuf_data(sb), sbuf_len(sb)); |
| sbuf_delete(sb); |
| return (rc); |
| } |
| |
| static int |
| kstat_sysctl_raw(SYSCTL_HANDLER_ARGS) |
| { |
| struct sbuf *sb; |
| void *data; |
| kstat_t *ksp = arg1; |
| void *(*addr_op)(kstat_t *ksp, loff_t index); |
| int n, has_header, rc = 0; |
| |
| sb = sbuf_new_auto(); |
| if (sb == NULL) |
| return (ENOMEM); |
| |
| if (ksp->ks_raw_ops.addr) |
| addr_op = ksp->ks_raw_ops.addr; |
| else |
| addr_op = kstat_raw_default_addr; |
| |
| mutex_enter(ksp->ks_lock); |
| |
| /* Update the aggsums before reading */ |
| (void) ksp->ks_update(ksp, KSTAT_READ); |
| |
| ksp->ks_raw_bufsize = PAGE_SIZE; |
| ksp->ks_raw_buf = malloc(PAGE_SIZE, M_TEMP, M_WAITOK); |
| |
| n = 0; |
| has_header = (ksp->ks_raw_ops.headers || |
| ksp->ks_raw_ops.seq_headers); |
| |
| restart_headers: |
| if (ksp->ks_raw_ops.headers) { |
| rc = ksp->ks_raw_ops.headers( |
| ksp->ks_raw_buf, ksp->ks_raw_bufsize); |
| } else if (ksp->ks_raw_ops.seq_headers) { |
| struct seq_file f; |
| |
| f.sf_buf = ksp->ks_raw_buf; |
| f.sf_size = ksp->ks_raw_bufsize; |
| rc = ksp->ks_raw_ops.seq_headers(&f); |
| } |
| if (has_header) { |
| if (rc == ENOMEM && !kstat_resize_raw(ksp)) |
| goto restart_headers; |
| if (rc == 0) |
| sbuf_printf(sb, "\n%s", ksp->ks_raw_buf); |
| } |
| |
| while ((data = addr_op(ksp, n)) != NULL) { |
| restart: |
| if (ksp->ks_raw_ops.data) { |
| rc = ksp->ks_raw_ops.data(ksp->ks_raw_buf, |
| ksp->ks_raw_bufsize, data); |
| if (rc == ENOMEM && !kstat_resize_raw(ksp)) |
| goto restart; |
| if (rc == 0) |
| sbuf_printf(sb, "%s", ksp->ks_raw_buf); |
| |
| } else { |
| ASSERT3U(ksp->ks_ndata, ==, 1); |
| sbuf_hexdump(sb, ksp->ks_data, |
| ksp->ks_data_size, NULL, 0); |
| } |
| n++; |
| } |
| free(ksp->ks_raw_buf, M_TEMP); |
| mutex_exit(ksp->ks_lock); |
| sbuf_trim(sb); |
| rc = sbuf_finish(sb); |
| if (rc == 0) |
| rc = SYSCTL_OUT(req, sbuf_data(sb), sbuf_len(sb)); |
| sbuf_delete(sb); |
| return (rc); |
| } |
| |
| kstat_t * |
| __kstat_create(const char *module, int instance, const char *name, |
| const char *class, uchar_t ks_type, uint_t ks_ndata, uchar_t flags) |
| { |
| char buf[KSTAT_STRLEN]; |
| struct sysctl_oid *root; |
| kstat_t *ksp; |
| char *pool; |
| |
| KASSERT(instance == 0, ("instance=%d", instance)); |
| if ((ks_type == KSTAT_TYPE_INTR) || (ks_type == KSTAT_TYPE_IO)) |
| ASSERT3U(ks_ndata, ==, 1); |
| |
| if (class == NULL) |
| class = "misc"; |
| |
| /* |
| * Allocate the main structure. We don't need to keep a copy of |
| * module in here, because it is only used for sysctl node creation |
| * done in this function. |
| */ |
| ksp = malloc(sizeof (*ksp), M_KSTAT, M_WAITOK|M_ZERO); |
| |
| ksp->ks_crtime = gethrtime(); |
| ksp->ks_snaptime = ksp->ks_crtime; |
| ksp->ks_instance = instance; |
| (void) strlcpy(ksp->ks_name, name, KSTAT_STRLEN); |
| (void) strlcpy(ksp->ks_class, class, KSTAT_STRLEN); |
| ksp->ks_type = ks_type; |
| ksp->ks_flags = flags; |
| ksp->ks_update = kstat_default_update; |
| |
| mutex_init(&ksp->ks_private_lock, NULL, MUTEX_DEFAULT, NULL); |
| ksp->ks_lock = &ksp->ks_private_lock; |
| |
| switch (ksp->ks_type) { |
| case KSTAT_TYPE_RAW: |
| ksp->ks_ndata = 1; |
| ksp->ks_data_size = ks_ndata; |
| break; |
| case KSTAT_TYPE_NAMED: |
| ksp->ks_ndata = ks_ndata; |
| ksp->ks_data_size = ks_ndata * sizeof (kstat_named_t); |
| break; |
| case KSTAT_TYPE_INTR: |
| ksp->ks_ndata = ks_ndata; |
| ksp->ks_data_size = ks_ndata * sizeof (kstat_intr_t); |
| break; |
| case KSTAT_TYPE_IO: |
| ksp->ks_ndata = ks_ndata; |
| ksp->ks_data_size = ks_ndata * sizeof (kstat_io_t); |
| break; |
| case KSTAT_TYPE_TIMER: |
| ksp->ks_ndata = ks_ndata; |
| ksp->ks_data_size = ks_ndata * sizeof (kstat_timer_t); |
| break; |
| default: |
| panic("Undefined kstat type %d\n", ksp->ks_type); |
| } |
| |
| if (ksp->ks_flags & KSTAT_FLAG_VIRTUAL) |
| ksp->ks_data = NULL; |
| else |
| ksp->ks_data = kmem_zalloc(ksp->ks_data_size, KM_SLEEP); |
| |
| /* |
| * Some kstats use a module name like "zfs/poolname" to distinguish a |
| * set of kstats belonging to a specific pool. Split on '/' to add an |
| * extra node for the pool name if needed. |
| */ |
| (void) strlcpy(buf, module, KSTAT_STRLEN); |
| module = buf; |
| pool = strchr(module, '/'); |
| if (pool != NULL) |
| *pool++ = '\0'; |
| |
| /* |
| * Create sysctl tree for those statistics: |
| * |
| * kstat.<module>[.<pool>].<class>.<name> |
| */ |
| sysctl_ctx_init(&ksp->ks_sysctl_ctx); |
| root = SYSCTL_ADD_NODE(&ksp->ks_sysctl_ctx, |
| SYSCTL_STATIC_CHILDREN(_kstat), OID_AUTO, module, CTLFLAG_RW, 0, |
| ""); |
| if (root == NULL) { |
| printf("%s: Cannot create kstat.%s tree!\n", __func__, module); |
| sysctl_ctx_free(&ksp->ks_sysctl_ctx); |
| free(ksp, M_KSTAT); |
| return (NULL); |
| } |
| if (pool != NULL) { |
| root = SYSCTL_ADD_NODE(&ksp->ks_sysctl_ctx, |
| SYSCTL_CHILDREN(root), OID_AUTO, pool, CTLFLAG_RW, 0, ""); |
| if (root == NULL) { |
| printf("%s: Cannot create kstat.%s.%s tree!\n", |
| __func__, module, pool); |
| sysctl_ctx_free(&ksp->ks_sysctl_ctx); |
| free(ksp, M_KSTAT); |
| return (NULL); |
| } |
| } |
| root = SYSCTL_ADD_NODE(&ksp->ks_sysctl_ctx, SYSCTL_CHILDREN(root), |
| OID_AUTO, class, CTLFLAG_RW, 0, ""); |
| if (root == NULL) { |
| if (pool != NULL) |
| printf("%s: Cannot create kstat.%s.%s.%s tree!\n", |
| __func__, module, pool, class); |
| else |
| printf("%s: Cannot create kstat.%s.%s tree!\n", |
| __func__, module, class); |
| sysctl_ctx_free(&ksp->ks_sysctl_ctx); |
| free(ksp, M_KSTAT); |
| return (NULL); |
| } |
| if (ksp->ks_type == KSTAT_TYPE_NAMED) { |
| root = SYSCTL_ADD_NODE(&ksp->ks_sysctl_ctx, |
| SYSCTL_CHILDREN(root), |
| OID_AUTO, name, CTLFLAG_RW, 0, ""); |
| if (root == NULL) { |
| if (pool != NULL) |
| printf("%s: Cannot create kstat.%s.%s.%s.%s " |
| "tree!\n", __func__, module, pool, class, |
| name); |
| else |
| printf("%s: Cannot create kstat.%s.%s.%s " |
| "tree!\n", __func__, module, class, name); |
| sysctl_ctx_free(&ksp->ks_sysctl_ctx); |
| free(ksp, M_KSTAT); |
| return (NULL); |
| } |
| |
| } |
| ksp->ks_sysctl_root = root; |
| |
| return (ksp); |
| } |
| |
| static void |
| kstat_install_named(kstat_t *ksp) |
| { |
| kstat_named_t *ksent; |
| char *namelast; |
| int typelast; |
| |
| ksent = ksp->ks_data; |
| |
| VERIFY((ksp->ks_flags & KSTAT_FLAG_VIRTUAL) || ksent != NULL); |
| |
| typelast = 0; |
| namelast = NULL; |
| |
| for (int i = 0; i < ksp->ks_ndata; i++, ksent++) { |
| if (ksent->data_type != 0) { |
| typelast = ksent->data_type; |
| namelast = ksent->name; |
| } |
| switch (typelast) { |
| case KSTAT_DATA_CHAR: |
| /* Not Implemented */ |
| break; |
| case KSTAT_DATA_INT32: |
| SYSCTL_ADD_PROC(&ksp->ks_sysctl_ctx, |
| SYSCTL_CHILDREN(ksp->ks_sysctl_root), |
| OID_AUTO, namelast, |
| CTLTYPE_S32 | CTLFLAG_RD | CTLFLAG_MPSAFE, |
| ksp, i, kstat_sysctl, "I", namelast); |
| break; |
| case KSTAT_DATA_UINT32: |
| SYSCTL_ADD_PROC(&ksp->ks_sysctl_ctx, |
| SYSCTL_CHILDREN(ksp->ks_sysctl_root), |
| OID_AUTO, namelast, |
| CTLTYPE_U32 | CTLFLAG_RD | CTLFLAG_MPSAFE, |
| ksp, i, kstat_sysctl, "IU", namelast); |
| break; |
| case KSTAT_DATA_INT64: |
| SYSCTL_ADD_PROC(&ksp->ks_sysctl_ctx, |
| SYSCTL_CHILDREN(ksp->ks_sysctl_root), |
| OID_AUTO, namelast, |
| CTLTYPE_S64 | CTLFLAG_RD | CTLFLAG_MPSAFE, |
| ksp, i, kstat_sysctl, "Q", namelast); |
| break; |
| case KSTAT_DATA_UINT64: |
| if (strcmp(ksp->ks_class, "dataset") == 0) { |
| SYSCTL_ADD_PROC(&ksp->ks_sysctl_ctx, |
| SYSCTL_CHILDREN(ksp->ks_sysctl_root), |
| OID_AUTO, namelast, |
| CTLTYPE_U64 | CTLFLAG_RD | CTLFLAG_MPSAFE, |
| ksp, i, kstat_sysctl_dataset, "QU", |
| namelast); |
| } else { |
| SYSCTL_ADD_PROC(&ksp->ks_sysctl_ctx, |
| SYSCTL_CHILDREN(ksp->ks_sysctl_root), |
| OID_AUTO, namelast, |
| CTLTYPE_U64 | CTLFLAG_RD | CTLFLAG_MPSAFE, |
| ksp, i, kstat_sysctl, "QU", namelast); |
| } |
| break; |
| case KSTAT_DATA_LONG: |
| SYSCTL_ADD_PROC(&ksp->ks_sysctl_ctx, |
| SYSCTL_CHILDREN(ksp->ks_sysctl_root), |
| OID_AUTO, namelast, |
| CTLTYPE_LONG | CTLFLAG_RD | CTLFLAG_MPSAFE, |
| ksp, i, kstat_sysctl, "L", namelast); |
| break; |
| case KSTAT_DATA_ULONG: |
| SYSCTL_ADD_PROC(&ksp->ks_sysctl_ctx, |
| SYSCTL_CHILDREN(ksp->ks_sysctl_root), |
| OID_AUTO, namelast, |
| CTLTYPE_ULONG | CTLFLAG_RD | CTLFLAG_MPSAFE, |
| ksp, i, kstat_sysctl, "LU", namelast); |
| break; |
| case KSTAT_DATA_STRING: |
| if (strcmp(ksp->ks_class, "dataset") == 0) { |
| SYSCTL_ADD_PROC(&ksp->ks_sysctl_ctx, |
| SYSCTL_CHILDREN(ksp->ks_sysctl_root), |
| OID_AUTO, namelast, CTLTYPE_STRING | |
| CTLFLAG_RD | CTLFLAG_MPSAFE, |
| ksp, i, kstat_sysctl_dataset_string, "A", |
| namelast); |
| } else { |
| SYSCTL_ADD_PROC(&ksp->ks_sysctl_ctx, |
| SYSCTL_CHILDREN(ksp->ks_sysctl_root), |
| OID_AUTO, namelast, CTLTYPE_STRING | |
| CTLFLAG_RD | CTLFLAG_MPSAFE, |
| ksp, i, kstat_sysctl_string, "A", |
| namelast); |
| } |
| break; |
| default: |
| panic("unsupported type: %d", typelast); |
| } |
| } |
| } |
| |
| void |
| kstat_install(kstat_t *ksp) |
| { |
| struct sysctl_oid *root; |
| |
| if (ksp->ks_ndata == UINT32_MAX) |
| VERIFY3U(ksp->ks_type, ==, KSTAT_TYPE_RAW); |
| |
| switch (ksp->ks_type) { |
| case KSTAT_TYPE_NAMED: |
| return (kstat_install_named(ksp)); |
| case KSTAT_TYPE_RAW: |
| if (ksp->ks_raw_ops.data) { |
| root = SYSCTL_ADD_PROC(&ksp->ks_sysctl_ctx, |
| SYSCTL_CHILDREN(ksp->ks_sysctl_root), |
| OID_AUTO, ksp->ks_name, CTLTYPE_STRING | CTLFLAG_RD |
| | CTLFLAG_MPSAFE | CTLFLAG_SKIP, |
| ksp, 0, kstat_sysctl_raw, "A", ksp->ks_name); |
| } else { |
| root = SYSCTL_ADD_PROC(&ksp->ks_sysctl_ctx, |
| SYSCTL_CHILDREN(ksp->ks_sysctl_root), |
| OID_AUTO, ksp->ks_name, CTLTYPE_OPAQUE | CTLFLAG_RD |
| | CTLFLAG_MPSAFE | CTLFLAG_SKIP, |
| ksp, 0, kstat_sysctl_raw, "", ksp->ks_name); |
| } |
| break; |
| case KSTAT_TYPE_IO: |
| root = SYSCTL_ADD_PROC(&ksp->ks_sysctl_ctx, |
| SYSCTL_CHILDREN(ksp->ks_sysctl_root), |
| OID_AUTO, ksp->ks_name, |
| CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, |
| ksp, 0, kstat_sysctl_io, "A", ksp->ks_name); |
| break; |
| case KSTAT_TYPE_TIMER: |
| case KSTAT_TYPE_INTR: |
| default: |
| panic("unsupported kstat type %d\n", ksp->ks_type); |
| } |
| VERIFY3P(root, !=, NULL); |
| ksp->ks_sysctl_root = root; |
| } |
| |
| void |
| kstat_delete(kstat_t *ksp) |
| { |
| |
| sysctl_ctx_free(&ksp->ks_sysctl_ctx); |
| ksp->ks_lock = NULL; |
| mutex_destroy(&ksp->ks_private_lock); |
| if (!(ksp->ks_flags & KSTAT_FLAG_VIRTUAL)) |
| kmem_free(ksp->ks_data, ksp->ks_data_size); |
| free(ksp, M_KSTAT); |
| } |