librazor/atomic-actions.c
author J. Ali Harlow <ali@juiblex.co.uk>
Mon Feb 20 19:28:38 2012 +0000 (2012-02-20)
changeset 427 08b9c2e3cb37
parent 416 d0aa9e0a6d04
child 444 a2908416b7cc
permissions -rw-r--r--
Overload --disable-shared to also build static executables.
ali@416
     1
/*
ali@416
     2
 * Copyright (C) 2012  J. Ali Harlow <ali@juiblex.co.uk>
ali@416
     3
 *
ali@416
     4
 * This program is free software; you can redistribute it and/or modify
ali@416
     5
 * it under the terms of the GNU General Public License as published by
ali@416
     6
 * the Free Software Foundation; either version 2 of the License, or
ali@416
     7
 * (at your option) any later version.
ali@416
     8
 *
ali@416
     9
 * This program is distributed in the hope that it will be useful,
ali@416
    10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
ali@416
    11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
ali@416
    12
 * GNU General Public License for more details.
ali@416
    13
 *
ali@416
    14
 * You should have received a copy of the GNU General Public License along
ali@416
    15
 * with this program; if not, write to the Free Software Foundation, Inc.,
ali@416
    16
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
ali@416
    17
 */
ali@416
    18
ali@416
    19
#include "config.h"
ali@416
    20
ali@416
    21
#if ENABLE_ATOMIC && !HAVE_WINDOWS_KTM
ali@416
    22
ali@416
    23
#include <stdlib.h>
ali@416
    24
#include <stdio.h>
ali@416
    25
#include <string.h>
ali@416
    26
#include <limits.h>
ali@416
    27
#include <errno.h>
ali@416
    28
#include <sys/stat.h>
ali@416
    29
#include <sys/types.h>
ali@416
    30
#include <dirent.h>
ali@416
    31
#include <assert.h>
ali@416
    32
#include "razor-internal.h"
ali@416
    33
ali@416
    34
char *atomic_action_attic_tmpnam(struct razor_atomic *atomic)
ali@416
    35
{
ali@416
    36
	char filename[17];
ali@416
    37
	sprintf(filename,"%03X",atomic->next_file_tag++);
ali@416
    38
	return razor_concat(atomic->toplevel, "/", filename, NULL);
ali@416
    39
}
ali@416
    40
ali@416
    41
static struct atomic_action *
ali@416
    42
atomic_action_list_pop_head(struct atomic_action **list)
ali@416
    43
{
ali@416
    44
	struct atomic_action *head;
ali@416
    45
ali@416
    46
	head = *list;
ali@416
    47
	if (head) {
ali@416
    48
		*list = head->next;
ali@416
    49
		head->next = NULL;
ali@416
    50
	}
ali@416
    51
ali@416
    52
	return head;
ali@416
    53
}
ali@416
    54
ali@416
    55
struct atomic_action *
ali@416
    56
atomic_action_list_prepend(struct atomic_action *list,
ali@416
    57
			   struct atomic_action *action)
ali@416
    58
{
ali@416
    59
	assert(action->next == NULL);
ali@416
    60
ali@416
    61
	action->next=list;
ali@416
    62
ali@416
    63
	return action;
ali@416
    64
}
ali@416
    65
ali@416
    66
static struct atomic_action *
ali@416
    67
atomic_action_list_concat(struct atomic_action *list1,
ali@416
    68
			  struct atomic_action *list2)
ali@416
    69
{
ali@416
    70
	struct atomic_action *action;
ali@416
    71
ali@416
    72
	if (!list1)
ali@416
    73
		return list2;
ali@416
    74
ali@416
    75
	for(action=list1;action->next;action=action->next)
ali@416
    76
		;
ali@416
    77
ali@416
    78
	action->next=list2;
ali@416
    79
ali@416
    80
	return list1;
ali@416
    81
}
ali@416
    82
ali@416
    83
void atomic_action_free(struct atomic_action *action)
ali@416
    84
{
ali@416
    85
	struct atomic_action *a;
ali@416
    86
ali@416
    87
	while(action) {
ali@416
    88
		a = atomic_action_list_pop_head(&action);
ali@416
    89
ali@416
    90
		free(a->args.path);
ali@416
    91
ali@416
    92
		switch(a->type) {
ali@416
    93
		case ACTION_MAKE_DIRS:
ali@416
    94
			free(a->args.u.make_dirs.root);
ali@416
    95
			break;
ali@416
    96
		case ACTION_MOVE:
ali@416
    97
			free(a->args.u.move.dest);
ali@416
    98
			break;
ali@416
    99
#if HAVE_SYMLINK
ali@416
   100
		case ACTION_CREATE_SYMLINK:
ali@416
   101
			free(a->args.u.create_symlink.target);
ali@416
   102
			break;
ali@416
   103
#endif
ali@416
   104
		case ACTION_REMOVE:
ali@416
   105
		case ACTION_CREATE_DIR:
ali@416
   106
			break;
ali@416
   107
		}
ali@416
   108
ali@416
   109
		free(a);
ali@416
   110
	}
ali@416
   111
}
ali@416
   112
