Subject: Re: RFC: lseek() extension for sparse files version 3 + UFS implementation
To: Reinoud Zandijk <reinoud@netbsd.org>
From: Darrin B.Jewell <dbj@netbsd.org>
List: tech-kern
Date: 09/26/2006 22:42:51
--=-=-=


The following program is something I whipped up to create pathological
sparse files for testing ffs.  Perhaps it will be useful.

Darrin


--=-=-=
Content-Disposition: attachment; filename=sparseffs.c
Content-Description: program to create pathological sparse files on ffs


/*
 * create a pathological sparse file on ufs
 *
 * One block is allocated per indirect block, and the allocation
 * is performed in a rather pessimal order.
 *
 * compile with a value for -D BLOCKSIZE to match the layout of the
 *    filesystem under test.  Default is 16384.  Note that the
 *    fragment size does not affect this program.
 * 
 * compile with -D UFS2 to set NINDIR appropriate for ufs2,
 *   otherwise the default is appropriate for ufs1
 *
 * compile with -D NOIO to skip doing any actual io
 *
 * for example, to create maximum sized file named foo
 *   sparseffs foo
 * or to create a file at most 1000 bytes size
 *   sparseffs foo 1000
 * or to create a maximum length file while printing debug info
 *   sparseffs foo -1 DEBUG
 *
 * Darrin B. Jewell <dbj@netbsd.org> 2006-09-26T19:51:17-0700
 */

#define HAVE_PWRITE
#define HAVE_ERR
#define HAVE_GETPROGNAME
//#define HAVE_LLSEEK

#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <stdio.h>
#include <inttypes.h>
#include <fcntl.h>
#include <unistd.h>

#ifdef HAVE_ERR
#include <err.h>
#else
#include <errno.h>
#define err(x, s, ...) (printf(s ": %s\n", ##__VA_ARGS__, strerror(errno)), exit(x))
#define errx(x, s, ...) (printf(s "\n" , ##__VA_ARGS__), exit(x))
#define warnx(s, ...) (printf(s "\n" , ##__VA_ARGS__))
#endif

#ifndef HAVE_GETPROGNAME
#define getprogname() (argv[0])
#endif

#ifndef BLOCKSIZE
#define BLOCKSIZE 16384 /* default if filesystem is >= 2048000 bytes */
#endif

/* 4 for ufs1, 8 for ufs2 */
#ifndef DADDRSIZE
#define DADDRSIZE 4 /* for ufs1 */
#endif

#ifndef NINDIR
#ifdef UFS2
#define NINDIR (BLOCKSIZE/8)
#else
#define NINDIR (BLOCKSIZE/4)
#endif
#endif

#define NDADDR 12
#define NIADDR 3

int debug;

void
writebyte(int fd, int64_t off)
{
#ifdef HAVE_PWRITE
	{
		ssize_t w;
		w = pwrite(fd, "\0", 1, off);
		if (w != 1)
			err(EXIT_FAILURE, "pwrite 1 byte at 0x%"PRIxMAX" returned %"PRIdMAX,
			    (intmax_t)off, (intmax_t)w);
	}
#else
	{
		ssize_t w;

#ifdef HAVE_LLSEEK
		offset_t l = off;
		offset_t r;
		r = lseek(fd, l, SEEK_SET);
		if (r != l)
			err(EXIT_FAILURE, "llseek(0x%"PRIxMAX") returned 0x%" PRIxMAX,
			    (intmax_t)l, (intmax_t)r);
#else
		off_t l = off;
		off_t r;
		r = lseek(fd, l, SEEK_SET);
		if (r != l)
			err(EXIT_FAILURE, "lseek(0x%"PRIxMAX") returned 0x%" PRIxMAX,
			    (intmax_t)l, (intmax_t)r);
#endif

		w = write(fd, "\0", 1);
		if (w != 1)
			err(EXIT_FAILURE, "write 1 byte at 0x%"PRIxMAX" returned %"PRIdMAX,
			    (intmax_t)off, (intmax_t)w);
	}
#endif
}

