librazor/atomic-actions.c
author J. Ali Harlow <ali@juiblex.co.uk>
Thu Feb 09 20:45:27 2012 +0000 (2012-02-09)
changeset 418 33b825d3128d
child 423 6112bcc5d1cf
permissions -rw-r--r--
Add transaction barriers
These allow packages to be installed and removed which have scripts
that depend on each other when atomic transactions are involved.
Note that yum supports pre, but not other requires flags. post will
need similar support to the post scripts themselves pulling in the
requires flags from the rpms. Likewise preun and postun will need
similar handling to those scrips since the requires flags will need
to be stored in the razor database.
     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_str)
   263 			razor_atomic_set_error_str(atomic, 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_str)
   282 			razor_atomic_set_error_str(atomic, 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_str)
   305 			razor_atomic_set_error_str(atomic, NULL,
   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_str)
   325 			razor_atomic_set_error_str(atomic, NULL,
   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_str)
   355 			razor_atomic_set_error_mswin(atomic, 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_str)
   365 			razor_atomic_set_error_str(atomic, 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 */