ali@416
   113
struct atomic_action *atomic_action_new(enum atomic_action_type type)
ali@416
   114
{
ali@416
   115
	struct atomic_action *action;
ali@416
   116
ali@416
   117
	action = zalloc(sizeof *action);
ali@416
   118
	action->type = type;
ali@416
   119
ali@416
   120
	return action;
ali@416
   121
}
ali@416
   122
ali@416
   123
struct atomic_action *atomic_action_list_reverse(struct atomic_action *list)
ali@416
   124
{
ali@416
   125
	struct atomic_action *prev = NULL, *next;
ali@416
   126
ali@416
   127
	while(list) {
ali@416
   128
		next = list->next;
ali@416
   129
		list->next = prev;
ali@416
   130
		prev = list;
ali@416
   131
		list = next;
ali@416
   132
	}
ali@416
   133
ali@416
   134
	return prev;
ali@416
   135
}
ali@416
   136
ali@416
   137
/*
ali@416
   138
 * All action_ functions take 1 action and return a list of primitive
ali@416
   139
 * actions they took (such that the first action in the list is the
ali@416
   140
 * last action they took). Primitive actions are always reversable.
ali@416
   141
 *
ali@416
   142
 * On failure, an error should be set on the atomic object (if it is
ali@416
   143
 * not already set), the action freed and NULL returned.
ali@416
   144
 *
ali@416
   145
 * Whether they succeed or fail, they take ownership of the passed
ali@416
   146
 * action and should thus either free it or return it back to their
ali@416
   147
 * caller as appropriate.
ali@416
   148
 *
ali@416
   149
 * A NULL return means that nothing was done which may, or may not,
ali@416
   150
 * indicate an error.
ali@416
   151
 */
ali@416
   152
ali@416
   153
static struct atomic_action *
ali@416
   154
atomic_action_make_dirs(struct razor_atomic *atomic,
ali@416
   155
			struct atomic_action *action)
ali@416
   156
{
ali@416
   157
	char buffer[PATH_MAX], *p;
ali@416
   158
	const char *slash, *next;
ali@416
   159
	struct atomic_action *primitives = NULL;
ali@416
   160
	struct atomic_action *prim;
ali@416
   161
ali@416
   162
	if (razor_atomic_in_error_state(atomic)) {
ali@416
   163
		atomic_action_free(action);
ali@416
   164
		return NULL;
ali@416
   165
	}
ali@416
   166
ali@416
   167
	strcpy(buffer, action->args.u.make_dirs.root);
ali@416
   168
	p = buffer + strlen(buffer);
ali@416
   169
	slash = action->args.path;
ali@416
   170
	for (; *slash != '\0'; slash = next) {
ali@416
   171
#ifdef MSWIN_API
ali@416
   172
		next = strpbrk(slash + 1, "/\\");
ali@416
   173
#else
ali@416
   174
		next = strchr(slash + 1, '/');
ali@416
   175
#endif
ali@416
   176
		if (next == NULL)
ali@416
   177
			break;
ali@416
   178
ali@416
   179
		memcpy(p, slash, next - slash);
ali@416
   180
		p += next - slash;
ali@416
   181
		*p = '\0';
ali@416
   182
ali@416
   183
		if (razor_valid_root_name(buffer))
ali@416
   184
			continue;
ali@416
   185
ali@416
   186
		prim = atomic_action_new(ACTION_CREATE_DIR);
ali@416
   187
		prim->args.path = strdup(buffer);
ali@416
   188
		prim->args.u.create_dir.mode = S_IRWXU | S_IRWXG | S_IRWXO;
ali@416
   189
		primitives = atomic_action_list_prepend(primitives, prim);
ali@416
   190
	}
ali@416
   191
	primitives = atomic_action_list_reverse(primitives);
ali@416
   192
ali@416
   193
	return atomic_action_do(atomic, primitives);
ali@416
   194
}
ali@416
   195
ali@416
   196
static struct atomic_action *
ali@416
   197
