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