undo_io.c revision 72a168b59cbabdcfb1fe9db30f3d63f6f9731324
1/*
2 * undo_io.c --- This is the undo io manager that copies the old data that
3 * copies the old data being overwritten into a tdb database
4 *
5 * Copyright IBM Corporation, 2007
6 * Author Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
7 *
8 * %Begin-Header%
9 * This file may be redistributed under the terms of the GNU Public
10 * License.
11 * %End-Header%
12 */
13
14#define _LARGEFILE_SOURCE
15#define _LARGEFILE64_SOURCE
16
17#include <stdio.h>
18#include <string.h>
19#if HAVE_UNISTD_H
20#include <unistd.h>
21#endif
22#if HAVE_ERRNO_H
23#include <errno.h>
24#endif
25#include <fcntl.h>
26#include <time.h>
27#ifdef __linux__
28#include <sys/utsname.h>
29#endif
30#if HAVE_SYS_STAT_H
31#include <sys/stat.h>
32#endif
33#if HAVE_SYS_TYPES_H
34#include <sys/types.h>
35#endif
36#if HAVE_SYS_RESOURCE_H
37#include <sys/resource.h>
38#endif
39
40#include "tdb.h"
41
42#include "ext2_fs.h"
43#include "ext2fs.h"
44
45/*
46 * For checking structure magic numbers...
47 */
48
49#define EXT2_CHECK_MAGIC(struct, code) \
50	  if ((struct)->magic != (code)) return (code)
51
52struct undo_private_data {
53	int	magic;
54	TDB_CONTEXT *tdb;
55	char *tdb_file;
56
57	/* The backing io channel */
58	io_channel real;
59
60	int tdb_data_size;
61	int tdb_written;
62
63	/* to support offset in unix I/O manager */
64	ext2_loff_t offset;
65};
66
67static errcode_t undo_open(const char *name, int flags, io_channel *channel);
68static errcode_t undo_close(io_channel channel);
69static errcode_t undo_set_blksize(io_channel channel, int blksize);
70static errcode_t undo_read_blk(io_channel channel, unsigned long block,
71			       int count, void *data);
72static errcode_t undo_write_blk(io_channel channel, unsigned long block,
73				int count, const void *data);
74static errcode_t undo_flush(io_channel channel);
75static errcode_t undo_write_byte(io_channel channel, unsigned long offset,
76				int size, const void *data);
77static errcode_t undo_set_option(io_channel channel, const char *option,
78				 const char *arg);
79
80static struct struct_io_manager struct_undo_manager = {
81	EXT2_ET_MAGIC_IO_MANAGER,
82	"Undo I/O Manager",
83	undo_open,
84	undo_close,
85	undo_set_blksize,
86	undo_read_blk,
87	undo_write_blk,
88	undo_flush,
89	undo_write_byte,
90	undo_set_option
91};
92
93io_manager undo_io_manager = &struct_undo_manager;
94static io_manager undo_io_backing_manager ;
95static char *tdb_file;
96static int actual_size;
97
98errcode_t set_undo_io_backing_manager(io_manager manager)
99{
100	/*
101	 * We may want to do some validation later
102	 */
103	undo_io_backing_manager = manager;
104	return 0;
105}
106
107errcode_t set_undo_io_backup_file(char *file_name)
108{
109	tdb_file = strdup(file_name);
110
111	if (tdb_file == NULL) {
112		return EXT2_ET_NO_MEMORY;
113	}
114
115	return 0;
116}
117
118static errcode_t write_file_system_identity(io_channel undo_channel,
119							TDB_CONTEXT *tdb)
120{
121	errcode_t retval;
122	struct ext2_super_block super;
123	TDB_DATA tdb_key, tdb_data;
124	struct undo_private_data *data;
125	io_channel channel;
126	int block_size ;
127
128	data = (struct undo_private_data *) undo_channel->private_data;
129	channel = data->real;
130	block_size = channel->block_size;
131
132	io_channel_set_blksize(channel, SUPERBLOCK_OFFSET);
133	retval = io_channel_read_blk(channel, 1, -SUPERBLOCK_SIZE, &super);
134	if (retval)
135		goto err_out;
136
137	/* Write to tdb file in the file system byte order */
138	tdb_key.dptr = "filesystem MTIME";
139	tdb_key.dsize = sizeof("filesystem MTIME");
140	tdb_data.dptr = (unsigned char *) &(super.s_mtime);
141	tdb_data.dsize = sizeof(super.s_mtime);
142
143	retval = tdb_store(tdb, tdb_key, tdb_data, TDB_INSERT);
144	if (retval == -1) {
145		retval = EXT2_ET_TDB_SUCCESS + tdb_error(tdb);
146		goto err_out;
147	}
148
149	tdb_key.dptr = "filesystem UUID";
150	tdb_key.dsize = sizeof("filesystem UUID");
151	tdb_data.dptr = (unsigned char *)&(super.s_uuid);
152	tdb_data.dsize = sizeof(super.s_uuid);
153
154	retval = tdb_store(tdb, tdb_key, tdb_data, TDB_INSERT);
155	if (retval == -1) {
156		retval = EXT2_ET_TDB_SUCCESS + tdb_error(tdb);
157	}
158
159err_out:
160	io_channel_set_blksize(channel, block_size);
161	return retval;
162}
163
164static errcode_t write_block_size(TDB_CONTEXT *tdb, int block_size)
165{
166	errcode_t retval;
167	TDB_DATA tdb_key, tdb_data;
168
169	tdb_key.dptr = "filesystem BLKSIZE";
170	tdb_key.dsize = sizeof("filesystem BLKSIZE");
171	tdb_data.dptr = (unsigned char *)&(block_size);
172	tdb_data.dsize = sizeof(block_size);
173
174	retval = tdb_store(tdb, tdb_key, tdb_data, TDB_INSERT);
175	if (retval == -1) {
176		retval = EXT2_ET_TDB_SUCCESS + tdb_error(tdb);
177	}
178
179	return retval;
180}
181
182static errcode_t undo_write_tdb(io_channel channel,
183				unsigned long block, int count)
184
185{
186	int size, i, sz;
187	unsigned long block_num, backing_blk_num;
188	errcode_t retval = 0;
189	ext2_loff_t offset;
190	struct undo_private_data *data;
191	TDB_DATA tdb_key, tdb_data;
192	char *read_ptr;
193	unsigned long end_block;
194
195	data = (struct undo_private_data *) channel->private_data;
196
197	if (data->tdb == NULL) {
198		/*
199		 * Transaction database not initialized
200		 */
201		return 0;
202	}
203
204	if (count == 1)
205		size = channel->block_size;
206	else {
207		if (count < 0)
208			size = -count;
209		else
210			size = count * channel->block_size;
211	}
212	/*
213	 * Data is stored in tdb database as blocks of tdb_data_size size
214	 * This helps in efficient lookup further.
215	 *
216	 * We divide the disk to blocks of tdb_data_size.
217	 */
218	offset = (block * channel->block_size) + data->offset ;
219	block_num = offset / data->tdb_data_size;
220	end_block = (offset + size) / data->tdb_data_size;
221
222	tdb_transaction_start(data->tdb);
223	while (block_num <= end_block ) {
224
225		tdb_key.dptr = (unsigned char *)&block_num;
226		tdb_key.dsize = sizeof(block_num);
227		/*
228		 * Check if we have the record already
229		 */
230		if (tdb_exists(data->tdb, tdb_key)) {
231			/* Try the next block */
232			block_num++;
233			continue;
234		}
235		/*
236		 * Read one block using the backing I/O manager
237		 * The backing I/O manager block size may be
238		 * different from the tdb_data_size.
239		 * Also we need to recalcuate the block number with respect
240		 * to the backing I/O manager.
241		 */
242		offset = block_num * data->tdb_data_size;
243		backing_blk_num = (offset - data->offset) / channel->block_size;
244
245		count = data->tdb_data_size +
246				((offset - data->offset) % channel->block_size);
247		retval = ext2fs_get_mem(count, &read_ptr);
248		if (retval) {
249			tdb_transaction_cancel(data->tdb);
250			return retval;
251		}
252
253		memset(read_ptr, 0, count);
254		actual_size = 0;
255		if ((count % channel->block_size) == 0)
256			sz = count / channel->block_size;
257		else
258			sz = -count;
259		retval = io_channel_read_blk(data->real, backing_blk_num,
260					     sz, read_ptr);
261		if (retval) {
262			if (retval != EXT2_ET_SHORT_READ) {
263				free(read_ptr);
264				tdb_transaction_cancel(data->tdb);
265				return retval;
266			}
267			/*
268			 * short read so update the record size
269			 * accordingly
270			 */
271			tdb_data.dsize = actual_size;
272		} else {
273			tdb_data.dsize = data->tdb_data_size;
274		}
275		tdb_data.dptr = read_ptr +
276				((offset - data->offset) % channel->block_size);
277#ifdef DEBUG
278		printf("Printing with key %ld data %x and size %d\n",
279		       block_num,
280		       tdb_data.dptr,
281		       tdb_data.dsize);
282#endif
283		if (!data->tdb_written) {
284			data->tdb_written = 1;
285			/* Write the blocksize to tdb file */
286			retval = write_block_size(data->tdb,
287						  data->tdb_data_size);
288			if (retval) {
289				tdb_transaction_cancel(data->tdb);
290				retval = EXT2_ET_TDB_ERR_IO;
291				free(read_ptr);
292				return retval;
293			}
294		}
295		retval = tdb_store(data->tdb, tdb_key, tdb_data, TDB_INSERT);
296		if (retval == -1) {
297			/*
298			 * TDB_ERR_EXISTS cannot happen because we
299			 * have already verified it doesn't exist
300			 */
301			tdb_transaction_cancel(data->tdb);
302			retval = EXT2_ET_TDB_ERR_IO;
303			free(read_ptr);
304			return retval;
305		}
306		free(read_ptr);
307		/* Next block */
308		block_num++;
309	}
310	tdb_transaction_commit(data->tdb);
311
312	return retval;
313}
314
315static errcode_t undo_io_read_error(io_channel channel,
316			unsigned long block, int count, void *data,
317			size_t size, int actual, errcode_t error)
318{
319	actual_size = actual;
320	return error;
321}
322
323static void undo_err_handler_init(io_channel channel)
324{
325	channel->read_error = undo_io_read_error;
326}
327
328static errcode_t undo_open(const char *name, int flags, io_channel *channel)
329{
330	io_channel	io = NULL;
331	struct undo_private_data *data = NULL;
332	errcode_t	retval;
333	int		open_flags;
334	struct stat	st;
335
336	if (name == 0)
337		return EXT2_ET_BAD_DEVICE_NAME;
338	retval = ext2fs_get_mem(sizeof(struct struct_io_channel), &io);
339	if (retval)
340		return retval;
341	memset(io, 0, sizeof(struct struct_io_channel));
342	io->magic = EXT2_ET_MAGIC_IO_CHANNEL;
343	retval = ext2fs_get_mem(sizeof(struct undo_private_data), &data);
344	if (retval)
345		goto cleanup;
346
347	io->manager = undo_io_manager;
348	retval = ext2fs_get_mem(strlen(name)+1, &io->name);
349	if (retval)
350		goto cleanup;
351
352	strcpy(io->name, name);
353	io->private_data = data;
354	io->block_size = 1024;
355	io->read_error = 0;
356	io->write_error = 0;
357	io->refcount = 1;
358
359	memset(data, 0, sizeof(struct undo_private_data));
360	data->magic = EXT2_ET_MAGIC_UNIX_IO_CHANNEL;
361
362	if (undo_io_backing_manager) {
363		retval = undo_io_backing_manager->open(name, flags,
364						       &data->real);
365		if (retval)
366			goto cleanup;
367	} else {
368		data->real = 0;
369	}
370
371	/* setup the tdb file */
372	data->tdb = tdb_open(tdb_file, 0, TDB_CLEAR_IF_FIRST,
373			     O_RDWR | O_CREAT | O_TRUNC | O_EXCL, 0600);
374	if (!data->tdb) {
375		retval = errno;
376		goto cleanup;
377	}
378
379	/*
380	 * setup err handler for read so that we know
381	 * when the backing manager fails do short read
382	 */
383	undo_err_handler_init(data->real);
384
385	*channel = io;
386	return 0;
387
388cleanup:
389	if (data->real)
390		io_channel_close(data->real);
391	if (data)
392		ext2fs_free_mem(&data);
393	if (io)
394		ext2fs_free_mem(&io);
395	return retval;
396}
397
398static errcode_t undo_close(io_channel channel)
399{
400	struct undo_private_data *data;
401	errcode_t	retval = 0;
402
403	EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL);
404	data = (struct undo_private_data *) channel->private_data;
405	EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_UNIX_IO_CHANNEL);
406
407	if (--channel->refcount > 0)
408		return 0;
409	/* Before closing write the file system identity */
410	retval = write_file_system_identity(channel, data->tdb);
411	if (retval)
412		return retval;
413	if (data->real)
414		retval = io_channel_close(data->real);
415	if (data->tdb)
416		tdb_close(data->tdb);
417	ext2fs_free_mem(&channel->private_data);
418	if (channel->name)
419		ext2fs_free_mem(&channel->name);
420	ext2fs_free_mem(&channel);
421
422	return retval;
423}
424
425static errcode_t undo_set_blksize(io_channel channel, int blksize)
426{
427	struct undo_private_data *data;
428	errcode_t		retval;
429
430	EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL);
431	data = (struct undo_private_data *) channel->private_data;
432	EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_UNIX_IO_CHANNEL);
433
434	if (data->real)
435		retval = io_channel_set_blksize(data->real, blksize);
436	/*
437	 * Set the block size used for tdb
438	 */
439	if (!data->tdb_data_size) {
440		data->tdb_data_size = blksize;
441	}
442	channel->block_size = blksize;
443	return retval;
444}
445
446static errcode_t undo_read_blk(io_channel channel, unsigned long block,
447			       int count, void *buf)
448{
449	errcode_t	retval;
450	struct undo_private_data *data;
451
452	EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL);
453	data = (struct undo_private_data *) channel->private_data;
454	EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_UNIX_IO_CHANNEL);
455
456	if (data->real)
457		retval = io_channel_read_blk(data->real, block, count, buf);
458
459	return retval;
460}
461
462static errcode_t undo_write_blk(io_channel channel, unsigned long block,
463				int count, const void *buf)
464{
465	struct undo_private_data *data;
466	errcode_t	retval = 0;
467
468	EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL);
469	data = (struct undo_private_data *) channel->private_data;
470	EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_UNIX_IO_CHANNEL);
471	/*
472	 * First write the existing content into database
473	 */
474	retval = undo_write_tdb(channel, block, count);
475	if (retval)
476		 return retval;
477	if (data->real)
478		retval = io_channel_write_blk(data->real, block, count, buf);
479
480	return retval;
481}
482
483static errcode_t undo_write_byte(io_channel channel, unsigned long offset,
484				 int size, const void *buf)
485{
486	struct undo_private_data *data;
487	errcode_t	retval = 0;
488	ssize_t		actual;
489	ext2_loff_t	location;
490	unsigned long blk_num, count;;
491
492	EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL);
493	data = (struct undo_private_data *) channel->private_data;
494	EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_UNIX_IO_CHANNEL);
495
496	location = offset + data->offset;
497	blk_num = location/channel->block_size;
498	/*
499	 * the size specified may spread across multiple blocks
500	 * also make sure we account for the fact that block start
501	 * offset for tdb is different from the backing I/O manager
502	 * due to possible different block size
503	 */
504	count = (size + (location % channel->block_size) +
505			channel->block_size  -1)/channel->block_size;
506	retval = undo_write_tdb(channel, blk_num, count);
507	if (retval)
508		return retval;
509	if (data->real && data->real->manager->write_byte)
510		retval = io_channel_write_byte(data->real, offset, size, buf);
511
512	return retval;
513}
514
515/*
516 * Flush data buffers to disk.
517 */
518static errcode_t undo_flush(io_channel channel)
519{
520	errcode_t	retval = 0;
521	struct undo_private_data *data;
522
523	EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL);
524	data = (struct undo_private_data *) channel->private_data;
525	EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_UNIX_IO_CHANNEL);
526
527	if (data->real)
528		retval = io_channel_flush(data->real);
529
530	return retval;
531}
532
533static errcode_t undo_set_option(io_channel channel, const char *option,
534				 const char *arg)
535{
536	errcode_t	retval = 0;
537	struct undo_private_data *data;
538	unsigned long tmp;
539	char *end;
540
541	EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL);
542	data = (struct undo_private_data *) channel->private_data;
543	EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_UNIX_IO_CHANNEL);
544
545	if (!strcmp(option, "tdb_data_size")) {
546		if (!arg)
547			return EXT2_ET_INVALID_ARGUMENT;
548
549		tmp = strtoul(arg, &end, 0);
550		if (*end)
551			return EXT2_ET_INVALID_ARGUMENT;
552		if (!data->tdb_data_size || !data->tdb_written) {
553			data->tdb_data_size = tmp;
554		}
555		return 0;
556	}
557	/*
558	 * Need to support offset option to work with
559	 * Unix I/O manager
560	 */
561	if (data->real && data->real->manager->set_option) {
562		retval = data->real->manager->set_option(data->real,
563							option, arg);
564	}
565	if (!retval && !strcmp(option, "offset")) {
566		if (!arg)
567			return EXT2_ET_INVALID_ARGUMENT;
568
569		tmp = strtoul(arg, &end, 0);
570		if (*end)
571			return EXT2_ET_INVALID_ARGUMENT;
572		data->offset = tmp;
573	}
574	return retval;
575}
576