summaryrefslogblamecommitdiffstats
path: root/shlibs/mount/src/tab_parse.c
blob: f8d6680fd299ac3857beff6cb33e606651a60f1b (plain) (tree)







































































































































































                                                                           

                              







                                              


                                                       
                         

                                      


































































































































































































































































































































































                                                                                                 
/*
 * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
 *
 * This file may be redistributed under the terms of the
 * GNU Lesser General Public License.
 */

#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <limits.h>

#include "nls.h"
#include "mountP.h"

static inline char *skip_spaces(char *s)
{
	assert(s);

	while (*s == ' ' || *s == '\t')
		s++;
	return s;
}

static inline char *skip_nonspaces(char *s)
{
	assert(s);

	while (*s && !(*s == ' ' || *s == '\t'))
		s++;
	return s;
}

#define isoctal(a) (((a) & ~7) == '0')

/* returns malloced pointer - no more strdup required */
static void unmangle(char *s, char *buf, size_t len)
{
	size_t sz = 0;
	assert(s);

	while(*s && sz < len - 1) {
		if (*s == '\\' && sz + 4 < len - 1 && isoctal(s[1]) &&
		    isoctal(s[2]) && isoctal(s[3])) {

			*buf++ = 64*(s[1] & 7) + 8*(s[2] & 7) + (s[3] & 7);
			s += 4;
			sz += 4;
		} else {
			*buf++ = *s++;
			sz++;
		}
	}
	*buf = '\0';
}

static size_t next_word_size(char *s, char **start, char **end)
{
	char *e;

	assert(s);

	s = skip_spaces(s);
	if (!*s)
		return 0;
	e = skip_nonspaces(s);

	if (start)
		*start = s;
	if (end)
		*end = e;

	return e - s;
}

static char *next_word(char **s)
{
	size_t sz;
	char *res, *end;

	assert(s);

	sz = next_word_size(*s, s, &end) + 1;
	if (sz == 1)
		return NULL;

	res = malloc(sz);
	if (!res)
		return NULL;

	unmangle(*s, res, sz);
	*s = end + 1;
	return res;
}

static int next_word_skip(char **s)
{
	*s = skip_spaces(*s);
	if (!**s)
		return 1;
	*s = skip_nonspaces(*s);
	return 0;
}

static int next_number(char **s, int *num)
{
	char *end = NULL;

	assert(num);
	assert(s);

	*s = skip_spaces(*s);
	if (!**s)
		return -1;
	*num = strtol(*s, &end, 10);
	if (end == NULL || *s == end)
	       return -1;

	*s = end;

	/* valid end of number is space or terminator */
	if (*end == ' ' || *end == '\t' || *end == '\0')
		return 0;
	return -1;
}

/*
 * Parses one line from {fs,m}tab
 */
static int mnt_tab_parse_file_line(mnt_fs *fs, char *s)
{
	/* SOURCE */
	if (__mnt_fs_set_source(fs, next_word(&s)) != 0)
		return 1;

	/* TARGET */
	fs->target = next_word(&s);
	if (!fs->target)
		return 1;

	/* TYPE */
	if (__mnt_fs_set_fstype(fs, next_word(&s)) != 0)
		return 1;

	/* OPTS */
	fs->optstr = next_word(&s);
	if (!fs->optstr)
		return 1;
	/* default */
	fs->passno = fs->freq = 0;

	/* FREQ (optional) */
	if (next_number(&s, &fs->freq) != 0) {
		if (*s)
			return 1;

	/* PASSNO (optional) */
	} else if (next_number(&s, &fs->passno) != 0 && *s)
		return 1;

	return 0;
}

/*
 * Parses one line from mountinfo file
 */
static int mnt_parse_mountinfo_line(mnt_fs *fs, char *s)
{
	unsigned int maj, min;

	/* ID */
	if (next_number(&s, &fs->id) != 0)
		return 1;

	/* PARENT */
	if (next_number(&s, &fs->parent) != 0)
		return 1;

	/* <maj>:<min> */
	s = skip_spaces(s);
	if (!*s || sscanf(s, "%u:%u", &maj, &min) != 2)
		return 1;
	fs->devno = makedev(maj, min);
	next_word_skip(&s);

	/* MOUNTROOT */
	fs->mntroot = next_word(&s);
	if (!fs->mntroot)
		return 1;

	/* TARGET (mountpoit) */
	fs->target = next_word(&s);
	if (!fs->target)
		return 1;

	/* OPTIONS (fs-independent) */
	fs->vfs_optstr = next_word(&s);
	if (!fs->vfs_optstr)
		return 1;

	/* optional fields (ignore) */
	do {
		s = skip_spaces(s);
		if (s && *s == '-' &&
		    (*(s + 1) == ' ' || *(s + 1) == '\t')) {
			s++;
			break;
		}
		if (s && next_word_skip(&s) != 0)
			return 1;
	} while (s);

	/* FSTYPE */
	if (__mnt_fs_set_fstype(fs, next_word(&s)) != 0)
		return 1;

	/* SOURCE or "none" */
	if (__mnt_fs_set_source(fs, next_word(&s)) != 0)
		return 1;

	/* OPTIONS (fs-dependent) */
	fs->fs_optstr = next_word(&s);
	if (!fs->fs_optstr)
		return 1;

	return 0;
}

