Skip to contentSkip to conversation

debugfs: dump a sparse file as a new sparse file

PATCH Applied Zheng Liu 3 participants 11 messages Jan 1, 2013 – Jan 10, 2013

lore
Share & tools
lore

Zheng Liu

author
#0 lore raw

v2 cover debugfs: dump a sparse file as a new sparse file

Currently when we use dump command in debugfs to dump a file, all extents will be dumped to a new file no matter whether these extents are written or not. If we want to dump a sparse file, it will waste time to fill zero into the file. This patch series tries to avoid to dump unwritten extent.

v2 <- v1:
- split original patch into two parts.
- make code clearly according to Ted's suggestions

Here is the first version:
http://patchwork.ozlabs.org/patch/199303/

Regards,
- Zheng

Zheng Liu (2):
debugfs: fixup the hard-coded buffer length in dump_file
debugfs: dump a sparse file as a new sparse file

 debugfs/dump.c      | 25 ++++++++++++++++++++++---
 lib/ext2fs/ext2fs.h |  3 +++
 lib/ext2fs/fileio.c | 50 +++++++++++++++++++++++++++++++++++++++-----------
 3 files changed, 64 insertions(+), 14 deletions(-)
Signature · 1 line
1.7.12.rc2.18.g61b472e
author
#1 lore raw

PATCH v2 1/2 debugfs: fixup the hard-coded buffer length in dump_file

From: Zheng Liu <[email protected]>

For dumping a sparse file, a buffer needs to be allocated, and length is equal to block size. So we can't define a hard-coded length due to the block size might be 1k, 2k, or 4k. Now we malloc this buffer to fix the hard-coded length.

CC George Spelvin <[email protected]>
CC "Theodore Ts'o" <[email protected]>
---
 debugfs/dump.c | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)