int
main(int argc, char *argv[])
{
	int64_t len;
	const char *name;
	int i, j, k;
#ifndef NOIO
	int fd;
	int error;
#endif

	if (argc < 2)
		errx(EXIT_FAILURE, "usage: %s filename maxlen DEBUG", getprogname());

	debug = argc > 3;

	name = argv[1];

	len = -1;
	if (argc > 2)
		len = strtoll(argv[2], NULL, 0);

#ifdef HAVE_LLSEEK
	if (sizeof(offset_t) < sizeof(int64_t))
		warnx("Warning: sizeof(offset_t) == %d < sizeof(int64_t) == %d",
		    sizeof(offset_t), sizeof(int64_t));
#else
	if (sizeof(off_t) < sizeof(int64_t))
		warnx("Warning: sizeof(off_t) == %d < sizeof(int64_t) == %d",
		    sizeof(off_t), sizeof(int64_t));
#endif

	fprintf(stderr, "BLOCKSIZE = %d\n", BLOCKSIZE);
	fprintf(stderr, "NINDIR    = %d\n", NINDIR);
	
	int64_t sizes[NIADDR+1];    /* number of blocks indirect block level */
	sizes[0] = 1;
	for (i=1; i < sizeof(sizes)/sizeof(sizes[0]); i++) {
		sizes[i] = sizes[i-1]*NINDIR;
	}
	int64_t maxsizes[NIADDR+1]; /* total number of blocks at indirect block level */
	maxsizes[0] = NDADDR * sizes[0];
	for (i=1; i < sizeof(maxsizes)/sizeof(maxsizes[0]); i++) {
		maxsizes[i] = maxsizes[i-1] + sizes[i];
	}
	int64_t indirs[NIADDR+1]; /* number of indirect blocks for each level */
	indirs[0] = 0;
	for (i=1; i < sizeof(indirs)/sizeof(indirs[0]); i++) {
		indirs[i] = (indirs[i-1]*NINDIR)+1;
	}
	int64_t maxindirs[NIADDR+1]; /* total number of indirect blocks for each level */
	maxindirs[0] = 0;
	for (i=1; i < sizeof(maxindirs)/sizeof(maxindirs[0]); i++) {
		maxindirs[i] = maxindirs[i-1] + indirs[i];
	}

	for (i=0; i < sizeof(sizes)/sizeof(sizes[0]); i++) {
		fprintf(stderr, "sizes[%d] = %"PRIu64" blocks,  %"PRIu64" bytes\n", i, sizes[i], sizes[i]*BLOCKSIZE);
	}
	for (i=0; i < sizeof(maxsizes)/sizeof(maxsizes[0]); i++) {
		fprintf(stderr, "maxsizes[%d] = %"PRIu64" blocks,  %"PRIu64" bytes\n", i, maxsizes[i], maxsizes[i]*BLOCKSIZE);
	}
	for (i=0; i < sizeof(indirs)/sizeof(indirs[0]); i++) {
		fprintf(stderr, "indirs[%d] = %"PRIu64" indirs,  %"PRIu64" bytes\n", i, indirs[i], indirs[i]*BLOCKSIZE);
	}
	for (i=0; i < sizeof(maxindirs)/sizeof(maxindirs[0]); i++) {
		fprintf(stderr, "maxindirs[%d] = %"PRIu64" maxindirs,  %"PRIu64" bytes\n", i, maxindirs[i], maxindirs[i]*BLOCKSIZE);
	}

	if ((len < 0) || (len > maxsizes[NIADDR]*BLOCKSIZE-1))
		len = maxsizes[NIADDR]*BLOCKSIZE-1; /* ffs sets maxfilesize to this */

	if (debug)
		fprintf(stdout, "creating file at most = 0x%"PRIxMAX" = %"PRIdMAX" bytes\n",
		    (intmax_t)len, (intmax_t)len);

#ifndef NOIO
	fd = open(name, O_CREAT | O_WRONLY, 0666);
	if (!fd)
		err(EXIT_FAILURE, "open %s", name);
#endif

	if (len <= 0)
		goto close;

	/* If sparse file would have no indirect blocks, allocate it now */
	if (len <= maxsizes[0]*BLOCKSIZE) {

		if (debug) {
			fprintf(stdout, "off = %16"PRIdMAX" direct\n", (intmax_t)len-1);
			fflush(stdout);
		}

#ifndef NOIO
		writebyte(fd, len-1);
#endif
		goto close;
	}

	/* Allocate indirect blocks in particularly insideous order
	 * Order as follows:
	 *  each 1 level indirect pointer on inode indirect pointer 3 step by sizes[3]
	 *  each 1 level indirect pointer on inode indirect pointer 2 step by sizes[2]
	 *  each 1 level indirect pointer on inode indirect pointer 1 step by sizes[1]
	 *  each 2 level indirect pointer on inode indirect pointer 3 step by sizes[2]
	 *  each 2 level indirect pointer on inode indirect pointer 2 step by sizes[1]
	 *  each 3 level indirect pointer on inode indirect pointer 3 step by sizes[1]
	 *       i                                                  j               k
	 * notice that k=j-i+1
	 */  
	for (i = 1; i <= NIADDR; i++) {

		for (j = NIADDR; j >= i; j--) {
			int64_t boff;
			k=j-i+1;

			/* allocate a block in the file, but at most one per indirect */
			for (boff = maxsizes[j]-sizes[k]; boff >= maxsizes[j-1]; boff -= sizes[k]) {
				int64_t off;

				/* First possible byte */
				off = boff*BLOCKSIZE;

				/* If start of offset is past len, then ignore this pass */
				if (off > len-1)
					continue;

				/* Last possible byte */
				off = ((boff + sizes[k])*BLOCKSIZE)-1;

				/* If end of offset is past len, truncate offset */
				if (off > len-1)
					off = len-1;

				if (debug) {
					fprintf(stdout, "off = %16"PRId64" step = %16"PRId64"; lvl[ %d ] ib[ %d ] sizes[ %d ]\n",
					    off, (sizes[k]*BLOCKSIZE), i, j, k);
					fflush(stdout);
				}

#ifndef NOIO
				writebyte(fd, off);
#endif
			}
		}
	}
close:

#ifndef NOIO
	error = close(fd);
	if (error == -1)
		err(EXIT_FAILURE, "close");
#endif

	return EXIT_SUCCESS;
}

--=-=-=--