atomic_action_remove(struct razor_atomic *atomic, struct atomic_action *action)
ali@416
   198
{
ali@416
   199
#ifdef MSWIN_API
ali@416
   200
	wchar_t *path;
ali@416
   201
	_WDIR *dir;
ali@416
   202
#else
ali@416
   203
	DIR *dir;
ali@416
   204
#endif
ali@416
   205
	struct atomic_action *prim;
ali@416
   206
ali@416
   207
	if (razor_atomic_in_error_state(atomic)) {
ali@416
   208
		atomic_action_free(action);
ali@416
   209
		return NULL;
ali@416
   210
	}
ali@416
   211
ali@416
   212
	/*
ali@416
   213
	 * Non-empty directories should NOT be removed
ali@416
   214
	 */
ali@416
   215
#ifdef MSWIN_API
ali@416
   216
	path = razor_utf8_to_utf16(action->args.path, -1);
ali@416
   217
ali@416
   218
	dir = _wopendir(path);
ali@416
   219
	if (dir && _wreaddir(dir)) {
ali@416
   220
		atomic_action_free(action);
ali@416
   221
		action = NULL;
ali@416
   222
	}
ali@416
   223
	_wclosedir(dir);
ali@416
   224
#else
ali@416
   225
	dir = opendir(action->args.path);
ali@416
   226
	if (dir && readdir(dir)) {
ali@416
   227
		atomic_action_free(action);
ali@416
   228
		action = NULL;
ali@416
   229
	}
ali@416
   230
	closedir(dir);
ali@416
   231
#endif
ali@416
   232
ali@416
   233
	if (action) {
ali@416
   234
		prim = atomic_action_new(ACTION_MOVE);
ali@416
   235
		prim->args.path = strdup(action->args.path);
ali@416
   236
		prim->args.u.move.dest = atomic_action_attic_tmpnam(atomic);
ali@416
   237
ali@416
   238
		atomic_action_free(action);
ali@416
   239
	} else
ali@416
   240
		prim = NULL;
ali@416
   241
ali@416
   242
	return prim ? atomic_action_do(atomic, prim) : NULL;
ali@416
   243
}
ali@416
   244
ali@416
   245
static struct atomic_action *
ali@416
   246
atomic_action_create_dir(struct razor_atomic *atomic,
ali@416
   247
			 struct atomic_action *action)
ali@416
   248
{
ali@416
   249
	mode_t mode;
ali@416
   250
ali@416
   251
	if (razor_atomic_in_error_state(atomic)) {
ali@416
   252
		atomic_action_free(action);
ali@416
   253
		return NULL;
ali@416
   254
	}
ali@416
   255
ali@416
   256
	mode = action->args.u.create_dir.mode & (S_IRWXU | S_IRWXG | S_IRWXO);
ali@416
   257
ali@416
   258
	if (!mkdir(action->args.path, mode))
ali@416
   259
		return 0;
ali@416
   260
ali@416
   261
	if (errno != EEXIST || chmod(action->args.path, mode) < 0) {
ali@423
   262
		if (!atomic->error)
ali@423
   263
			atomic->error = razor_error_new_str(action->args.path,
ali@423
   264
							    strerror(errno));
ali@416
   265
		atomic_action_free(action);
ali@416
   266
		return NULL;
ali@416
   267
	}
ali@416
   268
ali@416
   269
	return action;
ali@416
   270
}
ali@416
   271
ali@416
   272
static struct atomic_action *atomic_action_rmdir(struct razor_atomic *atomic,
ali@416
   273
						 struct atomic_action *action)
ali@416
   274
{
ali@416
   275
	if (razor_atomic_in_error_state(atomic)) {
ali@416
   276
		atomic_action_free(action);
ali@416
   277
		return NULL;
ali@416
   278
	}
ali@416
   279
ali@416
   280
	if (rmdir(action->args.path) < 0) {
ali@423
   281
		if (!atomic->error)
ali@423
   282
			atomic->error = razor_error_new_str(action->args.path,
ali@423
   283
							    strerror(errno));
ali@416
   284
		atomic_action_free(action);
ali@416
   285
		return NULL;
ali@416
   286
	} else
ali@416
   287
		return action;
ali@416
   288
}
ali@416
   289
ali@416
   290
#if HAVE_SYMLINK
ali@416
   291
static struct atomic_action *
ali@416
   292
atomic_action_create_symlink(struct razor_atomic *atomic,
ali@416
   293
			     struct atomic_action *action)
