449 lines · 9.6 KB · C
RawDownload
/* * linux/fs/seq_file.c * * helper functions for making synthetic files from sequences of records. * initial implementation -- AV, Oct 2001. */ #include <linux/fs.h>#include <linux/module.h>#include <linux/seq_file.h>#include <linux/slab.h> #include <asm/uaccess.h>#include <asm/page.h> /** *	seq_open -	initialize sequential file *	@file: file we initialize *	@op: method table describing the sequence * *	seq_open() sets @file, associating it with a sequence described *	by @op.  @op->start() sets the iterator up and returns the first *	element of sequence. @op->stop() shuts it down.  @op->next() *	returns the next element of sequence.  @op->show() prints element *	into the buffer.  In case of error ->start() and ->next() return *	ERR_PTR(error).  In the end of sequence they return %NULL. ->show() *	returns 0 in case of success and negative number in case of error. */int seq_open(struct file *file, struct seq_operations *op){	struct seq_file *p = file->private_data; 	if (!p) {		p = kmalloc(sizeof(*p), GFP_KERNEL);		if (!p)			return -ENOMEM;		file->private_data = p;	}	memset(p, 0, sizeof(*p));	mutex_init(&p->lock);	p->op = op; 	/*	 * Wrappers around seq_open(e.g. swaps_open) need to be	 * aware of this. If they set f_version themselves, they	 * should call seq_open first and then set f_version.	 */	file->f_version = 0; 	/* SEQ files support lseek, but not pread/pwrite */	file->f_mode &= ~(FMODE_PREAD | FMODE_PWRITE);	return 0;}EXPORT_SYMBOL(seq_open); /** *	seq_read -	->read() method for sequential files. *	@file: the file to read from *	@buf: the buffer to read to *	@size: the maximum number of bytes to read *	@ppos: the current position in the file * *	Ready-made ->f_op->read() */ssize_t seq_read(struct file *file, char __user *buf, size_t size, loff_t *ppos){	struct seq_file *m = (struct seq_file *)file->private_data;	size_t copied = 0;	loff_t pos;	size_t n;	void *p;	int err = 0; 	mutex_lock(&m->lock);	/*	 * seq_file->op->..m_start/m_stop/m_next may do special actions	 * or optimisations based on the file->f_version, so we want to	 * pass the file->f_version to those methods.	 *	 * seq_file->version is just copy of f_version, and seq_file	 * methods can treat it simply as file version.	 * It is copied in first and copied out after all operations.	 * It is convenient to have it as  part of structure to avoid the	 * need of passing another argument to all the seq_file methods.	 */	m->version = file->f_version;	/* grab buffer if we didn't have one */	if (!m->buf) {		m->buf = kmalloc(m->size = PAGE_SIZE, GFP_KERNEL);		if (!m->buf)			goto Enomem;	}	/* if not empty - flush it first */	if (m->count) {		n = min(m->count, size);		err = copy_to_user(buf, m->buf + m->from, n);		if (err)			goto Efault;		m->count -= n;		m->from += n;		size -= n;		buf += n;		copied += n;		if (!m->count)			m->index++;		if (!size)			goto Done;	}	/* we need at least one record in buffer */	while (1) {		pos = m->index;		p = m->op->start(m, &pos);		err = PTR_ERR(p);		if (!p || IS_ERR(p))			break;		err = m->op->show(m, p);		if (err)			break;		if (m->count < m->size)			goto Fill;		m->op->stop(m, p);		kfree(m->buf);		m->buf = kmalloc(m->size <<= 1, GFP_KERNEL);		if (!m->buf)			goto Enomem;		m->count = 0;		m->version = 0;	}	m->op->stop(m, p);	m->count = 0;	goto Done;Fill:	/* they want more? let's try to get some more */	while (m->count < size) {		size_t offs = m->count;		loff_t next = pos;		p = m->op->next(m, p, &next);		if (!p || IS_ERR(p)) {			err = PTR_ERR(p);			break;		}		err = m->op->show(m, p);		if (err || m->count == m->size) {			m->count = offs;			break;		}		pos = next;	}	m->op->stop(m, p);	n = min(m->count, size);	err = copy_to_user(buf, m->buf, n);	if (err)		goto Efault;	copied += n;	m->count -= n;	if (m->count)		m->from = n;	else		pos++;	m->index = pos;Done:	if (!copied)		copied = err;	else		*ppos += copied;	file->f_version = m->version;	mutex_unlock(&m->lock);	return copied;Enomem:	err = -ENOMEM;	goto Done;Efault:	err = -EFAULT;	goto Done;}EXPORT_SYMBOL(seq_read); static int traverse(struct seq_file *m, loff_t offset){	loff_t pos = 0;	int error = 0;	void *p; 	m->version = 0;	m->index = 0;	m->count = m->from = 0;	if (!offset)		return 0;	if (!m->buf) {		m->buf = kmalloc(m->size = PAGE_SIZE, GFP_KERNEL);		if (!m->buf)			return -ENOMEM;	}	p = m->op->start(m, &m->index);	while (p) {		error = PTR_ERR(p);		if (IS_ERR(p))			break;		error = m->op->show(m, p);		if (error)			break;		if (m->count == m->size)			goto Eoverflow;		if (pos + m->count > offset) {			m->from = offset - pos;			m->count -= m->from;			break;		}		pos += m->count;		m->count = 0;		if (pos == offset) {			m->index++;			break;		}		p = m->op->next(m, p, &m->index);	}	m->op->stop(m, p);	return error; Eoverflow:	m->op->stop(m, p);	kfree(m->buf);	m->buf = kmalloc(m->size <<= 1, GFP_KERNEL);	return !m->buf ? -ENOMEM : -EAGAIN;} /** *	seq_lseek -	->llseek() method for sequential files. *	@file: the file in question *	@offset: new position *	@origin: 0 for absolute, 1 for relative position * *	Ready-made ->f_op->llseek() */loff_t seq_lseek(struct file *file, loff_t offset, int origin){	struct seq_file *m = (struct seq_file *)file->private_data;	long long retval = -EINVAL; 	mutex_lock(&m->lock);	m->version = file->f_version;	switch (origin) {		case 1:			offset += file->f_pos;		case 0:			if (offset < 0)				break;			retval = offset;			if (offset != file->f_pos) {				while ((retval=traverse(m, offset)) == -EAGAIN)					;				if (retval) {					/* with extreme prejudice... */					file->f_pos = 0;					m->version = 0;					m->index = 0;					m->count = 0;				} else {					retval = file->f_pos = offset;				}			}	}	mutex_unlock(&m->lock);	file->f_version = m->version;	return retval;}EXPORT_SYMBOL(seq_lseek); /** *	seq_release -	free the structures associated with sequential file. *	@file: file in question *	@inode: file->f_dentry->d_inode * *	Frees the structures associated with sequential file; can be used *	as ->f_op->release() if you don't have private data to destroy. */int seq_release(struct inode *inode, struct file *file){	struct seq_file *m = (struct seq_file *)file->private_data;	kfree(m->buf);	kfree(m);	return 0;}EXPORT_SYMBOL(seq_release); /** *	seq_escape -	print string into buffer, escaping some characters *	@m:	target buffer *	@s:	string *	@esc:	set of characters that need escaping * *	Puts string into buffer, replacing each occurrence of character from *	@esc with usual octal escape.  Returns 0 in case of success, -1 - in *	case of overflow. */int seq_escape(struct seq_file *m, const char *s, const char *esc){	char *end = m->buf + m->size;        char *p;	char c;         for (p = m->buf + m->count; (c = *s) != '\0' && p < end; s++) {		if (!strchr(esc, c)) {			*p++ = c;			continue;		}		if (p + 3 < end) {			*p++ = '\\';			*p++ = '0' + ((c & 0300) >> 6);			*p++ = '0' + ((c & 070) >> 3);			*p++ = '0' + (c & 07);			continue;		}		m->count = m->size;		return -1;        }	m->count = p - m->buf;        return 0;}EXPORT_SYMBOL(seq_escape); int seq_printf(struct seq_file *m, const char *f, ...){	va_list args;	int len; 	if (m->count < m->size) {		va_start(args, f);		len = vsnprintf(m->buf + m->count, m->size - m->count, f, args);		va_end(args);		if (m->count + len < m->size) {			m->count += len;			return 0;		}	}	m->count = m->size;	return -1;}EXPORT_SYMBOL(seq_printf); int seq_path(struct seq_file *m,	     struct vfsmount *mnt, struct dentry *dentry,	     char *esc){	if (m->count < m->size) {		char *s = m->buf + m->count;		char *p = d_path(dentry, mnt, s, m->size - m->count);		if (!IS_ERR(p)) {			while (s <= p) {				char c = *p++;				if (!c) {					p = m->buf + m->count;					m->count = s - m->buf;					return s - p;				} else if (!strchr(esc, c)) {					*s++ = c;				} else if (s + 4 > p) {					break;				} else {					*s++ = '\\';					*s++ = '0' + ((c & 0300) >> 6);					*s++ = '0' + ((c & 070) >> 3);					*s++ = '0' + (c & 07);				}			}		}	}	m->count = m->size;	return -1;}EXPORT_SYMBOL(seq_path); static void *single_start(struct seq_file *p, loff_t *pos){	return NULL + (*pos == 0);} static void *single_next(struct seq_file *p, void *v, loff_t *pos){	++*pos;	return NULL;} static void single_stop(struct seq_file *p, void *v){} int single_open(struct file *file, int (*show)(struct seq_file *, void *),		void *data){	struct seq_operations *op = kmalloc(sizeof(*op), GFP_KERNEL);	int res = -ENOMEM; 	if (op) {		op->start = single_start;		op->next = single_next;		op->stop = single_stop;		op->show = show;		res = seq_open(file, op);		if (!res)			((struct seq_file *)file->private_data)->private = data;		else			kfree(op);	}	return res;}EXPORT_SYMBOL(single_open); int single_release(struct inode *inode, struct file *file){	struct seq_operations *op = ((struct seq_file *)file->private_data)->op;	int res = seq_release(inode, file);	kfree(op);	return res;}EXPORT_SYMBOL(single_release); int seq_release_private(struct inode *inode, struct file *file){	struct seq_file *seq = file->private_data; 	kfree(seq->private);	seq->private = NULL;	return seq_release(inode, file);}EXPORT_SYMBOL(seq_release_private); int seq_putc(struct seq_file *m, char c){	if (m->count < m->size) {		m->buf[m->count++] = c;		return 0;	}	return -1;}EXPORT_SYMBOL(seq_putc); int seq_puts(struct seq_file *m, const char *s){	int len = strlen(s);	if (m->count + len < m->size) {		memcpy(m->buf + m->count, s, len);		m->count += len;		return 0;	}	m->count = m->size;	return -1;}EXPORT_SYMBOL(seq_puts);