X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=kernel%2Fvserver%2Fproc.c;h=b16949bca2ab3dca5c9b8c8db9fad42370a64135;hb=43bc926fffd92024b46cafaf7350d669ba9ca884;hp=42bc182001c8a3acb3761c55cbb40c6a09d28909;hpb=b76fcd5f0c655b6e3e9bf534594357025421c66a;p=linux-2.6.git diff --git a/kernel/vserver/proc.c b/kernel/vserver/proc.c index 42bc18200..b16949bca 100644 --- a/kernel/vserver/proc.c +++ b/kernel/vserver/proc.c @@ -3,7 +3,7 @@ * * Virtual Context Support * - * Copyright (C) 2003-2004 Herbert Pötzl + * Copyright (C) 2003-2005 Herbert Pötzl * * V0.01 basic structure * V0.02 adaptation vs1.3.0 @@ -15,14 +15,22 @@ * */ -#include #include #include -#include +#include +#include +#include +#include + +#include #include #include +#include "cvirt_proc.h" +#include "limit_proc.h" +#include "sched_proc.h" +#include "vci_config.h" static struct proc_dir_entry *proc_virtual; @@ -43,7 +51,7 @@ enum vid_directory_inos { PROC_NID_STATUS, }; -#define PROC_VID_MASK 0x60 +#define PROC_VID_MASK 0x60 /* first the actual feeds */ @@ -54,9 +62,11 @@ static int proc_virtual_info(int vid, char *buffer) return sprintf(buffer, "VCIVersion:\t%04x:%04x\n" "VCISyscall:\t%d\n" + "VCIKernel:\t%08x\n" ,VCI_VERSION >> 16 ,VCI_VERSION & 0xFFFF ,__NR_vserver + ,vci_kernel_config() ); } @@ -66,7 +76,7 @@ int proc_xid_info (int vid, char *buffer) struct vx_info *vxi; int length; - vxi = find_vx_info(vid); + vxi = lookup_vx_info(vid); if (!vxi) return 0; length = sprintf(buffer, @@ -86,19 +96,21 @@ int proc_xid_status (int vid, char *buffer) struct vx_info *vxi; int length; - vxi = find_vx_info(vid); + vxi = lookup_vx_info(vid); if (!vxi) return 0; length = sprintf(buffer, - "RefC:\t%d\n" + "UseCnt:\t%d\n" + "Tasks:\t%d\n" "Flags:\t%016llx\n" "BCaps:\t%016llx\n" "CCaps:\t%016llx\n" - "Ticks:\t%d\n" - ,atomic_read(&vxi->vx_refcount) - ,vxi->vx_flags - ,vxi->vx_bcaps - ,vxi->vx_ccaps + "Ticks:\t%d\n" + ,atomic_read(&vxi->vx_usecnt) + ,atomic_read(&vxi->vx_tasks) + ,(unsigned long long)vxi->vx_flags + ,(unsigned long long)vxi->vx_bcaps + ,(unsigned long long)vxi->vx_ccaps ,atomic_read(&vxi->limit.ticks) ); put_vx_info(vxi); @@ -110,7 +122,7 @@ int proc_xid_limit (int vid, char *buffer) struct vx_info *vxi; int length; - vxi = find_vx_info(vid); + vxi = lookup_vx_info(vid); if (!vxi) return 0; length = vx_info_proc_limit(&vxi->limit, buffer); @@ -123,7 +135,7 @@ int proc_xid_sched (int vid, char *buffer) struct vx_info *vxi; int length; - vxi = find_vx_info(vid); + vxi = lookup_vx_info(vid); if (!vxi) return 0; length = vx_info_proc_sched(&vxi->sched, buffer); @@ -136,9 +148,10 @@ int proc_xid_cvirt (int vid, char *buffer) struct vx_info *vxi; int length; - vxi = find_vx_info(vid); + vxi = lookup_vx_info(vid); if (!vxi) return 0; + vx_update_load(vxi); length = vx_info_proc_cvirt(&vxi->cvirt, buffer); put_vx_info(vxi); return length; @@ -149,7 +162,7 @@ int proc_xid_cacct (int vid, char *buffer) struct vx_info *vxi; int length; - vxi = find_vx_info(vid); + vxi = lookup_vx_info(vid); if (!vxi) return 0; length = vx_info_proc_cacct(&vxi->cacct, buffer); @@ -169,16 +182,13 @@ static int proc_vnet_info(int vid, char *buffer) ); } -#define atoquad(a) \ - (((a)>>0) & 0xff), (((a)>>8) & 0xff), \ - (((a)>>16) & 0xff), (((a)>>24) & 0xff) int proc_nid_info (int vid, char *buffer) { struct nx_info *nxi; int length, i; - nxi = find_nx_info(vid); + nxi = lookup_nx_info(vid); if (!nxi) return 0; length = sprintf(buffer, @@ -189,9 +199,8 @@ int proc_nid_info (int vid, char *buffer) ); for (i=0; inbipv4; i++) { length += sprintf(buffer + length, - "%d:\t%d.%d.%d.%d/%d.%d.%d.%d\n", i, - atoquad(nxi->ipv4[i]), - atoquad(nxi->mask[i])); + "%d:\t" NIPQUAD_FMT "/" NIPQUAD_FMT "\n", i, + NIPQUAD(nxi->ipv4[i]), NIPQUAD(nxi->mask[i])); } put_nx_info(nxi); return length; @@ -202,12 +211,14 @@ int proc_nid_status (int vid, char *buffer) struct nx_info *nxi; int length; - nxi = find_nx_info(vid); + nxi = lookup_nx_info(vid); if (!nxi) return 0; length = sprintf(buffer, - "RefC:\t%d\n" - ,atomic_read(&nxi->nx_refcount) + "UseCnt:\t%d\n" + "Tasks:\t%d\n" + ,atomic_read(&nxi->nx_usecnt) + ,atomic_read(&nxi->nx_tasks) ); put_nx_info(nxi); return length; @@ -216,11 +227,11 @@ int proc_nid_status (int vid, char *buffer) /* here the inode helpers */ +#define fake_ino(id,nr) (((nr) & 0xFFFF) | \ + (((id) & 0xFFFF) << 16)) -#define fake_ino(id,ino) (((id)<<16)|(ino)) - -#define inode_vid(i) ((i)->i_ino >> 16) -#define inode_type(i) ((i)->i_ino & 0xFFFF) +#define inode_vid(i) (((i)->i_ino >> 16) & 0xFFFF) +#define inode_type(i) ((i)->i_ino & 0xFFFF) #define MAX_MULBY10 ((~0U-9)/10) @@ -239,7 +250,6 @@ static struct inode *proc_vid_make_inode(struct super_block * sb, inode->i_uid = 0; inode->i_gid = 0; - // inode->i_xid = xid; out: return inode; } @@ -247,40 +257,32 @@ out: static int proc_vid_revalidate(struct dentry * dentry, struct nameidata *nd) { struct inode * inode = dentry->d_inode; - int vid, valid=0; + int vid, hashed=0; vid = inode_vid(inode); switch (inode_type(inode) & PROC_VID_MASK) { case PROC_XID_INO: - valid = vx_info_id_valid(vid); + hashed = xid_is_hashed(vid); break; case PROC_NID_INO: - valid = nx_info_id_valid(vid); + hashed = nid_is_hashed(vid); break; - } - if (valid) + } + if (hashed) return 1; d_drop(dentry); return 0; } -/* -static int proc_vid_delete_dentry(struct dentry * dentry) -{ - return 1; -} -*/ - #define PROC_BLOCK_SIZE (PAGE_SIZE - 1024) -static ssize_t proc_vid_info_read(struct file * file, char * buf, +static ssize_t proc_vid_info_read(struct file * file, char __user * buf, size_t count, loff_t *ppos) { struct inode * inode = file->f_dentry->d_inode; unsigned long page; ssize_t length; - ssize_t end; int vid; if (count > PROC_BLOCK_SIZE) @@ -291,22 +293,11 @@ static ssize_t proc_vid_info_read(struct file * file, char * buf, vid = inode_vid(inode); length = PROC_I(inode)->op.proc_vid_read(vid, (char*)page); - if (length < 0) { - free_page(page); - return length; - } - /* Static 4kB (or whatever) block capacity */ - if (*ppos >= length) { - free_page(page); - return 0; - } - if (count + *ppos > length) - count = length - *ppos; - end = count + *ppos; - copy_to_user(buf, (char *) page + *ppos, count); - *ppos = end; + if (length >= 0) + length = simple_read_from_buffer(buf, count, ppos, + (char *)page, length); free_page(page); - return count; + return length; } @@ -316,12 +307,11 @@ static ssize_t proc_vid_info_read(struct file * file, char * buf, /* here comes the lower level (vid) */ static struct file_operations proc_vid_info_file_operations = { - read: proc_vid_info_read, + .read = proc_vid_info_read, }; static struct dentry_operations proc_vid_dentry_operations = { - d_revalidate: proc_vid_revalidate, -// d_delete: proc_vid_delete_dentry, + .d_revalidate = proc_vid_revalidate, }; @@ -364,10 +354,10 @@ static struct dentry *proc_vid_lookup(struct inode *dir, switch (inode_type(dir)) { case PROC_XID_INO: - p = vx_base_stuff; + p = vx_base_stuff; break; case PROC_NID_INO: - p = vn_base_stuff; + p = vn_base_stuff; break; default: goto out; @@ -413,18 +403,17 @@ static struct dentry *proc_vid_lookup(struct inode *dir, case PROC_NID_STATUS: PROC_I(inode)->op.proc_vid_read = proc_nid_status; break; - + default: printk("procfs: impossible type (%d)",p->type); iput(inode); return ERR_PTR(-EINVAL); } inode->i_mode = p->mode; -// inode->i_op = &proc_vid_info_inode_operations; inode->i_fop = &proc_vid_info_file_operations; inode->i_nlink = 1; inode->i_flags|=S_IMMUTABLE; - + dentry->d_op = &proc_vid_dentry_operations; d_add(dentry, inode); error = 0; @@ -439,47 +428,47 @@ static int proc_vid_readdir(struct file * filp, int i, size; struct inode *inode = filp->f_dentry->d_inode; struct vid_entry *p; - + i = filp->f_pos; switch (i) { - case 0: - if (filldir(dirent, ".", 1, i, - inode->i_ino, DT_DIR) < 0) - return 0; - i++; - filp->f_pos++; - /* fall through */ - case 1: - if (filldir(dirent, "..", 2, i, - PROC_ROOT_INO, DT_DIR) < 0) + case 0: + if (filldir(dirent, ".", 1, i, + inode->i_ino, DT_DIR) < 0) + return 0; + i++; + filp->f_pos++; + /* fall through */ + case 1: + if (filldir(dirent, "..", 2, i, + PROC_ROOT_INO, DT_DIR) < 0) + return 0; + i++; + filp->f_pos++; + /* fall through */ + default: + i -= 2; + switch (inode_type(inode)) { + case PROC_XID_INO: + size = sizeof(vx_base_stuff); + p = vx_base_stuff + i; + break; + case PROC_NID_INO: + size = sizeof(vn_base_stuff); + p = vn_base_stuff + i; + break; + default: + return 1; + } + if (i >= size/sizeof(struct vid_entry)) + return 1; + while (p->name) { + if (filldir(dirent, p->name, p->len, + filp->f_pos, fake_ino(inode_vid(inode), + p->type), p->mode >> 12) < 0) return 0; - i++; filp->f_pos++; - /* fall through */ - default: - i -= 2; - switch (inode_type(inode)) { - case PROC_XID_INO: - size = sizeof(vx_base_stuff); - p = vx_base_stuff + i; - break; - case PROC_NID_INO: - size = sizeof(vn_base_stuff); - p = vn_base_stuff + i; - break; - default: - return 1; - } - if (i >= size/sizeof(struct vid_entry)) - return 1; - while (p->name) { - if (filldir(dirent, p->name, p->len, - filp->f_pos, fake_ino(inode_vid(inode), - p->type), p->mode >> 12) < 0) - return 0; - filp->f_pos++; - p++; - } + p++; + } } return 1; } @@ -490,12 +479,12 @@ static int proc_vid_readdir(struct file * filp, /* now the upper level (virtual) */ static struct file_operations proc_vid_file_operations = { - read: generic_read_dir, - readdir: proc_vid_readdir, + .read = generic_read_dir, + .readdir = proc_vid_readdir, }; static struct inode_operations proc_vid_inode_operations = { - lookup: proc_vid_lookup, + .lookup = proc_vid_lookup, }; @@ -542,8 +531,6 @@ struct dentry *proc_virtual_lookup(struct inode *dir, inode->i_ino = fake_ino(1, PROC_XID_INO); inode->i_mode = S_IFLNK|S_IRWXUGO; inode->i_uid = inode->i_gid = 0; - inode->i_size = 64; -// inode->i_op = &proc_current_inode_operations; d_add(dentry, inode); return NULL; } @@ -554,8 +541,6 @@ struct dentry *proc_virtual_lookup(struct inode *dir, inode->i_fop = &proc_vid_info_file_operations; PROC_I(inode)->op.proc_vid_read = proc_virtual_info; inode->i_mode = S_IFREG|S_IRUGO; -// inode->i_size = 64; -// inode->i_op = &proc_current_inode_operations; d_add(dentry, inode); return NULL; } @@ -564,7 +549,7 @@ struct dentry *proc_virtual_lookup(struct inode *dir, xid = atovid(name, len); if (xid < 0) goto out; - vxi = find_vx_info(xid); + vxi = lookup_vx_info(xid); if (!vxi) goto out; @@ -575,7 +560,7 @@ struct dentry *proc_virtual_lookup(struct inode *dir, if (!inode) goto out_release; - inode->i_mode = S_IFDIR|S_IRUGO; + inode->i_mode = S_IFDIR|S_IRUGO|S_IXUGO; inode->i_op = &proc_vid_inode_operations; inode->i_fop = &proc_vid_file_operations; inode->i_nlink = 2; @@ -584,7 +569,7 @@ struct dentry *proc_virtual_lookup(struct inode *dir, dentry->d_op = &proc_vid_dentry_operations; d_add(dentry, inode); ret = 0; - + out_release: put_vx_info(vxi); out: @@ -612,8 +597,6 @@ struct dentry *proc_vnet_lookup(struct inode *dir, inode->i_ino = fake_ino(1, PROC_NID_INO); inode->i_mode = S_IFLNK|S_IRWXUGO; inode->i_uid = inode->i_gid = 0; - inode->i_size = 64; -// inode->i_op = &proc_current_inode_operations; d_add(dentry, inode); return NULL; } @@ -624,8 +607,6 @@ struct dentry *proc_vnet_lookup(struct inode *dir, inode->i_fop = &proc_vid_info_file_operations; PROC_I(inode)->op.proc_vid_read = proc_vnet_info; inode->i_mode = S_IFREG|S_IRUGO; -// inode->i_size = 64; -// inode->i_op = &proc_current_inode_operations; d_add(dentry, inode); return NULL; } @@ -634,7 +615,7 @@ struct dentry *proc_vnet_lookup(struct inode *dir, nid = atovid(name, len); if (nid < 0) goto out; - nxi = find_nx_info(nid); + nxi = lookup_nx_info(nid); if (!nxi) goto out; @@ -645,7 +626,7 @@ struct dentry *proc_vnet_lookup(struct inode *dir, if (!inode) goto out_release; - inode->i_mode = S_IFDIR|S_IRUGO; + inode->i_mode = S_IFDIR|S_IRUGO|S_IXUGO; inode->i_op = &proc_vid_inode_operations; inode->i_fop = &proc_vid_file_operations; inode->i_nlink = 2; @@ -654,7 +635,7 @@ struct dentry *proc_vnet_lookup(struct inode *dir, dentry->d_op = &proc_vid_dentry_operations; d_add(dentry, inode); ret = 0; - + out_release: put_nx_info(nxi); out: @@ -667,27 +648,6 @@ out: #define PROC_NUMBUF 10 #define PROC_MAXVIDS 32 - -static int get_xid_list(int index, unsigned int *xids) -{ - struct vx_info *p; - int nr_xids = 0; - - index--; - spin_lock(&vxlist_lock); - list_for_each_entry(p, &vx_infos, vx_list) { - int xid = p->vx_id; - - if (--index >= 0) - continue; - xids[nr_xids] = xid; - if (++nr_xids >= PROC_MAXVIDS) - break; - } - spin_unlock(&vxlist_lock); - return nr_xids; -} - int proc_virtual_readdir(struct file * filp, void * dirent, filldir_t filldir) { @@ -698,43 +658,42 @@ int proc_virtual_readdir(struct file * filp, ino_t ino; switch ((long)filp->f_pos) { - case 0: - ino = fake_ino(0, PROC_XID_INO); - if (filldir(dirent, ".", 1, - filp->f_pos, ino, DT_DIR) < 0) - return 0; - filp->f_pos++; - /* fall through */ - case 1: - ino = filp->f_dentry->d_parent->d_inode->i_ino; - if (filldir(dirent, "..", 2, - filp->f_pos, ino, DT_DIR) < 0) - return 0; - filp->f_pos++; - /* fall through */ - case 2: - ino = fake_ino(0, PROC_XID_INFO); - if (filldir(dirent, "info", 4, + case 0: + ino = fake_ino(0, PROC_XID_INO); + if (filldir(dirent, ".", 1, + filp->f_pos, ino, DT_DIR) < 0) + return 0; + filp->f_pos++; + /* fall through */ + case 1: + ino = filp->f_dentry->d_parent->d_inode->i_ino; + if (filldir(dirent, "..", 2, + filp->f_pos, ino, DT_DIR) < 0) + return 0; + filp->f_pos++; + /* fall through */ + case 2: + ino = fake_ino(0, PROC_XID_INFO); + if (filldir(dirent, "info", 4, + filp->f_pos, ino, DT_LNK) < 0) + return 0; + filp->f_pos++; + /* fall through */ + case 3: + if (vx_current_xid() > 1) { + ino = fake_ino(1, PROC_XID_INO); + if (filldir(dirent, "current", 7, filp->f_pos, ino, DT_LNK) < 0) return 0; - filp->f_pos++; - /* fall through */ - case 3: - if (current->xid > 1) { - ino = fake_ino(1, PROC_XID_INO); - if (filldir(dirent, "current", 7, - filp->f_pos, ino, DT_LNK) < 0) - return 0; - } - filp->f_pos++; + } + filp->f_pos++; } - nr_xids = get_xid_list(nr, xid_array); - + nr_xids = get_xid_list(nr, xid_array, PROC_MAXVIDS); for (i = 0; i < nr_xids; i++) { int xid = xid_array[i]; ino_t ino = fake_ino(xid, PROC_XID_INO); - unsigned long j = PROC_NUMBUF; + unsigned int j = PROC_NUMBUF; do buf[--j] = '0' + (xid % 10); while (xid/=10); @@ -748,79 +707,49 @@ int proc_virtual_readdir(struct file * filp, static struct file_operations proc_virtual_dir_operations = { - read: generic_read_dir, - readdir: proc_virtual_readdir, + .read = generic_read_dir, + .readdir = proc_virtual_readdir, }; static struct inode_operations proc_virtual_dir_inode_operations = { - lookup: proc_virtual_lookup, + .lookup = proc_virtual_lookup, }; - -static int get_nid_list(int index, unsigned int *nids) -{ - struct nx_info *p; - int nr_nids = 0; - - index--; - spin_lock(&nxlist_lock); - list_for_each_entry(p, &nx_infos, nx_list) { - int nid = p->nx_id; - - if (--index >= 0) - continue; - nids[nr_nids] = nid; - if (++nr_nids >= PROC_MAXVIDS) - break; - } - spin_unlock(&nxlist_lock); - return nr_nids; -} - int proc_vnet_readdir(struct file * filp, void * dirent, filldir_t filldir) { unsigned int nid_array[PROC_MAXVIDS]; char buf[PROC_NUMBUF]; - unsigned int nr = filp->f_pos-3; + unsigned int nr = filp->f_pos-2; unsigned int nr_nids, i; ino_t ino; switch ((long)filp->f_pos) { - case 0: - ino = fake_ino(0, PROC_NID_INO); - if (filldir(dirent, ".", 1, - filp->f_pos, ino, DT_DIR) < 0) - return 0; - filp->f_pos++; - /* fall through */ - case 1: - ino = filp->f_dentry->d_parent->d_inode->i_ino; - if (filldir(dirent, "..", 2, - filp->f_pos, ino, DT_DIR) < 0) - return 0; - filp->f_pos++; - /* fall through */ - case 2: - ino = fake_ino(0, PROC_NID_INFO); - if (filldir(dirent, "info", 4, - filp->f_pos, ino, DT_LNK) < 0) - return 0; - filp->f_pos++; - /* fall through */ - case 3: - if (current->xid > 1) { - ino = fake_ino(1, PROC_NID_INO); - if (filldir(dirent, "current", 7, - filp->f_pos, ino, DT_LNK) < 0) - return 0; - } - filp->f_pos++; + case 0: + ino = fake_ino(0, PROC_NID_INO); + if (filldir(dirent, ".", 1, + filp->f_pos, ino, DT_DIR) < 0) + return 0; + filp->f_pos++; + /* fall through */ + case 1: + ino = filp->f_dentry->d_parent->d_inode->i_ino; + if (filldir(dirent, "..", 2, + filp->f_pos, ino, DT_DIR) < 0) + return 0; + filp->f_pos++; + /* fall through */ + case 2: + ino = fake_ino(0, PROC_NID_INFO); + if (filldir(dirent, "info", 4, + filp->f_pos, ino, DT_REG) < 0) + return 0; + filp->f_pos++; + /* fall through */ } - nr_nids = get_nid_list(nr, nid_array); - + nr_nids = get_nid_list(nr, nid_array, PROC_MAXVIDS); for (i = 0; i < nr_nids; i++) { int nid = nid_array[i]; ino_t ino = fake_ino(nid, PROC_NID_INO); @@ -838,12 +767,12 @@ int proc_vnet_readdir(struct file * filp, static struct file_operations proc_vnet_dir_operations = { - read: generic_read_dir, - readdir: proc_vnet_readdir, + .read = generic_read_dir, + .readdir = proc_vnet_readdir, }; static struct inode_operations proc_vnet_dir_inode_operations = { - lookup: proc_vnet_lookup, + .lookup = proc_vnet_lookup, }; @@ -859,7 +788,7 @@ void proc_vx_init(void) } proc_virtual = ent; - ent = proc_mkdir("vnet", 0); + ent = proc_mkdir("virtnet", 0); if (ent) { ent->proc_fops = &proc_vnet_dir_operations; ent->proc_iops = &proc_vnet_dir_inode_operations; @@ -873,33 +802,59 @@ void proc_vx_init(void) /* per pid info */ -char *task_vx_info(struct task_struct *p, char *buffer) -{ - return buffer + sprintf(buffer, - "XID:\t%d\n" - ,p->xid); -} - int proc_pid_vx_info(struct task_struct *p, char *buffer) { + struct vx_info *vxi; char * orig = buffer; - buffer = task_vx_info(p, buffer); + buffer += sprintf (buffer,"XID:\t%d\n", vx_task_xid(p)); + if (vx_flags(VXF_INFO_HIDE, 0)) + goto out; + + vxi = task_get_vx_info(p); + if (!vxi) + goto out; + + buffer += sprintf (buffer,"BCaps:\t%016llx\n" + ,(unsigned long long)vxi->vx_bcaps); + buffer += sprintf (buffer,"CCaps:\t%016llx\n" + ,(unsigned long long)vxi->vx_ccaps); + buffer += sprintf (buffer,"CFlags:\t%016llx\n" + ,(unsigned long long)vxi->vx_flags); + buffer += sprintf (buffer,"CIPid:\t%d\n" + ,vxi->vx_initpid); + + put_vx_info(vxi); +out: return buffer - orig; } -char *task_nx_info(struct task_struct *p, char *buffer) -{ - return buffer + sprintf(buffer, - "NID:\t%d\n" - ,p->nid); -} int proc_pid_nx_info(struct task_struct *p, char *buffer) { + struct nx_info *nxi; char * orig = buffer; + int i; - buffer = task_nx_info(p, buffer); + buffer += sprintf (buffer,"NID:\t%d\n", nx_task_nid(p)); + if (vx_flags(VXF_INFO_HIDE, 0)) + goto out; + nxi = task_get_nx_info(p); + if (!nxi) + goto out; + + for (i=0; inbipv4; i++){ + buffer += sprintf (buffer, + "V4Root[%d]:\t%d.%d.%d.%d/%d.%d.%d.%d\n", i + ,NIPQUAD(nxi->ipv4[i]) + ,NIPQUAD(nxi->mask[i])); + } + buffer += sprintf (buffer, + "V4Root[bcast]:\t%d.%d.%d.%d\n" + ,NIPQUAD(nxi->v4_bcast)); + + put_nx_info(nxi); +out: return buffer - orig; }