ali@416
   294
{
ali@416
   295
	int r;
ali@416
   296
ali@416
   297
	if (razor_atomic_in_error_state(atomic)) {
ali@416
   298
		atomic_action_free(action);
ali@416
   299
		return NULL;
ali@416
   300
	}
ali@416
   301
ali@416
   302
	r = symlink(action->args.u.create_symlink.target, action->args.path);
ali@416
   303
	if (r < 0) {
ali@423
   304
		if (!atomic->error)
ali@423
   305
			atomic->error = razor_error_new_str(action->args.path,
ali@423
   306
							    strerror(errno));
ali@416
   307
		atomic_action_free(action);
ali@416
   308
		return NULL;
ali@416
   309
	}
ali@416
   310
ali@416
   311
	return action;
ali@416
   312
}
ali@416
   313
ali@416
   314
static struct atomic_action *
ali@416
   315
atomic_action_remove_symlink(struct razor_atomic *atomic,
ali@416
   316
			     struct atomic_action *action)
ali@416
   317
{
ali@416
   318
	if (razor_atomic_in_error_state(atomic)) {
ali@416
   319
		atomic_action_free(action);
ali@416
   320
		return NULL;
ali@416
   321
	}
ali@416
   322
ali@416
   323
	if (unlink(action->args.path) < 0) {
ali@423
   324
		if (!atomic->error)
ali@423
   325
			atomic->error = razor_error_new_str(action->args.path,
ali@423
   326
							    strerror(errno));
ali@416
   327
		atomic_action_free(action);
ali@416
   328
		return NULL;
ali@416
   329
	}
ali@416
   330
ali@416
   331
	return action;
ali@416
   332
}
ali@416
   333
#endif
ali@416
   334
ali@416
   335
static int
ali@416
   336
move_file(struct razor_atomic *atomic, const char *path, const char *dest)
ali@416
   337
{
ali@416
   338
#ifdef MSWIN_API
ali@416
   339
	wchar_t *oldbuf, *newbuf;
ali@416
   340
	const DWORD flags = MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING;
ali@416
   341
ali@416
   342
	newbuf = razor_utf8_to_utf16(dest, -1);
ali@416
   343
	oldbuf = razor_utf8_to_utf16(path, -1);
ali@416
   344
ali@416
   345
	/*
ali@416
   346
	 * Passing MOVEFILE_REPLACE_EXISTING to MoveFileEx() will
ali@416
   347
	 * cover every case we care about _except_ replacing an empty
ali@416
   348
	 * directory with a file. Calling RemoveDirectory() will deal
ali@416
   349
	 * with this case while having no effect in all other cases.
ali@416
   350
	 */
ali@416
   351
	(void)RemoveDirectoryW(newbuf);
ali@416
   352
ali@416
   353
	if (!MoveFileExW(oldbuf, newbuf, flags)) {
ali@423
   354
		if (!atomic->error)
ali@423
   355
			atomic->error = razor_error_new_mswin(newbuf,
ali@423
   356
							      GetLastError());
ali@416
   357
		return -1;
ali@416
   358
	}
ali@416
   359
ali@416
   360
	free(newbuf);
ali@416
   361
	free(oldbuf);
ali@416
   362
#else
ali@416
   363
	if (rename(path, dest)) {
ali@423
   364
		if (!atomic->error)
ali@423
   365
			atomic->error = razor_error_new_str(dest,
ali@423
   366
							    strerror(errno));
ali@416
   367
		return -1;
ali@416
   368
	}
ali@416
   369
#endif
ali@416
   370
ali@416
   371
	return 0;
ali@416
   372
}
ali@416
   373
ali@416
   374
static struct atomic_action *
ali@416
   375
atomic_action_move(struct razor_atomic *atomic, struct atomic_action *action)
ali@416
   376
{
ali@416
   377
	if (razor_atomic_in_error_state(atomic)) {
ali@416
   378
		atomic_action_free(action);
ali@416
   379
		return NULL;
ali@416
   380
	}
ali@416
   381
ali@416
   382
	if (move_file(atomic, action->args.path, action->args.u.move.dest)) {
ali@416
   383
		atomic_action_free(action);
ali@416
   384
		return NULL;
ali@416
   385
	}
ali@416
   386
ali@416
   387
	return action;
ali@416
   388
}
ali@416
   389
ali@416
   390
static struct atomic_action *
ali@416
   391
atomic_action_unmove(struct razor_atomic *atomic, struct atomic_action *action)
ali@416
   392
{
ali@416
   393
	if (razor_atomic_in_error_state(atomic)) {
ali@416
   394
		atomic_action_free(action);
ali@416
   395
		return NULL;
ali@416
   396
	}
ali@416
   397
ali@416
   398
	if (move_file(atomic, action->args.u.move.dest, action->args.path)) {
ali@416
   399
		atomic_action_free(action);
ali@416
   400
		return NULL;
ali@416
   401
	}
ali@416
   402
ali@416
   403
	return action;
ali@416
   404
}
ali@416
   405
