995 lines · 21.7 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/export.h>#include <linux/seq_file.h>#include <linux/slab.h>#include <linux/cred.h> #include <asm/uaccess.h>#include <asm/page.h>  /* * seq_files have a buffer which can may overflow. When this happens a larger * buffer is reallocated and all the data will be printed again. * The overflow state is true when m->count == m->size. */static bool seq_overflow(struct seq_file *m){	return m->count == m->size;} static void seq_set_overflow(struct seq_file *m){	m->count = m->size;} /** *	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. *	Returning SEQ_SKIP means "discard this element and move on". */int seq_open(struct file *file, const 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;#ifdef CONFIG_USER_NS	p->user_ns = file->f_cred->user_ns;#endif 	/*	 * 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() and pread().  They do not implement	 * write() at all, but we clear FMODE_PWRITE here for historical	 * reasons.	 *	 * If a client of seq_files a) implements file.write() and b) wishes to	 * support pwrite() then that client will need to implement its own	 * file.open() which calls seq_open() and then sets FMODE_PWRITE.	 */	file->f_mode &= ~FMODE_PWRITE;	return 0;}EXPORT_SYMBOL(seq_open); static int traverse(struct seq_file *m, loff_t offset){	loff_t pos = 0, index;	int error = 0;	void *p; 	m->version = 0;	index = 0;	m->count = m->from = 0;	if (!offset) {		m->index = index;		return 0;	}	if (!m->buf) {		m->buf = kmalloc(m->size = PAGE_SIZE, GFP_KERNEL);		if (!m->buf)			return -ENOMEM;	}	p = m->op->start(m, &index);	while (p) {		error = PTR_ERR(p);		if (IS_ERR(p))			break;		error = m->op->show(m, p);		if (error < 0)			break;		if (unlikely(error)) {			error = 0;			m->count = 0;		}		if (seq_overflow(m))			goto Eoverflow;		if (pos + m->count > offset) {			m->from = offset - pos;			m->count -= m->from;			m->index = index;			break;		}		pos += m->count;		m->count = 0;		if (pos == offset) {			index++;			m->index = index;			break;		}		p = m->op->next(m, p, &index);	}	m->op->stop(m, p);	m->index = index;	return error; Eoverflow:	m->op->stop(m, p);	kfree(m->buf);	m->count = 0;	m->buf = kmalloc(m->size <<= 1, GFP_KERNEL);	return !m->buf ? -ENOMEM : -EAGAIN;} /** *	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 = 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; 	/* Don't assume *ppos is where we left it */	if (unlikely(*ppos != m->read_pos)) {		while ((err = traverse(m, *ppos)) == -EAGAIN)			;		if (err) {			/* With prejudice... */			m->read_pos = 0;			m->version = 0;			m->index = 0;			m->count = 0;			goto Done;		} else {			m->read_pos = *ppos;		}	} 	/* 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 */	pos = m->index;	p = m->op->start(m, &pos);	while (1) {		err = PTR_ERR(p);		if (!p || IS_ERR(p))			break;		err = m->op->show(m, p);		if (err < 0)			break;		if (unlikely(err))			m->count = 0;		if (unlikely(!m->count)) {			p = m->op->next(m, p, &pos);			m->index = pos;			continue;		}		if (m->count < m->size)			goto Fill;		m->op->stop(m, p);		kfree(m->buf);		m->count = 0;		m->buf = kmalloc(m->size <<= 1, GFP_KERNEL);		if (!m->buf)			goto Enomem;		m->version = 0;		pos = m->index;		p = m->op->start(m, &pos);	}	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 (seq_overflow(m) || err) {			m->count = offs;			if (likely(err <= 0))				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;		m->read_pos += 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); /** *	seq_lseek -	->llseek() method for sequential files. *	@file: the file in question *	@offset: new position *	@whence: 0 for absolute, 1 for relative position * *	Ready-made ->f_op->llseek() */loff_t seq_lseek(struct file *file, loff_t offset, int whence){	struct seq_file *m = file->private_data;	loff_t retval = -EINVAL; 	mutex_lock(&m->lock);	m->version = file->f_version;	switch (whence) {	case SEEK_CUR:		offset += file->f_pos;	case SEEK_SET:		if (offset < 0)			break;		retval = offset;		if (offset != m->read_pos) {			while ((retval = traverse(m, offset)) == -EAGAIN)				;			if (retval) {				/* with extreme prejudice... */				file->f_pos = 0;				m->read_pos = 0;				m->version = 0;				m->index = 0;				m->count = 0;			} else {				m->read_pos = offset;				retval = file->f_pos = offset;			}		} else {			file->f_pos = offset;		}	}	file->f_version = m->version;	mutex_unlock(&m->lock);	return retval;}EXPORT_SYMBOL(seq_lseek); /** *	seq_release -	free the structures associated with sequential file. *	@file: file in question *	@inode: its 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 = 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;		}		seq_set_overflow(m);		return -1;        }	m->count = p - m->buf;        return 0;}EXPORT_SYMBOL(seq_escape); int seq_vprintf(struct seq_file *m, const char *f, va_list args){	int len; 	if (m->count < m->size) {		len = vsnprintf(m->buf + m->count, m->size - m->count, f, args);		if (m->count + len < m->size) {			m->count += len;			return 0;		}	}	seq_set_overflow(m);	return -1;}EXPORT_SYMBOL(seq_vprintf); int seq_printf(struct seq_file *m, const char *f, ...){	int ret;	va_list args; 	va_start(args, f);	ret = seq_vprintf(m, f, args);	va_end(args); 	return ret;}EXPORT_SYMBOL(seq_printf); /** *	mangle_path -	mangle and copy path to buffer beginning *	@s: buffer start *	@p: beginning of path in above buffer *	@esc: set of characters that need escaping * *      Copy the path from @p to @s, replacing each occurrence of character from *      @esc with usual octal escape. *      Returns pointer past last written character in @s, or NULL in case of *      failure. */char *mangle_path(char *s, const char *p, const char *esc){	while (s <= p) {		char c = *p++;		if (!c) {			return s;		} 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);		}	}	return NULL;}EXPORT_SYMBOL(mangle_path); /** * seq_path - seq_file interface to print a pathname * @m: the seq_file handle * @path: the struct path to print * @esc: set of characters to escape in the output * * return the absolute path of 'path', as represented by the * dentry / mnt pair in the path parameter. */int seq_path(struct seq_file *m, const struct path *path, const char *esc){	char *buf;	size_t size = seq_get_buf(m, &buf);	int res = -1; 	if (size) {		char *p = d_path(path, buf, size);		if (!IS_ERR(p)) {			char *end = mangle_path(buf, p, esc);			if (end)				res = end - buf;		}	}	seq_commit(m, res); 	return res;}EXPORT_SYMBOL(seq_path); /* * Same as seq_path, but relative to supplied root. */int seq_path_root(struct seq_file *m, const struct path *path,		  const struct path *root, const char *esc){	char *buf;	size_t size = seq_get_buf(m, &buf);	int res = -ENAMETOOLONG; 	if (size) {		char *p; 		p = __d_path(path, root, buf, size);		if (!p)			return SEQ_SKIP;

Lines 1–500 of 995.