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