162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/* RomFS storage access routines
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * Copyright © 2007 Red Hat, Inc. All Rights Reserved.
562306a36Sopenharmony_ci * Written by David Howells (dhowells@redhat.com)
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/fs.h>
962306a36Sopenharmony_ci#include <linux/mtd/super.h>
1062306a36Sopenharmony_ci#include <linux/buffer_head.h>
1162306a36Sopenharmony_ci#include "internal.h"
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#if !defined(CONFIG_ROMFS_ON_MTD) && !defined(CONFIG_ROMFS_ON_BLOCK)
1462306a36Sopenharmony_ci#error no ROMFS backing store interface configured
1562306a36Sopenharmony_ci#endif
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#ifdef CONFIG_ROMFS_ON_MTD
1862306a36Sopenharmony_ci#define ROMFS_MTD_READ(sb, ...) mtd_read((sb)->s_mtd, ##__VA_ARGS__)
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci/*
2162306a36Sopenharmony_ci * read data from an romfs image on an MTD device
2262306a36Sopenharmony_ci */
2362306a36Sopenharmony_cistatic int romfs_mtd_read(struct super_block *sb, unsigned long pos,
2462306a36Sopenharmony_ci			  void *buf, size_t buflen)
2562306a36Sopenharmony_ci{
2662306a36Sopenharmony_ci	size_t rlen;
2762306a36Sopenharmony_ci	int ret;
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci	ret = ROMFS_MTD_READ(sb, pos, buflen, &rlen, buf);
3062306a36Sopenharmony_ci	return (ret < 0 || rlen != buflen) ? -EIO : 0;
3162306a36Sopenharmony_ci}
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci/*
3462306a36Sopenharmony_ci * determine the length of a string in a romfs image on an MTD device
3562306a36Sopenharmony_ci */
3662306a36Sopenharmony_cistatic ssize_t romfs_mtd_strnlen(struct super_block *sb,
3762306a36Sopenharmony_ci				 unsigned long pos, size_t maxlen)
3862306a36Sopenharmony_ci{
3962306a36Sopenharmony_ci	ssize_t n = 0;
4062306a36Sopenharmony_ci	size_t segment;
4162306a36Sopenharmony_ci	u_char buf[16], *p;
4262306a36Sopenharmony_ci	size_t len;
4362306a36Sopenharmony_ci	int ret;
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci	/* scan the string up to 16 bytes at a time */
4662306a36Sopenharmony_ci	while (maxlen > 0) {
4762306a36Sopenharmony_ci		segment = min_t(size_t, maxlen, 16);
4862306a36Sopenharmony_ci		ret = ROMFS_MTD_READ(sb, pos, segment, &len, buf);
4962306a36Sopenharmony_ci		if (ret < 0)
5062306a36Sopenharmony_ci			return ret;
5162306a36Sopenharmony_ci		p = memchr(buf, 0, len);
5262306a36Sopenharmony_ci		if (p)
5362306a36Sopenharmony_ci			return n + (p - buf);
5462306a36Sopenharmony_ci		maxlen -= len;
5562306a36Sopenharmony_ci		pos += len;
5662306a36Sopenharmony_ci		n += len;
5762306a36Sopenharmony_ci	}
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	return n;
6062306a36Sopenharmony_ci}
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci/*
6362306a36Sopenharmony_ci * compare a string to one in a romfs image on MTD
6462306a36Sopenharmony_ci * - return 1 if matched, 0 if differ, -ve if error
6562306a36Sopenharmony_ci */
6662306a36Sopenharmony_cistatic int romfs_mtd_strcmp(struct super_block *sb, unsigned long pos,
6762306a36Sopenharmony_ci			    const char *str, size_t size)
6862306a36Sopenharmony_ci{
6962306a36Sopenharmony_ci	u_char buf[17];
7062306a36Sopenharmony_ci	size_t len, segment;
7162306a36Sopenharmony_ci	int ret;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	/* scan the string up to 16 bytes at a time, and attempt to grab the
7462306a36Sopenharmony_ci	 * trailing NUL whilst we're at it */
7562306a36Sopenharmony_ci	buf[0] = 0xff;
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	while (size > 0) {
7862306a36Sopenharmony_ci		segment = min_t(size_t, size + 1, 17);
7962306a36Sopenharmony_ci		ret = ROMFS_MTD_READ(sb, pos, segment, &len, buf);
8062306a36Sopenharmony_ci		if (ret < 0)
8162306a36Sopenharmony_ci			return ret;
8262306a36Sopenharmony_ci		len--;
8362306a36Sopenharmony_ci		if (memcmp(buf, str, len) != 0)
8462306a36Sopenharmony_ci			return 0;
8562306a36Sopenharmony_ci		buf[0] = buf[len];
8662306a36Sopenharmony_ci		size -= len;
8762306a36Sopenharmony_ci		pos += len;
8862306a36Sopenharmony_ci		str += len;
8962306a36Sopenharmony_ci	}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	/* check the trailing NUL was */
9262306a36Sopenharmony_ci	if (buf[0])
9362306a36Sopenharmony_ci		return 0;
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	return 1;
9662306a36Sopenharmony_ci}
9762306a36Sopenharmony_ci#endif /* CONFIG_ROMFS_ON_MTD */
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci#ifdef CONFIG_ROMFS_ON_BLOCK
10062306a36Sopenharmony_ci/*
10162306a36Sopenharmony_ci * read data from an romfs image on a block device
10262306a36Sopenharmony_ci */
10362306a36Sopenharmony_cistatic int romfs_blk_read(struct super_block *sb, unsigned long pos,
10462306a36Sopenharmony_ci			  void *buf, size_t buflen)
10562306a36Sopenharmony_ci{
10662306a36Sopenharmony_ci	struct buffer_head *bh;
10762306a36Sopenharmony_ci	unsigned long offset;
10862306a36Sopenharmony_ci	size_t segment;
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	/* copy the string up to blocksize bytes at a time */
11162306a36Sopenharmony_ci	while (buflen > 0) {
11262306a36Sopenharmony_ci		offset = pos & (ROMBSIZE - 1);
11362306a36Sopenharmony_ci		segment = min_t(size_t, buflen, ROMBSIZE - offset);
11462306a36Sopenharmony_ci		bh = sb_bread(sb, pos >> ROMBSBITS);
11562306a36Sopenharmony_ci		if (!bh)
11662306a36Sopenharmony_ci			return -EIO;
11762306a36Sopenharmony_ci		memcpy(buf, bh->b_data + offset, segment);
11862306a36Sopenharmony_ci		brelse(bh);
11962306a36Sopenharmony_ci		buf += segment;
12062306a36Sopenharmony_ci		buflen -= segment;
12162306a36Sopenharmony_ci		pos += segment;
12262306a36Sopenharmony_ci	}
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	return 0;
12562306a36Sopenharmony_ci}
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci/*
12862306a36Sopenharmony_ci * determine the length of a string in romfs on a block device
12962306a36Sopenharmony_ci */
13062306a36Sopenharmony_cistatic ssize_t romfs_blk_strnlen(struct super_block *sb,
13162306a36Sopenharmony_ci				 unsigned long pos, size_t limit)
13262306a36Sopenharmony_ci{
13362306a36Sopenharmony_ci	struct buffer_head *bh;
13462306a36Sopenharmony_ci	unsigned long offset;
13562306a36Sopenharmony_ci	ssize_t n = 0;
13662306a36Sopenharmony_ci	size_t segment;
13762306a36Sopenharmony_ci	u_char *buf, *p;
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	/* scan the string up to blocksize bytes at a time */
14062306a36Sopenharmony_ci	while (limit > 0) {
14162306a36Sopenharmony_ci		offset = pos & (ROMBSIZE - 1);
14262306a36Sopenharmony_ci		segment = min_t(size_t, limit, ROMBSIZE - offset);
14362306a36Sopenharmony_ci		bh = sb_bread(sb, pos >> ROMBSBITS);
14462306a36Sopenharmony_ci		if (!bh)
14562306a36Sopenharmony_ci			return -EIO;
14662306a36Sopenharmony_ci		buf = bh->b_data + offset;
14762306a36Sopenharmony_ci		p = memchr(buf, 0, segment);
14862306a36Sopenharmony_ci		brelse(bh);
14962306a36Sopenharmony_ci		if (p)
15062306a36Sopenharmony_ci			return n + (p - buf);
15162306a36Sopenharmony_ci		limit -= segment;
15262306a36Sopenharmony_ci		pos += segment;
15362306a36Sopenharmony_ci		n += segment;
15462306a36Sopenharmony_ci	}
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	return n;
15762306a36Sopenharmony_ci}
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci/*
16062306a36Sopenharmony_ci * compare a string to one in a romfs image on a block device
16162306a36Sopenharmony_ci * - return 1 if matched, 0 if differ, -ve if error
16262306a36Sopenharmony_ci */
16362306a36Sopenharmony_cistatic int romfs_blk_strcmp(struct super_block *sb, unsigned long pos,
16462306a36Sopenharmony_ci			    const char *str, size_t size)
16562306a36Sopenharmony_ci{
16662306a36Sopenharmony_ci	struct buffer_head *bh;
16762306a36Sopenharmony_ci	unsigned long offset;
16862306a36Sopenharmony_ci	size_t segment;
16962306a36Sopenharmony_ci	bool matched, terminated = false;
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	/* compare string up to a block at a time */
17262306a36Sopenharmony_ci	while (size > 0) {
17362306a36Sopenharmony_ci		offset = pos & (ROMBSIZE - 1);
17462306a36Sopenharmony_ci		segment = min_t(size_t, size, ROMBSIZE - offset);
17562306a36Sopenharmony_ci		bh = sb_bread(sb, pos >> ROMBSBITS);
17662306a36Sopenharmony_ci		if (!bh)
17762306a36Sopenharmony_ci			return -EIO;
17862306a36Sopenharmony_ci		matched = (memcmp(bh->b_data + offset, str, segment) == 0);
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci		size -= segment;
18162306a36Sopenharmony_ci		pos += segment;
18262306a36Sopenharmony_ci		str += segment;
18362306a36Sopenharmony_ci		if (matched && size == 0 && offset + segment < ROMBSIZE) {
18462306a36Sopenharmony_ci			if (!bh->b_data[offset + segment])
18562306a36Sopenharmony_ci				terminated = true;
18662306a36Sopenharmony_ci			else
18762306a36Sopenharmony_ci				matched = false;
18862306a36Sopenharmony_ci		}
18962306a36Sopenharmony_ci		brelse(bh);
19062306a36Sopenharmony_ci		if (!matched)
19162306a36Sopenharmony_ci			return 0;
19262306a36Sopenharmony_ci	}
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	if (!terminated) {
19562306a36Sopenharmony_ci		/* the terminating NUL must be on the first byte of the next
19662306a36Sopenharmony_ci		 * block */
19762306a36Sopenharmony_ci		BUG_ON((pos & (ROMBSIZE - 1)) != 0);
19862306a36Sopenharmony_ci		bh = sb_bread(sb, pos >> ROMBSBITS);
19962306a36Sopenharmony_ci		if (!bh)
20062306a36Sopenharmony_ci			return -EIO;
20162306a36Sopenharmony_ci		matched = !bh->b_data[0];
20262306a36Sopenharmony_ci		brelse(bh);
20362306a36Sopenharmony_ci		if (!matched)
20462306a36Sopenharmony_ci			return 0;
20562306a36Sopenharmony_ci	}
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci	return 1;
20862306a36Sopenharmony_ci}
20962306a36Sopenharmony_ci#endif /* CONFIG_ROMFS_ON_BLOCK */
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci/*
21262306a36Sopenharmony_ci * read data from the romfs image
21362306a36Sopenharmony_ci */
21462306a36Sopenharmony_ciint romfs_dev_read(struct super_block *sb, unsigned long pos,
21562306a36Sopenharmony_ci		   void *buf, size_t buflen)
21662306a36Sopenharmony_ci{
21762306a36Sopenharmony_ci	size_t limit;
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci	limit = romfs_maxsize(sb);
22062306a36Sopenharmony_ci	if (pos >= limit || buflen > limit - pos)
22162306a36Sopenharmony_ci		return -EIO;
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci#ifdef CONFIG_ROMFS_ON_MTD
22462306a36Sopenharmony_ci	if (sb->s_mtd)
22562306a36Sopenharmony_ci		return romfs_mtd_read(sb, pos, buf, buflen);
22662306a36Sopenharmony_ci#endif
22762306a36Sopenharmony_ci#ifdef CONFIG_ROMFS_ON_BLOCK
22862306a36Sopenharmony_ci	if (sb->s_bdev)
22962306a36Sopenharmony_ci		return romfs_blk_read(sb, pos, buf, buflen);
23062306a36Sopenharmony_ci#endif
23162306a36Sopenharmony_ci	return -EIO;
23262306a36Sopenharmony_ci}
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci/*
23562306a36Sopenharmony_ci * determine the length of a string in romfs
23662306a36Sopenharmony_ci */
23762306a36Sopenharmony_cissize_t romfs_dev_strnlen(struct super_block *sb,
23862306a36Sopenharmony_ci			  unsigned long pos, size_t maxlen)
23962306a36Sopenharmony_ci{
24062306a36Sopenharmony_ci	size_t limit;
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	limit = romfs_maxsize(sb);
24362306a36Sopenharmony_ci	if (pos >= limit)
24462306a36Sopenharmony_ci		return -EIO;
24562306a36Sopenharmony_ci	if (maxlen > limit - pos)
24662306a36Sopenharmony_ci		maxlen = limit - pos;
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci#ifdef CONFIG_ROMFS_ON_MTD
24962306a36Sopenharmony_ci	if (sb->s_mtd)
25062306a36Sopenharmony_ci		return romfs_mtd_strnlen(sb, pos, maxlen);
25162306a36Sopenharmony_ci#endif
25262306a36Sopenharmony_ci#ifdef CONFIG_ROMFS_ON_BLOCK
25362306a36Sopenharmony_ci	if (sb->s_bdev)
25462306a36Sopenharmony_ci		return romfs_blk_strnlen(sb, pos, maxlen);
25562306a36Sopenharmony_ci#endif
25662306a36Sopenharmony_ci	return -EIO;
25762306a36Sopenharmony_ci}
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci/*
26062306a36Sopenharmony_ci * compare a string to one in romfs
26162306a36Sopenharmony_ci * - the string to be compared to, str, may not be NUL-terminated; instead the
26262306a36Sopenharmony_ci *   string is of the specified size
26362306a36Sopenharmony_ci * - return 1 if matched, 0 if differ, -ve if error
26462306a36Sopenharmony_ci */
26562306a36Sopenharmony_ciint romfs_dev_strcmp(struct super_block *sb, unsigned long pos,
26662306a36Sopenharmony_ci		     const char *str, size_t size)
26762306a36Sopenharmony_ci{
26862306a36Sopenharmony_ci	size_t limit;
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci	limit = romfs_maxsize(sb);
27162306a36Sopenharmony_ci	if (pos >= limit)
27262306a36Sopenharmony_ci		return -EIO;
27362306a36Sopenharmony_ci	if (size > ROMFS_MAXFN)
27462306a36Sopenharmony_ci		return -ENAMETOOLONG;
27562306a36Sopenharmony_ci	if (size + 1 > limit - pos)
27662306a36Sopenharmony_ci		return -EIO;
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci#ifdef CONFIG_ROMFS_ON_MTD
27962306a36Sopenharmony_ci	if (sb->s_mtd)
28062306a36Sopenharmony_ci		return romfs_mtd_strcmp(sb, pos, str, size);
28162306a36Sopenharmony_ci#endif
28262306a36Sopenharmony_ci#ifdef CONFIG_ROMFS_ON_BLOCK
28362306a36Sopenharmony_ci	if (sb->s_bdev)
28462306a36Sopenharmony_ci		return romfs_blk_strcmp(sb, pos, str, size);
28562306a36Sopenharmony_ci#endif
28662306a36Sopenharmony_ci	return -EIO;
28762306a36Sopenharmony_ci}
288