debugfs/dump.c +10 -3
index a15a0b7..5b2289c 100644
@@ -105,10 +105,10 @@ static void dump_file(const char *cmdname, ext2_ino_t ino, int fd,
 {
 	errcode_t retval;
 	struct ext2_inode	inode;
-	char 		buf[8192];
+	char		*buf = 0;
 	ext2_file_t	e2_file;
 	int		nbytes;
-	unsigned int	got;
+	unsigned int	got, blocksize = current_fs->blocksize;
 
 	if (debugfs_read_inode(ino, &inode, cmdname))
 		return;
@@ -118,8 +118,13 @@ static void dump_file(const char *cmdname, ext2_ino_t ino, int fd,
 		com_err(cmdname, retval, "while opening ext2 file");
 		return;
 	}
+	retval = ext2fs_get_array(1, blocksize, &buf);
+	if (retval) {
+		com_err(cmdname, retval, "while allocating memory");
+		return;
+	}
 	while (1) {
-		retval = ext2fs_file_read(e2_file, buf, sizeof(buf), &got);
+		retval = ext2fs_file_read(e2_file, buf, blocksize, &got);
 		if (retval)
 			com_err(cmdname, retval, "while reading ext2 file");
 		if (got == 0)
@@ -128,6 +133,8 @@ static void dump_file(const char *cmdname, ext2_ino_t ino, int fd,
 		if ((unsigned) nbytes != got)
 			com_err(cmdname, errno, "while writing file");
 	}
+	if (buf)
+		ext2fs_free_mem(&buf);
 	retval = ext2fs_file_close(e2_file);
 	if (retval) {
 		com_err(cmdname, retval, "while closing ext2 file");
Signature · 1 line
1.7.12.rc2.18.g61b472e
#2 lore raw

Re: [PATCH 1/2 v2] debugfs: fixup the hard-coded buffer length in dump_file

debugfs/dump.c static void dump_file(const char *cmdname, ext2_ino_t ino, int fd, · line 121

Quoted 1 line · Zheng Liu
debugfs/dump.c +1 -0
+	retval = ext2fs_get_array(1, blocksize, &buf);

Thanks, applied. I did make one change to the above line:

	retval = ext2fs_get_mem(blocksize, &buf);

							- Ted
author
#3 lore raw

PATCH v2 2/2 debugfs: dump a sparse file as a new sparse file

In reply to Zheng Liu

From: Zheng Liu <[email protected]>

For dumping a sparse file, ext2fs_file_read2 is defined to expand the interface. It returns the sizeof hole and we can call lseek64(2) to skip it.

CC George Spelvin <[email protected]>
CC "Theodore Ts'o" <[email protected]>
---
 debugfs/dump.c      | 14 +++++++++++++-
 lib/ext2fs/ext2fs.h |  3 +++
 lib/ext2fs/fileio.c | 50 +++++++++++++++++++++++++++++++++++++++-----------
 3 files changed, 55 insertions(+), 12 deletions(-)
debugfs/dump.c +13 -1
index 5b2289c..d4483dc 100644
@@ -107,6 +107,7 @@ static void dump_file(const char *cmdname, ext2_ino_t ino, int fd,
 	struct ext2_inode	inode;
 	char		*buf = 0;
 	ext2_file_t	e2_file;
+	ext2_off64_t	seek;
 	int		nbytes;
 	unsigned int	got, blocksize = current_fs->blocksize;
 
@@ -124,11 +125,22 @@ static void dump_file(const char *cmdname, ext2_ino_t ino, int fd,
 		return;
 	}
 	while (1) {
-		retval = ext2fs_file_read(e2_file, buf, blocksize, &got);
+		/* If fd is stdout, we won't consider a sparse file. */
+		if (fd == 1)
+			retval = ext2fs_file_read(e2_file, buf, blocksize,
+						  &got);
+		else
+			retval = ext2fs_file_read2(e2_file, buf, blocksize,
+						   &got, &seek);
 		if (retval)
 			com_err(cmdname, retval, "while reading ext2 file");
 		if (got == 0)
 			break;
+		if (fd != 1) {
+			nbytes = lseek64(fd, blocksize * seek, SEEK_CUR);
+			if (nbytes < 0)
+				com_err(cmdname, errno, "while lseek file");
+		}
 		nbytes = write(fd, buf, got);
 		if ((unsigned) nbytes != got)
 			com_err(cmdname, errno, "while writing file");
lib/ext2fs/ext2fs.h +3 -0
index 7ec189e..435b68c 100644
@@ -1171,6 +1171,9 @@ extern errcode_t ext2fs_file_close(ext2_file_t file);
 extern errcode_t ext2fs_file_flush(ext2_file_t file);
 extern errcode_t ext2fs_file_read(ext2_file_t file, void *buf,
 				  unsigned int wanted, unsigned int *got);
+extern errcode_t ext2fs_file_read2(ext2_file_t file, void *buf,
+				   unsigned int wanted, unsigned int *got,
+				   ext2_off64_t *seek);
 extern errcode_t ext2fs_file_write(ext2_file_t file, const void *buf,
 				   unsigned int nbytes, unsigned int *written);
 extern errcode_t ext2fs_file_llseek(ext2_file_t file, __u64 offset,
lib/ext2fs/fileio.c +39 -11

106 lines not on this page · raw message

Signature · 1 line
1.7.12.rc2.18.g61b472e
#4 lore raw

lib/ext2fs/fileio.c errcode_t ext2fs_file_close(ext2_file_t file) · line 245

Quoted 3 lines · Zheng Liu
lib/ext2fs/fileio.c +3 -0
+errcode_t ext2fs_file_read2(ext2_file_t file, void *buf,
+			    unsigned int wanted, unsigned int *got,
+			    ext2_off64_t *seek)

I'm a bit concenred about this abstraction. Consider what happens if wanted is greater than a block size --- for example, consider if wanted is 16k, and every other 1k block is uninitialized.

Then ext2fs_file_read2() will return *got set to 8k, and *seek set to 8k, and the buffer will contain the blocks that are initialized packed up right against each other.

Worse, ext2fs_file_read() will do the same thing, so this commit changes how ext2fs_file_read() functions, and a program which expects to get the correct contents from the file will malfunction.

- Ted

author
#5 lore raw
Quoted 8 lines · Theodore Ts'o

lib/ext2fs/fileio.c errcode_t ext2fs_file_close(ext2_file_t file) · line 245

Quoted 3 lines · Zheng Liu
lib/ext2fs/fileio.c +3 -0
+errcode_t ext2fs_file_read2(ext2_file_t file, void *buf,
+			    unsigned int wanted, unsigned int *got,
+			    ext2_off64_t *seek)

I'm a bit concenred about this abstraction. Consider what happens if wanted is greater than a block size --- for example, consider if wanted is 16k, and every other 1k block is uninitialized.

Hi Ted,

I wonder why wanted is 16k. If a program calls ext2fs_file_read() function, seek will be 0 and SEEK flag won't be marked. The behavior of ext2fs_file_read() is the same as before. If ext2fs_file_read2() is called by dump_file(), seek won't be 0 and wanted is always equal to block size. That is why I fix the hard-coded buffer length in dump_file(). If I miss something, please let me know.

Thanks,
- Zheng

Quoted 8 lines

Then ext2fs_file_read2() will return *got set to 8k, and *seek set to 8k, and the buffer will contain the blocks that are initialized packed up right against each other.

Worse, ext2fs_file_read() will do the same thing, so this commit changes how ext2fs_file_read() functions, and a program which expects to get the correct contents from the file will malfunction.

#6 lore raw
Quoted 5 lines · Zheng Liu
Quoted 4 lines

I'm a bit concenred about this abstraction. Consider what happens if wanted is greater than a block size --- for example, consider if wanted is 16k, and every other 1k block is uninitialized.

Quoted 8 lines

Hi Ted,

I wonder why wanted is 16k. If a program calls ext2fs_file_read() function, seek will be 0 and SEEK flag won't be marked. The behavior of ext2fs_file_read() is the same as before. If ext2fs_file_read2() is called by dump_file(), seek won't be 0 and wanted is always equal to block size. That is why I fix the hard-coded buffer length in dump_file(). If I miss something, please let me know.

The problem is that ext2fs_file_read() is an exported function, and there are users of this API/ABI outside of e2fsprogs.

The goal of this function is that it should look like the read system call, and the caller might not know what the blocksize might be. So if the caller uses a 4k fixed size buffer, and the underlying file system blocksize is 1k, this function needs to work properly.

So consider what happens if some program, perhaps an ext[234] FUSE driver (there are two or three of them out there), or the e2tools package, uses a 4k or 16k buffer --- this is legal, and they call the existing ext2fs_file_read() library function. In your patch, ext2fs_file_read() will call ext2fs_file_read2(), and it will skip the sparse blocks, and since the returned seek pointer is null, there's no possible way for the caller of the ext2fs_file_read() would know this had happened --- and even if there was a way, we don't ever change the semantics/behaviour of an existing functional interface unless it's a clear bug (and even then we need to think very carefully about the backwards compatibility implications).

Regards,

- Ted

author
#7 lore raw

Hi Ted,

Sorry, I am still confused. Maybe I misunderstand something. Please bear with me.

Quoted 15 lines · Theodore Ts'o
Quoted 5 lines · Zheng Liu
Quoted 4 lines

I'm a bit concenred about this abstraction. Consider what happens if wanted is greater than a block size --- for example, consider if wanted is 16k, and every other 1k block is uninitialized.

Quoted 8 lines

Hi Ted,

I wonder why wanted is 16k. If a program calls ext2fs_file_read() function, seek will be 0 and SEEK flag won't be marked. The behavior of ext2fs_file_read() is the same as before. If ext2fs_file_read2() is called by dump_file(), seek won't be 0 and wanted is always equal to block size. That is why I fix the hard-coded buffer length in dump_file(). If I miss something, please let me know.

Quoted 7 lines

The problem is that ext2fs_file_read() is an exported function, and there are users of this API/ABI outside of e2fsprogs.

The goal of this function is that it should look like the read system call, and the caller might not know what the blocksize might be. So if the caller uses a 4k fixed size buffer, and the underlying file system blocksize is 1k, this function needs to work properly.

Yeah, Caller can use a fixed size buffer and needn't to care about the underlying file system blocksize.

Quoted 12 lines

So consider what happens if some program, perhaps an ext[234] FUSE driver (there are two or three of them out there), or the e2tools package, uses a 4k or 16k buffer --- this is legal, and they call the existing ext2fs_file_read() library function. In your patch, ext2fs_file_read() will call ext2fs_file_read2(), and it will skip the sparse blocks, and since the returned seek pointer is null, there's no possible way for the caller of the ext2fs_file_read() would know this had happened --- and even if there was a way, we don't ever change the semantics/behaviour of an existing functional interface unless it's a clear bug (and even then we need to think very carefully about the backwards compatibility implications).

Yes, some programs call ext2fs_file_read() with a 4k or 16k fixed size buffer, and ext2fs_file_read() calls ext2fs_file_read2(). But it won't skip the sparse blocks because when ext2fs_file_read2() is called in ext2fs_file_read(), the last argument, namely 'seek', is 0. That means that in ext2fs_file_read2() 'flags' is 0. Thus, in load_buffer() 'flags' is not equal to SEEK, and EXT2_FILE_BUF_VALID is marked. Then we return back to ext2fs_file_read2() and all data in file->buf is copied. So I think the behavior of ext2fs_file_read() doesn't be changed.

On the other hand, if ext2fs_file_read2() is called by dump_file() directly. 'seek' is not 0 and 'wanted' is equal to blocksize. In ext2fs_file_read2() 'flags' is assigned to SEEK and in load_buffer() EXT2_FILE_BUF_VALID is not marked if it meets an uninitialized extent.

Am I missing something? Thanks for your time.

Regards,
- Zheng

#8 lore raw
Quoted 9 lines · Zheng Liu

Yes, some programs call ext2fs_file_read() with a 4k or 16k fixed size buffer, and ext2fs_file_read() calls ext2fs_file_read2(). But it won't skip the sparse blocks because when ext2fs_file_read2() is called in ext2fs_file_read(), the last argument, namely 'seek', is 0. That means that in ext2fs_file_read2() 'flags' is 0. Thus, in load_buffer() 'flags' is not equal to SEEK, and EXT2_FILE_BUF_VALID is marked. Then we return back to ext2fs_file_read2() and all data in file->buf is copied. So I think the behavior of ext2fs_file_read() doesn't be changed.

You're right; I had forgotten about that part of the change.

I still am a bit concerned about the interface, because if you specify a pointer to seek in ext2fs_file_read2(), you have to know what the file system blocksize is, because if you give a count which is larger than a single block, the value of the returned seek and the data which is returned in the buffer is impossible to interpret (consider a file where every other 1k block is sparse, and you try to read into a 4k buffer).

So what I would suggest is the following as a better, more efficient interface.

1) Add a new flag which can be passed into ext2_file_open() which requests sparse-intelligent handling.

2) If the sparse flag is set, then ext2_file_read() will stop the read when it runs into the first uninitialized or sparse block. That is, consider the example file which has 8k of data, a 4k uninitialized block, and then 12k of data after that. If the sparse flag is passed to ext2_file_open(), then ext2fs_file_read(fd, buf, 16384, &got) will read 8k of data into buf, and return with got set to 8192.

3) To distinguish between EOF and a sparse block, if the current file offset is pointed at a sparse/uninitialized block, and the sparse flag was passed to ext2_file_open(), then in addition to *got getting set 0, ext2_file_read() will also return a new error code, EXT2_ET_READ_HOLE_FOUND.

4) We also extend ext2_file_llseek() to also support EXT2_SEEK_HOLE and EXT2_SEEK_DATA, which works like SEEK_HOLE and SEEK_DATA flags to llseek(). This will allow the caller to efficiently find the next part of the file with valid data.

What I like about this interface is that we don't need to define a new ext2_file_read2(), and it is also more efficient for an application which is interested in reading multiple blocks at a time.

What do you think?

- Ted

author
#9 lore raw
Quoted 45 lines · Theodore Ts'o
Quoted 9 lines · Zheng Liu

Yes, some programs call ext2fs_file_read() with a 4k or 16k fixed size buffer, and ext2fs_file_read() calls ext2fs_file_read2(). But it won't skip the sparse blocks because when ext2fs_file_read2() is called in ext2fs_file_read(), the last argument, namely 'seek', is 0. That means that in ext2fs_file_read2() 'flags' is 0. Thus, in load_buffer() 'flags' is not equal to SEEK, and EXT2_FILE_BUF_VALID is marked. Then we return back to ext2fs_file_read2() and all data in file->buf is copied. So I think the behavior of ext2fs_file_read() doesn't be changed.

You're right; I had forgotten about that part of the change.

I still am a bit concerned about the interface, because if you specify a pointer to seek in ext2fs_file_read2(), you have to know what the file system blocksize is, because if you give a count which is larger than a single block, the value of the returned seek and the data which is returned in the buffer is impossible to interpret (consider a file where every other 1k block is sparse, and you try to read into a 4k buffer).

So what I would suggest is the following as a better, more efficient interface.

1) Add a new flag which can be passed into ext2_file_open() which requests sparse-intelligent handling.

2) If the sparse flag is set, then ext2_file_read() will stop the read when it runs into the first uninitialized or sparse block. That is, consider the example file which has 8k of data, a 4k uninitialized block, and then 12k of data after that. If the sparse flag is passed to ext2_file_open(), then ext2fs_file_read(fd, buf, 16384, &got) will read 8k of data into buf, and return with got set to 8192.

3) To distinguish between EOF and a sparse block, if the current file offset is pointed at a sparse/uninitialized block, and the sparse flag was passed to ext2_file_open(), then in addition to *got getting set 0, ext2_file_read() will also return a new error code, EXT2_ET_READ_HOLE_FOUND.

4) We also extend ext2_file_llseek() to also support EXT2_SEEK_HOLE and EXT2_SEEK_DATA, which works like SEEK_HOLE and SEEK_DATA flags to llseek(). This will allow the caller to efficiently find the next part of the file with valid data.