ali@416
   406
static struct atomic_action *atomic_action_apply(struct razor_atomic *atomic,
ali@416
   407
						 struct atomic_action *action)
ali@416
   408
{
ali@416
   409
	switch(action->type) {
ali@416
   410
	case ACTION_MAKE_DIRS:
ali@416
   411
		action = atomic_action_make_dirs(atomic, action);
ali@416
   412
		break;
ali@416
   413
	case ACTION_REMOVE:
ali@416
   414
		action = atomic_action_remove(atomic, action);
ali@416
   415
		break;
ali@416
   416
	case ACTION_CREATE_DIR:
ali@416
   417
		action = atomic_action_create_dir(atomic, action);
ali@416
   418
		break;
ali@416
   419
	case ACTION_MOVE:
ali@416
   420
		action = atomic_action_move(atomic, action);
ali@416
   421
		break;
ali@416
   422
#if HAVE_SYMLINK
ali@416
   423
	case ACTION_CREATE_SYMLINK:
ali@416
   424
		action = atomic_action_create_symlink(atomic, action);
ali@416
   425
		break;
ali@416
   426
#endif
ali@416
   427
	}
ali@416
   428
	return action;
ali@416
   429
}
ali@416
   430
ali@416
   431
static struct atomic_action *atomic_action_reverse(struct razor_atomic *atomic,
ali@416
   432
						   struct atomic_action *action)
ali@416
   433
{
ali@416
   434
	switch(action->type) {
ali@416
   435
	case ACTION_MAKE_DIRS:
ali@416
   436
	case ACTION_REMOVE:
ali@416
   437
		/* Complex actions: should never happen */
ali@416
   438
		break;
ali@416
   439
	case ACTION_CREATE_DIR:
ali@416
   440
		action = atomic_action_rmdir(atomic, action);
ali@416
   441
		break;
ali@416
   442
	case ACTION_MOVE:
ali@416
   443
		action = atomic_action_unmove(atomic, action);
ali@416
   444
		break;
ali@416
   445
#if HAVE_SYMLINK
ali@416
   446
	case ACTION_CREATE_SYMLINK:
ali@416
   447
		action = atomic_action_remove_symlink(atomic, action);
ali@416
   448
		break;
ali@416
   449
#endif
ali@416
   450
	}
ali@416
   451
	return action;
ali@416
   452
}
ali@416
   453
ali@416
   454
/*
ali@416
   455
 * Note that undo has no error checking.
ali@416
   456
 */
ali@416
   457
ali@416
   458
void atomic_action_undo(struct razor_atomic *atomic,
ali@416
   459
			struct atomic_action *actions)
ali@416
   460
{
ali@416
   461
	struct atomic_action *a;
ali@416
   462
ali@416
   463
	atomic->in_undo = 1;
ali@416
   464
ali@416
   465
	while (actions) {
ali@416
   466
		a = atomic_action_list_pop_head(&actions);
ali@416
   467
		a = atomic_action_reverse(atomic, a);
ali@416
   468
		atomic_action_free(a);
ali@416
   469
	}
ali@416
   470
ali@416
   471
	atomic->in_undo = 0;
ali@416
   472
}
ali@416
   473
ali@416
   474
struct atomic_action *atomic_action_do(struct razor_atomic *atomic,
ali@416
   475
				       struct atomic_action *actions)
ali@416
   476
{
ali@416
   477
	struct atomic_action *done = NULL, *a;
ali@416
   478
ali@416
   479
	if (razor_atomic_in_error_state(atomic)) {
ali@416
   480
		atomic_action_free(actions);
ali@416
   481
		return NULL;
ali@416
   482
	}
ali@416
   483
ali@416
   484
	while (actions) {
ali@416
   485
		a = atomic_action_list_pop_head(&actions);
ali@416
   486
		a = atomic_action_apply(atomic, a);
ali@416
   487
		if (a)
ali@416
   488
			done = atomic_action_list_concat(a, done);
ali@416
   489
		if (razor_atomic_in_error_state(atomic)) {
ali@416
   490
			atomic_action_undo(atomic, done);
ali@416
   491
			done = NULL;
ali@416
   492
			atomic_action_free(actions);
ali@416
   493
		}
ali@416
   494
	}
ali@416
   495
ali@416
   496
	return done;
ali@416
   497
}
ali@416
   498
ali@416
   499
#endif	/* ENABLE_ATOMIC && !HAVE_WINDOWS_KTM */