libblkid: care about unsafe chars in cache

The high-level libblkid API uses /run/blkid/blkid.tab cache to
store probing results. The cache format is

   <device NAME="value" ...>devname</device>

and unfortunately the cache code does not escape quotation marks:

   # mkfs.ext4 -L 'AAA"BBB'

   # cat /run/blkid/blkid.tab
   ...
   <device ... LABEL="AAA"BBB" ...>/dev/sdb1</device>

such string is later incorrectly parsed and blkid(8) returns
nonsenses. And for use-cases like

   # eval $(blkid -o export /dev/sdb1)

it's also insecure.

Note that mount, udevd and blkid -p are based on low-level libblkid
API, it bypass the cache and directly read data from the devices.

The current udevd upstream does not depend on blkid(8) output at all,
it's directly linked with the library and all unsafe chars are encoded by
\x<hex> notation.

   # mkfs.ext4 -L 'X"`/tmp/foo` "' /dev/sdb1
   # udevadm info --export-db | grep LABEL
   ...
   E: ID_FS_LABEL=X__/tmp/foo___
   E: ID_FS_LABEL_ENC=X\x22\x60\x2ftmp\x2ffoo\x60\x20\x22

Signed-off-by: Karel Zak <kzak@redhat.com>
void blkid_read_cache(blkid_cache cache)
{
	FILE *file;
	char buf[4096];
	int fd, lineno = 0;
	struct stat st;

	if (!cache)
		return;

	/*
	 * If the file doesn't exist, then we just return an empty
	 * struct so that the cache can be populated.
	 */
	if ((fd = open(cache->bic_filename, O_RDONLY|O_CLOEXEC)) < 0)
		return;
	if (fstat(fd, &st) < 0)
		goto errout;
	if ((st.st_mtime == cache->bic_ftime) ||
	    (cache->bic_flags & BLKID_BIC_FL_CHANGED)) {
		DBG(CACHE, ul_debug("skipping re-read of %s",
					cache->bic_filename));
		goto errout;
	}

	DBG(CACHE, ul_debug("reading cache file %s",
				cache->bic_filename));

	file = fdopen(fd, "r" UL_CLOEXECSTR);
	if (!file)
		goto errout;

	while (fgets(buf, sizeof(buf), file)) {
		blkid_dev dev;
		unsigned int end;

		lineno++;
		if (buf[0] == 0)
			continue;
		end = strlen(buf) - 1;
		/* Continue reading next line if it ends with a backslash */
		while (end < (sizeof(buf) - 2) && buf[end] == '\\' &&
		       fgets(buf + end, sizeof(buf) - end, file)) {
			end = strlen(buf) - 1;
			lineno++;
		}

		if (blkid_parse_line(cache, &dev, buf) < 0) {
			DBG(READ, ul_debug("blkid: bad format on line %d", lineno));
			continue;
		}
	}
	fclose(file);

	/*
	 * Initially we do not need to write out the cache file.
	 */
	cache->bic_flags &= ~BLKID_BIC_FL_CHANGED;
	cache->bic_ftime = st.st_mtime;

	return;
errout:
	close(fd);
	return;
}

void blkid_read_cache(blkid_cache cache)
{
	FILE *file;
	char buf[4096];
	int fd, lineno = 0;
	struct stat st;

	if (!cache)
		return;

	/*
	 * If the file doesn't exist, then we just return an empty
	 * struct so that the cache can be populated.
	 */
	if ((fd = open(cache->bic_filename, O_RDONLY|O_CLOEXEC)) < 0)
		return;
	if (fstat(fd, &st) < 0)
		goto errout;
	if ((st.st_mtime == cache->bic_ftime) ||
	    (cache->bic_flags & BLKID_BIC_FL_CHANGED)) {
		DBG(CACHE, ul_debug("skipping re-read of %s",
					cache->bic_filename));
		goto errout;
	}

	DBG(CACHE, ul_debug("reading cache file %s",
				cache->bic_filename));

	file = fdopen(fd, "r" UL_CLOEXECSTR);
	if (!file)
		goto errout;

	while (fgets(buf, sizeof(buf), file)) {
		blkid_dev dev;
		unsigned int end;

		lineno++;
		if (buf[0] == 0)
			continue;
		end = strlen(buf) - 1;
		/* Continue reading next line if it ends with a backslash */
		while (end < (sizeof(buf) - 2) && buf[end] == '\\' &&
		       fgets(buf + end, sizeof(buf) - end, file)) {
			end = strlen(buf) - 1;
			lineno++;
		}

		if (blkid_parse_line(cache, &dev, buf) < 0) {
			DBG(READ, ul_debug("blkid: bad format on line %d", lineno));
			continue;
		}
	}
	fclose(file);

	/*
	 * Initially we do not need to write out the cache file.
	 */
	cache->bic_flags &= ~BLKID_BIC_FL_CHANGED;
	cache->bic_ftime = st.st_mtime;

	return;
errout:
	close(fd);
	return;
}