/*
 * Returns {m,fs}tab or mountinfo file format (MNT_FMT_*)
 *
 * The "mountinfo" format is always: "<number> <number> ... "
 */
static int detect_fmt(char *line)
{
	int num;

	/* ID */
	if (next_number(&line, &num) != 0)
		return MNT_FMT_FSTAB;

	/* PARENT */
	if (next_number(&line, &num) != 0)
		return MNT_FMT_FSTAB;

	return MNT_FMT_MOUNTINFO;
}


/*
 * Merges @vfs and @fs options strings into a new string
 * This function skips the generic part of @fs options.
 * For example (see "rw"):
 *
 *    mnt_merge_optstr("rw,noexec", "rw,journal=update")
 *
 * returns --> "rw,noexec,journal=update"
 *
 * We need this function for /proc/self/mountinfo parsing.
 */
static char *merge_optstr(const char *vfs, const char *fs)
{
	const char *p1 = vfs, *p2 = fs;
	char *res;
	size_t sz;

	if (!vfs && !fs)
		return NULL;
	if (!vfs || !fs)
		return strdup(fs ? fs : vfs);
	if (!strcmp(vfs, fs))
		return strdup(vfs);		/* e.g. "aaa" and "aaa" */

	/* skip the same FS options */
	while (*p1 && *p2 && *++p1 == *++p2);

	if (*p1 == ',')
		p1++;
	if (*p2 == ',')
		p2++;
	if (!*p1 && !*p2)			/* e.g. "aaa,bbb" and "aaa,bbb," */
		return strdup(vfs);
	if (!*p1 || !*p2)			/* e.g. "aaa" and "aaa,bbb" */
		return strdup(*p1 ? vfs : fs);

	p1 = vfs;
	sz = strlen(p1) + strlen(p2) + 2;	/* 2= separator + '\0' */

	res = malloc(sz);
	if (!res)
		return NULL;

	snprintf(res, sz, "%s,%s", p1, p2);
	return res;
}

/*
 * Read and parse the next line from {fs,m}tab or mountinfo
 */
static int mnt_tab_parse_next(mnt_tab *tb, FILE *f, mnt_fs *fs)
{
	char buf[BUFSIZ];
	char *s;

	assert(tb);
	assert(f);
	assert(fs);

	/* read the next non-blank non-comment line */
	do {
		if (fgets(buf, sizeof(buf), f) == NULL)
			return -1;
		tb->nlines++;
		s = index (buf, '\n');
		if (!s) {
			/* Missing final newline?  Otherwise extremely */
			/* long line - assume file was corrupted */
			if (feof(f)) {
				DBG(DEBUG_TAB, fprintf(stderr,
					"libmount: WARNING: no final newline at the end of %s\n",
					tb->filename));
				s = index (buf, '\0');
			} else {
				DBG(DEBUG_TAB, fprintf(stderr,
					"libmount: %s: %d: missing newline at line\n",
					tb->filename, tb->nlines));
				goto err;
			}
		}
		*s = '\0';
		if (--s >= buf && *s == '\r')
			*s = '\0';
		s = skip_spaces(buf);
	} while (*s == '\0' || *s == '#');

	DBG(DEBUG_TAB, fprintf(stderr, "libmount: %s:%d: %s\n",
		tb->filename, tb->nlines, s));

	if (!tb->fmt)
		tb->fmt = detect_fmt(s);

	if (tb->fmt == MNT_FMT_FSTAB) {
		/* parse /etc/{fs,m}tab */
		if (mnt_tab_parse_file_line(fs, s) != 0)
			goto err;
	} else if (tb->fmt == MNT_FMT_MOUNTINFO) {
		/* parse /proc/self/mountinfo */
		if (mnt_parse_mountinfo_line(fs, s) != 0)
			goto err;
	}

	/* merge fs_optstr and vfs_optstr into optstr (necessary for "mountinfo") */
	if (!fs->optstr && (fs->vfs_optstr || fs->fs_optstr)) {
		fs->optstr = merge_optstr(fs->vfs_optstr, fs->fs_optstr);
		if (!fs->optstr)
			goto err;
	}

	fs->lineno = tb->nlines;

	DBG(DEBUG_TAB, fprintf(stderr,
		"libmount: %s: %d: SOURCE:%s, MNTPOINT:%s, TYPE:%s, "
				  "OPTS:%s, FREQ:%d, PASSNO:%d\n",
		tb->filename, fs->lineno,
		fs->source, fs->target, fs->fstype,
		fs->optstr, fs->freq, fs->passno));

	return 0;
err:
	/* we don't report parse errors to caller; caller has to check
	 * errors by mnt_tab_get_nerrs() or internaly by MNT_ENTRY_ERR flag
	 */
	fs->lineno = tb->nlines;
	fs->flags |= MNT_FS_ERROR;
	return 0;
}

