/* * Copyright (C) 2002-2004 Sistina Software, Inc. All rights reserved. * Copyright (C) 2004 Red Hat, Inc. All rights reserved. * * This file is part of LVM2. * * This copyrighted material is made available to anyone wishing to use, * modify, copy, or redistribute it subject to the terms and conditions * of the GNU General Public License v.2. * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "lib.h" #include "metadata.h" #include "report.h" #include "toolcontext.h" #include "lvm-string.h" #include "display.h" #include "activate.h" #include "segtype.h" /* * For macro use */ static union { struct physical_volume _pv; struct logical_volume _lv; struct volume_group _vg; struct lv_segment _seg; struct pv_segment _pvseg; } _dummy; /* * Report handle flags */ #define RH_SORT_REQUIRED 0x00000001 #define RH_HEADINGS_PRINTED 0x00000002 #define RH_BUFFERED 0x00000004 #define RH_ALIGNED 0x00000008 #define RH_HEADINGS 0x00000010 struct report_handle { struct cmd_context *cmd; struct dm_pool *mem; report_type_t type; const char *field_prefix; uint32_t flags; const char *separator; uint32_t keys_count; /* Ordered list of fields needed for this report */ struct list field_props; /* Rows of report data */ struct list rows; }; /* * Per-field flags */ #define FLD_ALIGN_LEFT 0x00000001 #define FLD_ALIGN_RIGHT 0x00000002 #define FLD_STRING 0x00000004 #define FLD_NUMBER 0x00000008 #define FLD_HIDDEN 0x00000010 #define FLD_SORT_KEY 0x00000020 #define FLD_ASCENDING 0x00000040 #define FLD_DESCENDING 0x00000080 struct field_properties { struct list list; uint32_t field_num; uint32_t sort_posn; int width; uint32_t flags; }; /* * Report data */ struct field { struct list list; struct field_properties *props; const char *report_string; /* Formatted ready for display */ const void *sort_value; /* Raw value for sorting */ }; struct row { struct list list; struct report_handle *rh; struct list fields; /* Fields in display order */ struct field *(*sort_fields)[]; /* Fields in sort order */ }; static char _alloc_policy_char(alloc_policy_t alloc) { switch (alloc) { case ALLOC_CONTIGUOUS: return 'c'; case ALLOC_NORMAL: return 'n'; case ALLOC_ANYWHERE: return 'a'; default: return 'i'; } } /* * Data-munging functions to prepare each data type for display and sorting */ static int _string_disp(struct report_handle *rh, struct field *field, const void *data) { if (! (field->report_string = dm_pool_strdup(rh->mem, *(const char **) data))) { log_error("dm_pool_strdup failed"); return 0; } field->sort_value = (const void *) field->report_string; return 1; } static int _dev_name_disp(struct report_handle *rh, struct field *field, const void *data) { const char *name = dev_name(*(const struct device **) data); return _string_disp(rh, field, &name); } static int _devices_disp(struct report_handle *rh, struct field *field, const void *data) { const struct lv_segment *seg = (const struct lv_segment *) data; unsigned int s; const char *name = NULL; uint32_t extent = 0; char extent_str[32]; if (!dm_pool_begin_object(rh->mem, 256)) { log_error("dm_pool_begin_object failed"); return 0; } for (s = 0; s < seg->area_count; s++) { switch (seg_type(seg, s)) { case AREA_LV: name = seg_lv(seg, s)->name; extent = seg_le(seg, s); break; case AREA_PV: name = dev_name(seg_dev(seg, s)); extent = seg_pe(seg, s); break; case AREA_UNASSIGNED: name = "unassigned"; extent = 0; } if (!dm_pool_grow_object(rh->mem, name, strlen(name))) { log_error("dm_pool_grow_object failed"); return 0; } if (lvm_snprintf(extent_str, sizeof(extent_str), "(%" PRIu32 ")", extent) < 0) { log_error("Extent number lvm_snprintf failed"); return 0; } if (!dm_pool_grow_object(rh->mem, extent_str, strlen(extent_str))) { log_error("dm_pool_grow_object failed"); return 0; } if ((s != seg->area_count - 1) && !dm_pool_grow_object(rh->mem, ",", 1)) { log_error("dm_pool_grow_object failed"); return 0; } } if (!dm_pool_grow_object(rh->mem, "\0", 1)) { log_error("dm_pool_grow_object failed"); return 0; } field->report_string = dm_pool_end_object(rh->mem); field->sort_value = (const void *) field->report_string; return 1; } static int _tags_disp(struct report_handle *rh, struct field *field, const void *data) { const struct list *tags = (const struct list *) data; struct str_list *sl; if (!dm_pool_begin_object(rh->mem, 256)) { log_error("dm_pool_begin_object failed"); return 0; } list_iterate_items(sl, tags) { if (!dm_pool_grow_object(rh->mem, sl->str, strlen(sl->str)) || (sl->list.n != tags && !dm_pool_grow_object(rh->mem, ",", 1))) { log_error("dm_pool_grow_object failed"); return 0; } } if (!dm_pool_grow_object(rh->mem, "\0", 1)) { log_error("dm_pool_grow_object failed"); return 0; } field->report_string = dm_pool_end_object(rh->mem); field->sort_value = (const void *) field->report_string; return 1; } static int _vgfmt_disp(struct report_handle *rh, struct field *field, const void *data) { const struct volume_group *vg = (const struct volume_group *) data; if (!vg->fid) { field->report_string = ""; field->sort_value = (const void *) field->report_string; return 1; } return _string_disp(rh, field, &vg->fid->fmt->name); } static int _pvfmt_disp(struct report_handle *rh, struct field *field, const void *data) { const struct physical_volume *pv = (const struct physical_volume *) data; if (!pv->fmt) { field->report_string = ""; field->sort_value = (const void *) field->report_string; return 1; } return _string_disp(rh, field, &pv->fmt->name); } static int _int_disp(struct report_handle *rh, struct field *field, const void *data) { const int value = *(const int *) data; uint64_t *sortval; char *repstr; if (!(repstr = dm_pool_zalloc(rh->mem, 13))) { log_error("dm_pool_alloc failed"); return 0; } if (!(sortval = dm_pool_alloc(rh->mem, sizeof(int64_t)))) { log_error("dm_pool_alloc failed"); return 0; } if (lvm_snprintf(repstr, 12, "%d", value) < 0) { log_error("int too big: %d", value); return 0; } *sortval = (const uint64_t) value; field->sort_value = sortval; field->report_string = repstr; return 1; } static int _lvkmaj_disp(struct report_handle *rh, struct field *field, const void *data) { const struct logical_volume *lv = (const struct logical_volume *) data; struct lvinfo info; uint64_t minusone = UINT64_C(-1); if (lv_info(lv->vg->cmd, lv, &info, 0) && info.exists) return _int_disp(rh, field, &info.major); else return _int_disp(rh, field, &minusone); return 1; } static int _lvkmin_disp(struct report_handle *rh, struct field *field, const void *data) { const struct logical_volume *lv = (const struct logical_volume *) data; struct lvinfo info; uint64_t minusone = UINT64_C(-1); if (lv_info(lv->vg->cmd, lv, &info, 0) && info.exists) return _int_disp(rh, field, &info.minor); else return _int_disp(rh, field, &minusone); return 1; } static int _lvstatus_disp(struct report_handle *rh, struct field *field, const void *data) { const struct logical_volume *lv = (const struct logical_volume *) data; struct lvinfo info; char *repstr; float snap_percent; if (!(repstr = dm_pool_zalloc(rh->mem, 7))) { log_error("dm_pool_alloc failed"); return 0; } if (lv->status & PVMOVE) repstr[0] = 'p'; else if (lv->status & MIRRORED) { if (lv->status & MIRROR_NOTSYNCED) repstr[0] = 'M'; else repstr[0] = 'm'; }else if (lv->status & MIRROR_IMAGE) repstr[0] = 'i'; else if (lv->status & MIRROR_LOG) repstr[0] = 'l'; else if (lv->status & VIRTUAL) repstr[0] = 'v'; else if (lv_is_origin(lv)) repstr[0] = 'o'; else if (lv_is_cow(lv)) repstr[0] = 's'; else repstr[0] = '-'; if (lv->status & PVMOVE) repstr[1] = '-'; else if (lv->status & LVM_WRITE) repstr[1] = 'w'; else repstr[1] = 'r'; repstr[2] = _alloc_policy_char(lv->alloc); if (lv->status & LOCKED) repstr[2] = toupper(repstr[2]); if (lv->status & FIXED_MINOR) repstr[3] = 'm'; /* Fixed Minor */ else repstr[3] = '-'; if (lv_info(lv->vg->cmd, lv, &info, 1) && info.exists) { if (info.suspended) repstr[4] = 's'; /* Suspended */ else if (info.live_table) repstr[4] = 'a'; /* Active */ else if (info.inactive_table) repstr[4] = 'i'; /* Inactive with table */ else repstr[4] = 'd'; /* Inactive without table */ /* Snapshot dropped? */ if (info.live_table && lv_is_cow(lv) && (!lv_snapshot_percent(lv, &snap_percent) || snap_percent < 0 || snap_percent >= 100)) { repstr[0] = toupper(repstr[0]); if (info.suspended) repstr[4] = 'S'; /* Susp Inv snapshot */ else repstr[4] = 'I'; /* Invalid snapshot */ } if (info.open_count) repstr[5] = 'o'; /* Open */ else repstr[5] = '-'; } else { repstr[4] = '-'; repstr[5] = '-'; } field->report_string = repstr; field->sort_value = (const void *) field->report_string; return 1; } static int _pvstatus_disp(struct report_handle *rh, struct field *field, const void *data) { const uint32_t status = *(const uint32_t *) data; char *repstr; if (!(repstr = dm_pool_zalloc(rh->mem, 4))) { log_error("dm_pool_alloc failed"); return 0; } if (status & ALLOCATABLE_PV) repstr[0] = 'a'; else repstr[0] = '-'; if (status & EXPORTED_VG) repstr[1] = 'x'; else repstr[1] = '-'; field->report_string = repstr; field->sort_value = (const void *) field->report_string; return 1; } static int _vgstatus_disp(struct report_handle *rh, struct field *field, const void *data) { const struct volume_group *vg = (const struct volume_group *) data; char *repstr; if (!(repstr = dm_pool_zalloc(rh->mem, 7))) { log_error("dm_pool_alloc failed"); return 0; } if (vg->status & LVM_WRITE) repstr[0] = 'w'; else repstr[0] = 'r'; if (vg->status & RESIZEABLE_VG) repstr[1] = 'z'; else repstr[1] = '-'; if (vg->status & EXPORTED_VG) repstr[2] = 'x'; else repstr[2] = '-'; if (vg->status & PARTIAL_VG) repstr[3] = 'p'; else repstr[3] = '-'; repstr[4] = _alloc_policy_char(vg->alloc); if (vg->status & CLUSTERED) repstr[5] = 'c'; else repstr[5] = '-'; field->report_string = repstr; field->sort_value = (const void *) field->report_string; return 1; } static int _segtype_disp(struct report_handle *rh __attribute((unused)), struct field *field, const void *data) { const struct lv_segment *seg = (const struct lv_segment *) data; if (seg->area_count == 1) field->report_string = "linear"; else field->report_string = seg->segtype->ops->name(seg); field->sort_value = (const void *) field->report_string; return 1; } static int _origin_disp(struct report_handle *rh, struct field *field, const void *data) { const struct logical_volume *lv = (const struct logical_volume *) data; if (lv_is_cow(lv)) return _string_disp(rh, field, &origin_from_cow(lv)->name); field->report_string = ""; field->sort_value = (const void *) field->report_string; return 1; } static int _loglv_disp(struct report_handle *rh, struct field *field, const void *data) { const struct logical_volume *lv = (const struct logical_volume *) data; struct lv_segment *seg; list_iterate_items(seg, &lv->segments) { if (!seg_is_mirrored(seg) || !seg->log_lv) continue; return _string_disp(rh, field, &seg->log_lv->name); } field->report_string = ""; field->sort_value = (const void *) field->report_string; return 1; } static int _lvname_disp(struct report_handle *rh, struct field *field, const void *data) { const struct logical_volume *lv = (const struct logical_volume *) data; char *repstr; size_t len; if (lv_is_visible(lv)) { repstr = lv->name; return _string_disp(rh, field, &repstr); } len = strlen(lv->name) + 3; if (!(repstr = dm_pool_zalloc(rh->mem, len))) { log_error("dm_pool_alloc failed"); return 0; } if (lvm_snprintf(repstr, len, "[%s]", lv->name) < 0) { log_error("lvname snprintf failed"); return 0; } field->report_string = repstr; if (!(field->sort_value = dm_pool_strdup(rh->mem, lv->name))) { log_error("dm_pool_strdup failed"); return 0; } return 1; } static int _movepv_disp(struct report_handle *rh, struct field *field, const void *data) { const struct logical_volume *lv = (const struct logical_volume *) data; const char *name; struct lv_segment *seg; list_iterate_items(seg, &lv->segments) { if (!(seg->status & PVMOVE)) continue; name = dev_name(seg_dev(seg, 0)); return _string_disp(rh, field, &name); } field->report_string = ""; field->sort_value = (const void *) field->report_string; return 1; } static int _size32_disp(struct report_handle *rh, struct field *field, const void *data) { const uint32_t size = *(const uint32_t *) data; const char *disp; uint64_t *sortval; if (!*(disp = display_size_units(rh->cmd, (uint64_t) size))) { stack; return 0; } if (!(field->report_string = dm_pool_strdup(rh->mem, disp))) { log_error("dm_pool_strdup failed"); return 0; } if (!(sortval = dm_pool_alloc(rh->mem, sizeof(uint64_t)))) { log_error("dm_pool_alloc failed"); return 0; } *sortval = (const uint64_t) size; field->sort_value = (const void *) sortval; return 1; } static int _size64_disp(struct report_handle *rh, struct field *field, const void *data) { const uint64_t size = *(const uint64_t *) data; const char *disp; uint64_t *sortval; if (!*(disp = display_size_units(rh->cmd, size))) { stack; return 0; } if (!(field->report_string = dm_pool_strdup(rh->mem, disp))) { log_error("dm_pool_strdup failed"); return 0; } if (!(sortval = dm_pool_alloc(rh->mem, sizeof(uint64_t)))) { log_error("dm_pool_alloc failed"); return 0; } *sortval = size; field->sort_value = sortval; return 1; } static int _vgsize_disp(struct report_handle *rh, struct field *field, const void *data) { const struct volume_group *vg = (const struct volume_group *) data; uint64_t size; size = (uint64_t) vg->extent_count * vg->extent_size; return _size64_disp(rh, field, &size); } static int _segstart_disp(struct report_handle *rh, struct field *field, const void *data) { const struct lv_segment *seg = (const struct lv_segment *) data; uint64_t start; start = (uint64_t) seg->le * seg->lv->vg->extent_size; return _size64_disp(rh, field, &start); } static int _segsize_disp(struct report_handle *rh, struct field *field, const void *data) { const struct lv_segment *seg = (const struct lv_segment *) data; uint64_t size; size = (uint64_t) seg->len * seg->lv->vg->extent_size; return _size64_disp(rh, field, &size); } static int _chunksize_disp(struct report_handle *rh, struct field *field, const void *data) { const struct lv_segment *seg = (const struct lv_segment *) data; uint64_t size; if (lv_is_cow(seg->lv)) size = (uint64_t) find_cow(seg->lv)->chunk_size; else size = 0; return _size64_disp(rh, field, &size); } static int _pvused_disp(struct report_handle *rh, struct field *field, const void *data) { const struct physical_volume *pv = (const struct physical_volume *) data; uint64_t used; if (!pv->pe_count) used = 0LL; else used = (uint64_t) pv->pe_alloc_count * pv->pe_size; return _size64_disp(rh, field, &used); } static int _pvfree_disp(struct report_handle *rh, struct field *field, const void *data) { const struct physical_volume *pv = (const struct physical_volume *) data; uint64_t freespace; if (!pv->pe_count) freespace = pv->size; else freespace = (uint64_t) (pv->pe_count - pv->pe_alloc_count) * pv->pe_size; return _size64_disp(rh, field, &freespace); } static int _pvsize_disp(struct report_handle *rh, struct field *field, const void *data) { const struct physical_volume *pv = (const struct physical_volume *) data; uint64_t size; if (!pv->pe_count) size = pv->size; else size = (uint64_t) pv->pe_count * pv->pe_size; return _size64_disp(rh, field, &size); } static int _devsize_disp(struct report_handle *rh, struct field *field, const void *data) { const struct device *dev = *(const struct device **) data; uint64_t size; if (!dev_get_size(dev, &size)) size = 0; return _size64_disp(rh, field, &size); } static int _vgfree_disp(struct report_handle *rh, struct field *field, const void *data) { const struct volume_group *vg = (const struct volume_group *) data; uint64_t freespace; freespace = (uint64_t) vg->free_count * vg->extent_size; return _size64_disp(rh, field, &freespace); } static int _uuid_disp(struct report_handle *rh, struct field *field, const void *data) { char *repstr = NULL; if (!(repstr = dm_pool_alloc(rh->mem, 40))) { log_error("dm_pool_alloc failed"); return 0; } if (!id_write_format((const struct id *) data, repstr, 40)) { stack; return 0; } field->report_string = repstr; field->sort_value = (const void *) field->report_string; return 1; } static int _uint32_disp(struct report_handle *rh, struct field *field, const void *data) { const uint32_t value = *(const uint32_t *) data; uint64_t *sortval; char *repstr; if (!(repstr = dm_pool_zalloc(rh->mem, 12))) { log_error("dm_pool_alloc failed"); return 0; } if (!(sortval = dm_pool_alloc(rh->mem, sizeof(uint64_t)))) { log_error("dm_pool_alloc failed"); return 0; } if (lvm_snprintf(repstr, 11, "%u", value) < 0) { log_error("uint32 too big: %u", value); return 0; } *sortval = (const uint64_t) value; field->sort_value = sortval; field->report_string = repstr; return 1; } static int _int32_disp(struct report_handle *rh, struct field *field, const void *data) { const int32_t value = *(const int32_t *) data; uint64_t *sortval; char *repstr; if (!(repstr = dm_pool_zalloc(rh->mem, 13))) { log_error("dm_pool_alloc failed"); return 0; } if (!(sortval = dm_pool_alloc(rh->mem, sizeof(int64_t)))) { log_error("dm_pool_alloc failed"); return 0; } if (lvm_snprintf(repstr, 12, "%d", value) < 0) { log_error("int32 too big: %d", value); return 0; } *sortval = (const uint64_t) value; field->sort_value = sortval; field->report_string = repstr; return 1; } static int _lvsegcount_disp(struct report_handle *rh, struct field *field, const void *data) { const struct logical_volume *lv = (const struct logical_volume *) data; uint32_t count; count = list_size(&lv->segments); return _uint32_disp(rh, field, &count); } static int _snpercent_disp(struct report_handle *rh, struct field *field, const void *data) { const struct logical_volume *lv = (const struct logical_volume *) data; struct lvinfo info; float snap_percent; uint64_t *sortval; char *repstr; if (!(sortval = dm_pool_alloc(rh->mem, sizeof(uint64_t)))) { log_error("dm_pool_alloc failed"); return 0; } if (!lv_is_cow(lv) || (lv_info(lv->vg->cmd, lv, &info, 0) && !info.exists)) { field->report_string = ""; *sortval = UINT64_C(0); field->sort_value = sortval; return 1; } if (!lv_snapshot_percent(lv, &snap_percent) || snap_percent < 0) { field->report_string = "100.00"; *sortval = UINT64_C(100); field->sort_value = sortval; return 1; } if (!(repstr = dm_pool_zalloc(rh->mem, 8))) { log_error("dm_pool_alloc failed"); return 0; } if (lvm_snprintf(repstr, 7, "%.2f", snap_percent) < 0) { log_error("snapshot percentage too large"); return 0; } *sortval = snap_percent * UINT64_C(1000); field->sort_value = sortval; field->report_string = repstr; return 1; } static int _copypercent_disp(struct report_handle *rh, struct field *field, const void *data) { struct logical_volume *lv = (struct logical_volume *) data; float percent; uint64_t *sortval; char *repstr; if (!(sortval = dm_pool_alloc(rh->mem, sizeof(uint64_t)))) { log_error("dm_pool_alloc failed"); return 0; } if ((!(lv->status & PVMOVE) && !(lv->status & MIRRORED)) || !lv_mirror_percent(lv->vg->cmd, lv, 0, &percent, NULL)) { field->report_string = ""; *sortval = UINT64_C(0); field->sort_value = sortval; return 1; } percent = copy_percent(lv); if (!(repstr = dm_pool_zalloc(rh->mem, 8))) { log_error("dm_pool_alloc failed"); return 0; } if (lvm_snprintf(repstr, 7, "%.2f", percent) < 0) { log_error("copy percentage too large"); return 0; } *sortval = percent * UINT64_C(1000); field->sort_value = sortval; field->report_string = repstr; return 1; } /* * Import column definitions */ #define STR (FLD_STRING | FLD_ALIGN_LEFT) #define NUM (FLD_NUMBER | FLD_ALIGN_RIGHT) #define FIELD(type, strct, sorttype, head, field, width, func, id) {type, id, (off_t)((void *)&_dummy._ ## strct.field - (void *)&_dummy._ ## strct), head, width, sorttype, &_ ## func ## _disp}, static struct { report_type_t type; const char id[32]; off_t offset; const char heading[32]; int width; uint32_t flags; field_report_fn report_fn; } _fields[] = { #include "columns.h" }; #undef STR #undef NUM #undef FIELD const unsigned int _num_fields = sizeof(_fields) / sizeof(_fields[0]); /* * Initialise report handle */ static int _field_match(struct report_handle *rh, const char *field, size_t len) { uint32_t f, l; struct field_properties *fp; if (!len) return 0; for (f = 0; f < _num_fields; f++) { if ((!strncasecmp(_fields[f].id, field, len) && strlen(_fields[f].id) == len) || (l = strlen(rh->field_prefix), !strncasecmp(rh->field_prefix, _fields[f].id, l) && !strncasecmp(_fields[f].id + l, field, len) && strlen(_fields[f].id) == l + len)) { rh->type |= _fields[f].type; if (!(fp = dm_pool_zalloc(rh->mem, sizeof(*fp)))) { log_error("struct field_properties allocation " "failed"); return 0; } fp->field_num = f; fp->width = _fields[f].width; fp->flags = _fields[f].flags; /* Suppress snapshot percentage if not using driver */ if (!activation() && !strncmp(field, "snap_percent", len)) fp->flags |= FLD_HIDDEN; list_add(&rh->field_props, &fp->list); return 1; } } return 0; } static int _add_sort_key(struct report_handle *rh, uint32_t field_num, uint32_t flags) { struct field_properties *fp, *found = NULL; list_iterate_items(fp, &rh->field_props) { if (fp->field_num == field_num) { found = fp; break; } } if (!found) { /* Add as a non-display field */ if (!(found = dm_pool_zalloc(rh->mem, sizeof(*found)))) { log_error("struct field_properties allocation failed"); return 0; } rh->type |= _fields[field_num].type; found->field_num = field_num; found->width = _fields[field_num].width; found->flags = _fields[field_num].flags | FLD_HIDDEN; list_add(&rh->field_props, &found->list); } if (found->flags & FLD_SORT_KEY) { log_error("Ignoring duplicate sort field: %s", _fields[field_num].id); return 1; } found->flags |= FLD_SORT_KEY; found->sort_posn = rh->keys_count++; found->flags |= flags; return 1; } static int _key_match(struct report_handle *rh, const char *key, size_t len) { uint32_t f, l; uint32_t flags = 0; if (!len) return 0; if (*key == '+') { key++; len--; flags = FLD_ASCENDING; } else if (*key == '-') { key++; len--; flags = FLD_DESCENDING; } else flags = FLD_ASCENDING; if (!len) { log_error("Missing sort field name"); return 0; } for (f = 0; f < _num_fields; f++) { if ((!strncasecmp(_fields[f].id, key, len) && strlen(_fields[f].id) == len) || (l = strlen(rh->field_prefix), !strncasecmp(rh->field_prefix, _fields[f].id, l) && !strncasecmp(_fields[f].id + l, key, len) && strlen(_fields[f].id) == l + len)) { return _add_sort_key(rh, f, flags); } } return 0; } static int _parse_options(struct report_handle *rh, const char *format) { const char *ws; /* Word start */ const char *we = format; /* Word end */ while (*we) { /* Allow consecutive commas */ while (*we && *we == ',') we++; ws = we; while (*we && *we != ',') we++; if (!_field_match(rh, ws, (size_t) (we - ws))) { log_error("Unrecognised field: %.*s", (int) (we - ws), ws); return 0; } } return 1; } static int _parse_keys(struct report_handle *rh, const char *keys) { const char *ws; /* Word start */ const char *we = keys; /* Word end */ while (*we) { /* Allow consecutive commas */ while (*we && *we == ',') we++; ws = we; while (*we && *we != ',') we++; if (!_key_match(rh, ws, (size_t) (we - ws))) { log_error("Unrecognised field: %.*s", (int) (we - ws), ws); return 0; } } return 1; } void *report_init(struct cmd_context *cmd, const char *format, const char *keys, report_type_t *report_type, const char *separator, int aligned, int buffered, int headings) { struct report_handle *rh; if (!(rh = dm_pool_zalloc(cmd->mem, sizeof(*rh)))) { log_error("report_handle dm_pool_zalloc failed"); return 0; } rh->cmd = cmd; rh->type = *report_type; rh->separator = separator; if (aligned) rh->flags |= RH_ALIGNED; if (buffered) rh->flags |= RH_BUFFERED | RH_SORT_REQUIRED; if (headings) rh->flags |= RH_HEADINGS; list_init(&rh->field_props); list_init(&rh->rows); switch (rh->type) { case PVS: rh->field_prefix = "pv_"; break; case LVS: rh->field_prefix = "lv_"; break; case VGS: rh->field_prefix = "vg_"; break; case SEGS: rh->field_prefix = "seg_"; break; case PVSEGS: rh->field_prefix = "pvseg_"; break; default: rh->field_prefix = ""; } if (!(rh->mem = dm_pool_create("report", 10 * 1024))) { log_error("Allocation of memory pool for report failed"); return NULL; } /* Generate list of fields for output based on format string & flags */ if (!_parse_options(rh, format)) return NULL; if (!_parse_keys(rh, keys)) return NULL; /* Ensure options selected are compatible */ if (rh->type & SEGS) rh->type |= LVS; if (rh->type & PVSEGS) rh->type |= PVS; if ((rh->type & LVS) && (rh->type & PVS)) { log_error("Can't report LV and PV fields at the same time"); return NULL; } /* Change report type if fields specified makes this necessary */ if (rh->type & SEGS) *report_type = SEGS; else if (rh->type & LVS) *report_type = LVS; else if (rh->type & PVSEGS) *report_type = PVSEGS; else if (rh->type & PVS) *report_type = PVS; return rh; } void report_free(void *handle) { struct report_handle *rh = handle; dm_pool_destroy(rh->mem); return; } /* * Create a row of data for an object */ int report_object(void *handle, struct volume_group *vg, struct logical_volume *lv, struct physical_volume *pv, struct lv_segment *seg, struct pv_segment *pvseg) { struct report_handle *rh = handle; struct field_properties *fp; struct row *row; struct field *field; void *data = NULL; int skip; if (lv && pv) { log_error("report_object: One of *lv and *pv must be NULL!"); return 0; } if (!(row = dm_pool_zalloc(rh->mem, sizeof(*row)))) { log_error("struct row allocation failed"); return 0; } row->rh = rh; if ((rh->flags & RH_SORT_REQUIRED) && !(row->sort_fields = dm_pool_zalloc(rh->mem, sizeof(struct field *) * rh->keys_count))) { log_error("row sort value structure allocation failed"); return 0; } list_init(&row->fields); list_add(&rh->rows, &row->list); /* For each field to be displayed, call its report_fn */ list_iterate_items(fp, &rh->field_props) { skip = 0; if (!(field = dm_pool_zalloc(rh->mem, sizeof(*field)))) { log_error("struct field allocation failed"); return 0; } field->props = fp; switch (_fields[fp->field_num].type) { case LVS: data = (void *) lv + _fields[fp->field_num].offset; break; case VGS: if (!vg) { skip = 1; break; } data = (void *) vg + _fields[fp->field_num].offset; break; case PVS: data = (void *) pv + _fields[fp->field_num].offset; break; case SEGS: data = (void *) seg + _fields[fp->field_num].offset; break; case PVSEGS: data = (void *) pvseg + _fields[fp->field_num].offset; } if (skip) { field->report_string = ""; field->sort_value = (const void *) field->report_string; } else if (!_fields[fp->field_num].report_fn(rh, field, data)) { log_error("report function failed for field %s", _fields[fp->field_num].id); return 0; } if ((strlen(field->report_string) > field->props->width)) field->props->width = strlen(field->report_string); if ((rh->flags & RH_SORT_REQUIRED) && (field->props->flags & FLD_SORT_KEY)) { (*row->sort_fields)[field->props->sort_posn] = field; } list_add(&row->fields, &field->list); } if (!(rh->flags & RH_BUFFERED)) report_output(handle); return 1; } /* * Print row of headings */ static int _report_headings(void *handle) { struct report_handle *rh = handle; struct field_properties *fp; const char *heading; char buf[1024]; if (rh->flags & RH_HEADINGS_PRINTED) return 1; rh->flags |= RH_HEADINGS_PRINTED; if (!(rh->flags & RH_HEADINGS)) return 1; if (!dm_pool_begin_object(rh->mem, 128)) { log_error("dm_pool_begin_object failed for headings"); return 0; } /* First heading line */ list_iterate_items(fp, &rh->field_props) { if (fp->flags & FLD_HIDDEN) continue; heading = _fields[fp->field_num].heading; if (rh->flags & RH_ALIGNED) { if (lvm_snprintf(buf, sizeof(buf), "%-*.*s", fp->width, fp->width, heading) < 0) { log_error("snprintf heading failed"); dm_pool_end_object(rh->mem); return 0; } if (!dm_pool_grow_object(rh->mem, buf, fp->width)) goto bad; } else if (!dm_pool_grow_object(rh->mem, heading, strlen(heading))) goto bad; if (!list_end(&rh->field_props, &fp->list)) if (!dm_pool_grow_object(rh->mem, rh->separator, strlen(rh->separator))) goto bad; } if (!dm_pool_grow_object(rh->mem, "\0", 1)) { log_error("dm_pool_grow_object failed"); goto bad; } log_print("%s", (char *) dm_pool_end_object(rh->mem)); return 1; bad: log_error("Failed to generate report headings for printing"); return 0; } /* * Sort rows of data */ static int _row_compare(const void *a, const void *b) { const struct row *rowa = *(const struct row **) a; const struct row *rowb = *(const struct row **) b; const struct field *sfa, *sfb; int32_t cnt = -1; for (cnt = 0; cnt < rowa->rh->keys_count; cnt++) { sfa = (*rowa->sort_fields)[cnt]; sfb = (*rowb->sort_fields)[cnt]; if (sfa->props->flags & FLD_NUMBER) { const uint64_t numa = *(const uint64_t *) sfa->sort_value; const uint64_t numb = *(const uint64_t *) sfb->sort_value; if (numa == numb) continue; if (sfa->props->flags & FLD_ASCENDING) { return (numa > numb) ? 1 : -1; } else { /* FLD_DESCENDING */ return (numa < numb) ? 1 : -1; } } else { /* FLD_STRING */ const char *stra = (const char *) sfa->sort_value; const char *strb = (const char *) sfb->sort_value; int cmp = strcmp(stra, strb); if (!cmp) continue; if (sfa->props->flags & FLD_ASCENDING) { return (cmp > 0) ? 1 : -1; } else { /* FLD_DESCENDING */ return (cmp < 0) ? 1 : -1; } } } return 0; /* Identical */ } static int _sort_rows(struct report_handle *rh) { struct row *(*rows)[]; uint32_t count = 0; struct row *row; if (!(rows = dm_pool_alloc(rh->mem, sizeof(**rows) * list_size(&rh->rows)))) { log_error("sort array allocation failed"); return 0; } list_iterate_items(row, &rh->rows) (*rows)[count++] = row; qsort(rows, count, sizeof(**rows), _row_compare); list_init(&rh->rows); while (count--) list_add_h(&rh->rows, &(*rows)[count]->list); return 1; } /* * Produce report output */ int report_output(void *handle) { struct report_handle *rh = handle; struct list *fh, *rowh, *ftmp, *rtmp; struct row *row = NULL; struct field *field; const char *repstr; char buf[4096]; int width; if (list_empty(&rh->rows)) return 1; /* Sort rows */ if ((rh->flags & RH_SORT_REQUIRED)) _sort_rows(rh); /* If headings not printed yet, calculate field widths and print them */ if (!(rh->flags & RH_HEADINGS_PRINTED)) _report_headings(rh); /* Print and clear buffer */ list_iterate_safe(rowh, rtmp, &rh->rows) { if (!dm_pool_begin_object(rh->mem, 512)) { log_error("dm_pool_begin_object failed for row"); return 0; } row = list_item(rowh, struct row); list_iterate_safe(fh, ftmp, &row->fields) { field = list_item(fh, struct field); if (field->props->flags & FLD_HIDDEN) continue; repstr = field->report_string; width = field->props->width; if (!(rh->flags & RH_ALIGNED)) { if (!dm_pool_grow_object(rh->mem, repstr, strlen(repstr))) goto bad; } else if (field->props->flags & FLD_ALIGN_LEFT) { if (lvm_snprintf(buf, sizeof(buf), "%-*.*s", width, width, repstr) < 0) { log_error("snprintf repstr failed"); dm_pool_end_object(rh->mem); return 0; } if (!dm_pool_grow_object(rh->mem, buf, width)) goto bad; } else if (field->props->flags & FLD_ALIGN_RIGHT) { if (lvm_snprintf(buf, sizeof(buf), "%*.*s", width, width, repstr) < 0) { log_error("snprintf repstr failed"); dm_pool_end_object(rh->mem); return 0; } if (!dm_pool_grow_object(rh->mem, buf, width)) goto bad; } if (!list_end(&row->fields, fh)) if (!dm_pool_grow_object(rh->mem, rh->separator, strlen(rh->separator))) goto bad; list_del(&field->list); } if (!dm_pool_grow_object(rh->mem, "\0", 1)) { log_error("dm_pool_grow_object failed for row"); return 0; } log_print("%s", (char *) dm_pool_end_object(rh->mem)); list_del(&row->list); } if (row) dm_pool_free(rh->mem, row); return 1; bad: log_error("Failed to generate row for printing"); return 0; }