blob: a7418a60f2dab702c744ec07be77d279fa607f03 [file] [log] [blame]
/*
* kernel/lvm-fs.c
*
* Copyright (C) 2001-2002 Sistina Software
*
* January-May,December 2001
* May 2002
*
* 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 <linux/config.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/vmalloc.h>
#include <linux/smp_lock.h>
#include <linux/devfs_fs_kernel.h>
#include <linux/proc_fs.h>
#include <linux/init.h>
#include <linux/lvm.h>
#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;
if (!vg_ptr)
return;
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;
if (!vg_ptr)
return;
devfs_unregister(ch_devfs_handle[vg_ptr->vg_number]);
ch_devfs_handle[vg_ptr->vg_number] = NULL;
/* 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;
/* 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]);
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;
if (!vg_ptr || !lv)
return NULL;
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)
{
if (!vg_ptr || !lv)
return;
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)
return;
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)
return;
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';
}