/**
 * mnt_tab_parse_file:
 * @tb: tab pointer
 *
 * Parses whole table (e.g. /etc/fstab).
 *
 * Returns 0 on success and -1 in case of error. The parse errors is possible
 * to detect by mnt_tab_get_nerrs() and error message is possible to create by
 * mnt_tab_strerror().
 *
 * Example:
 *
 *	mnt_tab *tb = mnt_new_tab("/etc/fstab");
 *	int rc;
 *
 *	rc = mnt_tab_parse_file(tb);
 *	if (rc) {
 *		if (mnt_tab_get_nerrs(tb)) {             / * parse error * /
 *			mnt_tab_strerror(tb, buf, sizeof(buf));
 *			fprintf(stderr, "%s: %s\n", progname, buf);
 *		} else
 *			perror(mnt_tab_get_name(tb));  / * system error * /
 *	} else
 *		mnt_fprintf_tab(tb, stdout, NULL);
 *
 *	mnt_free_tab(tb);
 */
int mnt_tab_parse_file(mnt_tab *tb)
{
	FILE *f;

	assert(tb);
	assert(tb->filename);

	if (!tb->filename)
		return -1;

	f = fopen(tb->filename, "r");
	if (!f)
		return -1;

	while (!feof(f)) {
		int rc;
		mnt_fs *fs = mnt_new_fs();
		if (!fs)
			goto error;

		rc = mnt_tab_parse_next(tb, f, fs);
		if (!rc)
			rc = mnt_tab_add_fs(tb, fs);
		else if (feof(f)) {
			mnt_free_fs(fs);
			break;
		}
		if (rc) {
			mnt_free_fs(fs);
			goto error;
		}
	}

	fclose(f);
	return 0;
error:
	fclose(f);
	return -1;
}

/**
 * mnt_new_tab_parse:
 * @filename: /etc/{m,fs}tab or /proc/self/mountinfo path
 *
 * Same as mnt_new_tab() + mnt_tab_parse_file(). Note that this function does
 * not provide details (by mnt_tab_strerror()) about failed parsing -- so you
 * should not to use this function for user-writeable files like /etc/fstab.
 *
 * Returns newly allocated tab on success and NULL in case of error.
 */
mnt_tab *mnt_new_tab_from_file(const char *filename)
{
	mnt_tab *tb;

	assert(filename);

	if (!filename)
		return NULL;
	tb = mnt_new_tab(filename);
	if (tb && mnt_tab_parse_file(tb) != 0) {
		mnt_free_tab(tb);
		tb = NULL;
	}
	return tb;
}

/**
 * mnt_tab_get_nerrs:
 * @tb: pointer to table
 *
 * Returns number of broken (parse error) entries in the table.
 */
int mnt_tab_get_nerrs(mnt_tab *tb)
{
	assert(tb);
	return tb->nerrs;
}

/**
 * mnt_tab_strerror:
 * @tb: pointer to table
 * @buf: buffer to return error message
 * @buflen: lenght of the buf
 *
 * Returns error message for table (file) parse errors. For example:
 *
 *	"/etc/fstab: parse error at line(s): 1, 2 and 3."
 */
char *mnt_tab_strerror(mnt_tab *tb, char *buf, size_t buflen)
{
	struct list_head *p;
	int last = -1;
	char *b = buf;
	char *end = buf + buflen - 1;

	assert(tb);
	assert(buf);
	assert(buflen);

	if (!tb || !tb->nerrs || !buf || buflen <=0)
		return NULL;

	if (tb->filename) {
		snprintf(b, end - b, "%s: ", tb->filename);
		b += strnlen(b, end - b);
	}

	if (tb->nerrs > 1)
		strncpy(b, _("parse error at lines: "), end - b);
	else
		strncpy(b, _("parse error at line: "), end - b);
	b += strnlen(b, end - b);
	*b = '\0';

	list_for_each(p, &tb->ents) {
		mnt_fs *fs = list_entry(p, mnt_fs, ents);
		if (b == end)
			goto done;
		if (fs->flags & MNT_FS_ERROR) {
			if (last != -1) {
				snprintf(b, end - b, "%d, ", last);
				b += strnlen(b, end - b);
			}
			last = fs->lineno;
		}
	}

	if (tb->nerrs == 1)
		snprintf(b, end - b, "%d.", last);
	else
		snprintf(b - 1, end - b, _(" and %d."), last);
done:
	return buf;
}