Quoted 5 lines

What I like about this interface is that we don't need to define a new ext2_file_read2(), and it is also more efficient for an application which is interested in reading multiple blocks at a time.

What do you think?

Thanks so much for your advices. I will try to generate the latest patches.

Regards,
- Zheng

#10 lore raw
In reply to Theodore Ts'o
Quoted 7 lines

2) If the sparse flag is set, then ext2_file_read() will stop the read when it runs into the first uninitialized or sparse block. That is, consider the example file which has 8k of data, a 4k uninitialized block, and then 12k of data after that. If the sparse flag is passed to ext2_file_open(), then ext2fs_file_read(fd, buf, 16384, &got) will read 8k of data into buf, and return with got set to 8192.

Quoted 5 lines

3) To distinguish between EOF and a sparse block, if the current file offset is pointed at a sparse/uninitialized block, and the sparse flag was passed to ext2_file_open(), then in addition to *got getting set 0, ext2_file_read() will also return a new error code, EXT2_ET_READ_HOLE_FOUND.

Given that the current model of ext2fs_file_read is that it returns some valid data in *got, AND the reason it stopped short as the retval, wouldn't it make more sense to return EXT2_ET_READ_HOLE_FOUND from the *first* read call?

It's a minor thing, as you just end up falling back to the Unix syscall model of deferring the error until the next read call, but wouldn't it be more consistent?