/* * kernel/lvm-fs.c * * Copyright (C) 2001 Sistina Software * * January-April 2001 * * LVM driver 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 2, or (at your option) * any later version. * * LVM driver 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 GNU CC; see the file COPYING. If not, write to * the Free Software Foundation, 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. * */ /* * Changelog * * 11/01/2001 - First version (Joe Thornber) * 21/03/2001 - added display of stripes and stripe size (HM) * 04/10/2001 - corrected devfs_register() call in lvm_init_fs() * 11/04/2001 - don't devfs_register("lvm") as user-space always does it * 10/05/2001 - show more of PV name in /proc/lvm/global * 16/12/2001 - fix devfs unregister order and prevent duplicate unreg (REG) * */ #include #include #include #include #include #include #include #include #include #include "lvm-internal.h" static int _proc_read_vg(char *page, char **start, off_t off, int count, int *eof, void *data); static int _proc_read_lv(char *page, char **start, off_t off, int count, int *eof, void *data); static int _proc_read_pv(char *page, char **start, off_t off, int count, int *eof, void *data); static int _proc_read_global(char *page, char **start, off_t off, int count, int *eof, void *data); static int _vg_info(vg_t *vg_ptr, char *buf); static int _lv_info(vg_t *vg_ptr, lv_t *lv_ptr, char *buf); static int _pv_info(pv_t *pv_ptr, char *buf); static void _show_uuid(const char *src, char *b, char *e); #if 0 static devfs_handle_t lvm_devfs_handle; #endif static devfs_handle_t vg_devfs_handle[MAX_VG]; static devfs_handle_t ch_devfs_handle[MAX_VG]; static devfs_handle_t lv_devfs_handle[MAX_LV]; static struct proc_dir_entry *lvm_proc_dir = NULL; static struct proc_dir_entry *lvm_proc_vg_subdir = NULL; /* inline functions */ /* public interface */ void __init lvm_init_fs() { struct proc_dir_entry *pde; /* User-space has already registered this */ #if 0 lvm_devfs_handle = devfs_register( 0 , "lvm", 0, LVM_CHAR_MAJOR, 0, S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP, &lvm_chr_fops, NULL); #endif lvm_proc_dir = create_proc_entry(LVM_DIR, S_IFDIR, &proc_root); if (lvm_proc_dir) { lvm_proc_vg_subdir = create_proc_entry(LVM_VG_SUBDIR, S_IFDIR, lvm_proc_dir); pde = create_proc_entry(LVM_GLOBAL, S_IFREG, lvm_proc_dir); if ( pde != NULL) pde->read_proc = _proc_read_global; } } void lvm_fin_fs() { #if 0 devfs_unregister (lvm_devfs_handle); #endif remove_proc_entry(LVM_GLOBAL, lvm_proc_dir); remove_proc_entry(LVM_VG_SUBDIR, lvm_proc_dir); remove_proc_entry(LVM_DIR, &proc_root); } void lvm_fs_create_vg(vg_t *vg_ptr) { struct proc_dir_entry *pde; vg_devfs_handle[vg_ptr->vg_number] = devfs_mk_dir(0, vg_ptr->vg_name, NULL); ch_devfs_handle[vg_ptr->vg_number] = devfs_register( vg_devfs_handle[vg_ptr->vg_number] , "group", DEVFS_FL_DEFAULT, LVM_CHAR_MAJOR, vg_ptr->vg_number, S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP, &lvm_chr_fops, NULL); vg_ptr->vg_dir_pde = create_proc_entry(vg_ptr->vg_name, S_IFDIR, lvm_proc_vg_subdir); if((pde = create_proc_entry("group", S_IFREG, vg_ptr->vg_dir_pde))) { pde->read_proc = _proc_read_vg; pde->data = vg_ptr; } vg_ptr->lv_subdir_pde = create_proc_entry(LVM_LV_SUBDIR, S_IFDIR, vg_ptr->vg_dir_pde); vg_ptr->pv_subdir_pde = create_proc_entry(LVM_PV_SUBDIR, S_IFDIR, vg_ptr->vg_dir_pde); } void lvm_fs_remove_vg(vg_t *vg_ptr) { int i; devfs_unregister(ch_devfs_handle[vg_ptr->vg_number]); ch_devfs_handle[vg_ptr->vg_number] = NULL; /* remove pv's */ for(i = 0; i < vg_ptr->pv_max; i++) if(vg_ptr->pv[i]) lvm_fs_remove_pv(vg_ptr, vg_ptr->pv[i]); /* remove lv's */ for(i = 0; i < vg_ptr->lv_max; i++) if(vg_ptr->lv[i]) lvm_fs_remove_lv(vg_ptr, vg_ptr->lv[i]); /* must not remove directory before leaf nodes */ devfs_unregister(vg_devfs_handle[vg_ptr->vg_number]); vg_devfs_handle[vg_ptr->vg_number] = NULL; if(vg_ptr->vg_dir_pde) { remove_proc_entry(LVM_LV_SUBDIR, vg_ptr->vg_dir_pde); vg_ptr->lv_subdir_pde = NULL; remove_proc_entry(LVM_PV_SUBDIR, vg_ptr->vg_dir_pde); vg_ptr->pv_subdir_pde = NULL; remove_proc_entry("group", vg_ptr->vg_dir_pde); vg_ptr->vg_dir_pde = NULL; remove_proc_entry(vg_ptr->vg_name, lvm_proc_vg_subdir); } } static inline const char *_basename(const char *str) { const char *name = strrchr(str, '/'); name = name ? name + 1 : str; return name; } devfs_handle_t lvm_fs_create_lv(vg_t *vg_ptr, lv_t *lv) { struct proc_dir_entry *pde; const char *name = _basename(lv->lv_name); lv_devfs_handle[MINOR(lv->lv_dev)] = devfs_register( vg_devfs_handle[vg_ptr->vg_number], name, DEVFS_FL_DEFAULT, LVM_BLK_MAJOR, MINOR(lv->lv_dev), S_IFBLK | S_IRUSR | S_IWUSR | S_IRGRP, &lvm_blk_dops, NULL); if(vg_ptr->lv_subdir_pde && (pde = create_proc_entry(name, S_IFREG, vg_ptr->lv_subdir_pde))) { pde->read_proc = _proc_read_lv; pde->data = lv; } return lv_devfs_handle[MINOR(lv->lv_dev)]; } void lvm_fs_remove_lv(vg_t *vg_ptr, lv_t *lv) { devfs_unregister(lv_devfs_handle[MINOR(lv->lv_dev)]); lv_devfs_handle[MINOR(lv->lv_dev)] = NULL; if(vg_ptr->lv_subdir_pde) { const char *name = _basename(lv->lv_name); remove_proc_entry(name, vg_ptr->lv_subdir_pde); } } static inline void _make_pv_name(const char *src, char *b, char *e) { int offset = strlen(LVM_DIR_PREFIX); if(strncmp(src, LVM_DIR_PREFIX, offset)) offset = 0; e--; src += offset; while(*src && (b != e)) { *b++ = (*src == '/') ? '_' : *src; src++; } *b = '\0'; } void lvm_fs_create_pv(vg_t *vg_ptr, pv_t *pv) { struct proc_dir_entry *pde; char name[NAME_LEN]; if(!vg_ptr->pv_subdir_pde) return; _make_pv_name(pv->pv_name, name, name + sizeof(name)); if((pde = create_proc_entry(name, S_IFREG, vg_ptr->pv_subdir_pde))) { pde->read_proc = _proc_read_pv; pde->data = pv; } } void lvm_fs_remove_pv(vg_t *vg_ptr, pv_t *pv) { char name[NAME_LEN]; if(!vg_ptr->pv_subdir_pde) return; _make_pv_name(pv->pv_name, name, name + sizeof(name)); remove_proc_entry(name, vg_ptr->pv_subdir_pde); } static int _proc_read_vg(char *page, char **start, off_t off, int count, int *eof, void *data) { int sz = 0; vg_t *vg_ptr = data; char uuid[NAME_LEN]; sz += sprintf(page + sz, "name: %s\n", vg_ptr->vg_name); sz += sprintf(page + sz, "size: %u\n", vg_ptr->pe_total * vg_ptr->pe_size / 2); sz += sprintf(page + sz, "access: %u\n", vg_ptr->vg_access); sz += sprintf(page + sz, "status: %u\n", vg_ptr->vg_status); sz += sprintf(page + sz, "number: %u\n", vg_ptr->vg_number); sz += sprintf(page + sz, "LV max: %u\n", vg_ptr->lv_max); sz += sprintf(page + sz, "LV current: %u\n", vg_ptr->lv_cur); sz += sprintf(page + sz, "LV open: %u\n", vg_ptr->lv_open); sz += sprintf(page + sz, "PV max: %u\n", vg_ptr->pv_max); sz += sprintf(page + sz, "PV current: %u\n", vg_ptr->pv_cur); sz += sprintf(page + sz, "PV active: %u\n", vg_ptr->pv_act); sz += sprintf(page + sz, "PE size: %u\n", vg_ptr->pe_size / 2); sz += sprintf(page + sz, "PE total: %u\n", vg_ptr->pe_total); sz += sprintf(page + sz, "PE allocated: %u\n", vg_ptr->pe_allocated); _show_uuid(vg_ptr->vg_uuid, uuid, uuid + sizeof(uuid)); sz += sprintf(page + sz, "uuid: %s\n", uuid); return sz; } static int _proc_read_lv(char *page, char **start, off_t off, int count, int *eof, void *data) { int sz = 0; lv_t *lv = data; sz += sprintf(page + sz, "name: %s\n", lv->lv_name); sz += sprintf(page + sz, "size: %u\n", lv->lv_size); sz += sprintf(page + sz, "access: %u\n", lv->lv_access); sz += sprintf(page + sz, "status: %u\n", lv->lv_status); sz += sprintf(page + sz, "number: %u\n", lv->lv_number); sz += sprintf(page + sz, "open: %u\n", lv->lv_open); sz += sprintf(page + sz, "allocation: %u\n", lv->lv_allocation); if(lv->lv_stripes > 1) { sz += sprintf(page + sz, "stripes: %u\n", lv->lv_stripes); sz += sprintf(page + sz, "stripesize: %u\n", lv->lv_stripesize); } sz += sprintf(page + sz, "device: %02u:%02u\n", MAJOR(lv->lv_dev), MINOR(lv->lv_dev)); return sz; } static int _proc_read_pv(char *page, char **start, off_t off, int count, int *eof, void *data) { int sz = 0; pv_t *pv = data; char uuid[NAME_LEN]; sz += sprintf(page + sz, "name: %s\n", pv->pv_name); sz += sprintf(page + sz, "size: %u\n", pv->pv_size); sz += sprintf(page + sz, "status: %u\n", pv->pv_status); sz += sprintf(page + sz, "number: %u\n", pv->pv_number); sz += sprintf(page + sz, "allocatable: %u\n", pv->pv_allocatable); sz += sprintf(page + sz, "LV current: %u\n", pv->lv_cur); sz += sprintf(page + sz, "PE size: %u\n", pv->pe_size / 2); sz += sprintf(page + sz, "PE total: %u\n", pv->pe_total); sz += sprintf(page + sz, "PE allocated: %u\n", pv->pe_allocated); sz += sprintf(page + sz, "device: %02u:%02u\n", MAJOR(pv->pv_dev), MINOR(pv->pv_dev)); _show_uuid(pv->pv_uuid, uuid, uuid + sizeof(uuid)); sz += sprintf(page + sz, "uuid: %s\n", uuid); return sz; } static int _proc_read_global(char *page, char **start, off_t pos, int count, int *eof, void *data) { #define LVM_PROC_BUF ( i == 0 ? dummy_buf : &buf[sz]) int c, i, l, p, v, vg_counter, pv_counter, lv_counter, lv_open_counter, lv_open_total, pe_t_bytes, hash_table_bytes, lv_block_exception_t_bytes, seconds; static off_t sz; off_t sz_last; static char *buf = NULL; static char dummy_buf[160]; /* sized for 2 lines */ vg_t *vg_ptr; lv_t *lv_ptr; pv_t *pv_ptr; #ifdef DEBUG_LVM_PROC_GET_INFO printk(KERN_DEBUG "%s - lvm_proc_get_global_info CALLED pos: %lu count: %d\n", lvm_name, pos, count); #endif if(pos != 0 && buf != NULL) goto out; sz_last = vg_counter = pv_counter = lv_counter = lv_open_counter = \ lv_open_total = pe_t_bytes = hash_table_bytes = \ lv_block_exception_t_bytes = 0; /* get some statistics */ for (v = 0; v < ABS_MAX_VG; v++) { if ((vg_ptr = vg[v]) != NULL) { vg_counter++; pv_counter += vg_ptr->pv_cur; lv_counter += vg_ptr->lv_cur; if (vg_ptr->lv_cur > 0) { for (l = 0; l < vg[v]->lv_max; l++) { if ((lv_ptr = vg_ptr->lv[l]) != NULL) { pe_t_bytes += lv_ptr->lv_allocated_le; hash_table_bytes += lv_ptr->lv_snapshot_hash_table_size; if (lv_ptr->lv_block_exception != NULL) lv_block_exception_t_bytes += lv_ptr->lv_remap_end; if (lv_ptr->lv_open > 0) { lv_open_counter++; lv_open_total += lv_ptr->lv_open; } } } } } } pe_t_bytes *= sizeof(pe_t); lv_block_exception_t_bytes *= sizeof(lv_block_exception_t); if (buf != NULL) { P_KFREE("%s -- vfree %d\n", lvm_name, __LINE__); lock_kernel(); vfree(buf); unlock_kernel(); buf = NULL; } /* 2 times: first to get size to allocate buffer, 2nd to fill the malloced buffer */ for (i = 0; i < 2; i++) { sz = 0; sz += sprintf(LVM_PROC_BUF, "LVM " #ifdef MODULE "module" #else "driver" #endif " %s\n\n" "Total: %d VG%s %d PV%s %d LV%s ", lvm_version, vg_counter, vg_counter == 1 ? "" : "s", pv_counter, pv_counter == 1 ? "" : "s", lv_counter, lv_counter == 1 ? "" : "s"); sz += sprintf(LVM_PROC_BUF, "(%d LV%s open", lv_open_counter, lv_open_counter == 1 ? "" : "s"); if (lv_open_total > 0) sz += sprintf(LVM_PROC_BUF, " %d times)\n", lv_open_total); else sz += sprintf(LVM_PROC_BUF, ")"); sz += sprintf(LVM_PROC_BUF, "\nGlobal: %lu bytes malloced IOP version: %d ", vg_counter * sizeof(vg_t) + pv_counter * sizeof(pv_t) + lv_counter * sizeof(lv_t) + pe_t_bytes + hash_table_bytes + lv_block_exception_t_bytes + sz_last, lvm_iop_version); seconds = CURRENT_TIME - loadtime; if (seconds < 0) loadtime = CURRENT_TIME + seconds; if (seconds / 86400 > 0) { sz += sprintf(LVM_PROC_BUF, "%d day%s ", seconds / 86400, seconds / 86400 == 0 || seconds / 86400 > 1 ? "s" : ""); } sz += sprintf(LVM_PROC_BUF, "%d:%02d:%02d active\n", (seconds % 86400) / 3600, (seconds % 3600) / 60, seconds % 60); if (vg_counter > 0) { for (v = 0; v < ABS_MAX_VG; v++) { /* volume group */ if ((vg_ptr = vg[v]) != NULL) { sz += _vg_info(vg_ptr, LVM_PROC_BUF); /* physical volumes */ sz += sprintf(LVM_PROC_BUF, "\n PV%s ", vg_ptr->pv_cur == 1 ? ": " : "s:"); c = 0; for (p = 0; p < vg_ptr->pv_max; p++) { if ((pv_ptr = vg_ptr->pv[p]) != NULL) { sz += _pv_info(pv_ptr, LVM_PROC_BUF); c++; if (c < vg_ptr->pv_cur) sz += sprintf(LVM_PROC_BUF, "\n "); } } /* logical volumes */ sz += sprintf(LVM_PROC_BUF, "\n LV%s ", vg_ptr->lv_cur == 1 ? ": " : "s:"); c = 0; for (l = 0; l < vg_ptr->lv_max; l++) { if ((lv_ptr = vg_ptr->lv[l]) != NULL) { sz += _lv_info(vg_ptr, lv_ptr, LVM_PROC_BUF); c++; if (c < vg_ptr->lv_cur) sz += sprintf(LVM_PROC_BUF, "\n "); } } if (vg_ptr->lv_cur == 0) sz += sprintf(LVM_PROC_BUF, "none"); sz += sprintf(LVM_PROC_BUF, "\n"); } } } if (buf == NULL) { lock_kernel(); buf = vmalloc(sz); unlock_kernel(); if (buf == NULL) { sz = 0; return sprintf(page, "%s - vmalloc error at line %d\n", lvm_name, __LINE__); } } sz_last = sz; } out: if (pos > sz - 1) { lock_kernel(); vfree(buf); unlock_kernel(); buf = NULL; return 0; } *start = &buf[pos]; if (sz - pos < count) return sz - pos; else return count; #undef LVM_PROC_BUF } /* * provide VG info for proc filesystem use (global) */ static int _vg_info(vg_t *vg_ptr, char *buf) { int sz = 0; char inactive_flag = ' '; if (!(vg_ptr->vg_status & VG_ACTIVE)) inactive_flag = 'I'; sz = sprintf(buf, "\nVG: %c%s [%d PV, %d LV/%d open] " " PE Size: %d KB\n" " Usage [KB/PE]: %d /%d total " "%d /%d used %d /%d free", inactive_flag, vg_ptr->vg_name, vg_ptr->pv_cur, vg_ptr->lv_cur, vg_ptr->lv_open, vg_ptr->pe_size >> 1, vg_ptr->pe_size * vg_ptr->pe_total >> 1, vg_ptr->pe_total, vg_ptr->pe_allocated * vg_ptr->pe_size >> 1, vg_ptr->pe_allocated, (vg_ptr->pe_total - vg_ptr->pe_allocated) * vg_ptr->pe_size >> 1, vg_ptr->pe_total - vg_ptr->pe_allocated); return sz; } /* * provide LV info for proc filesystem use (global) */ static int _lv_info(vg_t *vg_ptr, lv_t *lv_ptr, char *buf) { int sz = 0; char inactive_flag = 'A', allocation_flag = ' ', stripes_flag = ' ', rw_flag = ' ', *basename; if (!(lv_ptr->lv_status & LV_ACTIVE)) inactive_flag = 'I'; rw_flag = 'R'; if (lv_ptr->lv_access & LV_WRITE) rw_flag = 'W'; allocation_flag = 'D'; if (lv_ptr->lv_allocation & LV_CONTIGUOUS) allocation_flag = 'C'; stripes_flag = 'L'; if (lv_ptr->lv_stripes > 1) stripes_flag = 'S'; sz += sprintf(buf+sz, "[%c%c%c%c", inactive_flag, rw_flag, allocation_flag, stripes_flag); if (lv_ptr->lv_stripes > 1) sz += sprintf(buf+sz, "%-2d", lv_ptr->lv_stripes); else sz += sprintf(buf+sz, " "); /* FIXME: use _basename */ basename = strrchr(lv_ptr->lv_name, '/'); if ( basename == 0) basename = lv_ptr->lv_name; else basename++; sz += sprintf(buf+sz, "] %-25s", basename); if (strlen(basename) > 25) sz += sprintf(buf+sz, "\n "); sz += sprintf(buf+sz, "%9d /%-6d ", lv_ptr->lv_size >> 1, lv_ptr->lv_size / vg_ptr->pe_size); if (lv_ptr->lv_open == 0) sz += sprintf(buf+sz, "close"); else sz += sprintf(buf+sz, "%dx open", lv_ptr->lv_open); return sz; } /* * provide PV info for proc filesystem use (global) */ static int _pv_info(pv_t *pv, char *buf) { int sz = 0; char inactive_flag = 'A', allocation_flag = ' '; char *pv_name = NULL; if (!(pv->pv_status & PV_ACTIVE)) inactive_flag = 'I'; allocation_flag = 'A'; if (!(pv->pv_allocatable & PV_ALLOCATABLE)) allocation_flag = 'N'; pv_name = strchr(pv->pv_name+1,'/'); if ( pv_name == 0) pv_name = pv->pv_name; else pv_name++; sz = sprintf(buf, "[%c%c] %-21s %8d /%-6d " "%8d /%-6d %8d /%-6d", inactive_flag, allocation_flag, pv_name, pv->pe_total * pv->pe_size >> 1, pv->pe_total, pv->pe_allocated * pv->pe_size >> 1, pv->pe_allocated, (pv->pe_total - pv->pe_allocated) * pv->pe_size >> 1, pv->pe_total - pv->pe_allocated); return sz; } static void _show_uuid(const char *src, char *b, char *e) { int i; e--; for(i = 0; *src && (b != e); i++) { if(i && !(i & 0x3)) *b++ = '-'; *b++ = *src++; } *b = '\0'; }