1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/librazor/atomic-actions.c Thu Feb 09 20:45:27 2012 +0000
1.3 @@ -0,0 +1,499 @@
1.4 +/*
1.5 + * Copyright (C) 2012 J. Ali Harlow <ali@juiblex.co.uk>
1.6 + *
1.7 + * This program is free software; you can redistribute it and/or modify
1.8 + * it under the terms of the GNU General Public License as published by
1.9 + * the Free Software Foundation; either version 2 of the License, or
1.10 + * (at your option) any later version.
1.11 + *
1.12 + * This program is distributed in the hope that it will be useful,
1.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
1.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1.15 + * GNU General Public License for more details.
1.16 + *
1.17 + * You should have received a copy of the GNU General Public License along
1.18 + * with this program; if not, write to the Free Software Foundation, Inc.,
1.19 + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
1.20 + */
1.21 +
1.22 +#include "config.h"
1.23 +
1.24 +#if ENABLE_ATOMIC && !HAVE_WINDOWS_KTM
1.25 +
1.26 +#include <stdlib.h>
1.27 +#include <stdio.h>
1.28 +#include <string.h>
1.29 +#include <limits.h>
1.30 +#include <errno.h>
1.31 +#include <sys/stat.h>
1.32 +#include <sys/types.h>
1.33 +#include <dirent.h>
1.34 +#include <assert.h>
1.35 +#include "razor-internal.h"
1.36 +
1.37 +char *atomic_action_attic_tmpnam(struct razor_atomic *atomic)
1.38 +{
1.39 + char filename[17];
1.40 + sprintf(filename,"%03X",atomic->next_file_tag++);
1.41 + return razor_concat(atomic->toplevel, "/", filename, NULL);
1.42 +}
1.43 +
1.44 +static struct atomic_action *
1.45 +atomic_action_list_pop_head(struct atomic_action **list)
1.46 +{
1.47 + struct atomic_action *head;
1.48 +
1.49 + head = *list;
1.50 + if (head) {
1.51 + *list = head->next;
1.52 + head->next = NULL;
1.53 + }
1.54 +
1.55 + return head;
1.56 +}
1.57 +
1.58 +struct atomic_action *
1.59 +atomic_action_list_prepend(struct atomic_action *list,
1.60 + struct atomic_action *action)
1.61 +{
1.62 + assert(action->next == NULL);
1.63 +
1.64 + action->next=list;
1.65 +
1.66 + return action;
1.67 +}
1.68 +
1.69 +static struct atomic_action *
1.70 +atomic_action_list_concat(struct atomic_action *list1,
1.71 + struct atomic_action *list2)
1.72 +{
1.73 + struct atomic_action *action;
1.74 +
1.75 + if (!list1)
1.76 + return list2;
1.77 +
1.78 + for(action=list1;action->next;action=action->next)
1.79 + ;
1.80 +
1.81 + action->next=list2;
1.82 +
1.83 + return list1;
1.84 +}
1.85 +
1.86 +void atomic_action_free(struct atomic_action *action)
1.87 +{
1.88 + struct atomic_action *a;
1.89 +
1.90 + while(action) {
1.91 + a = atomic_action_list_pop_head(&action);
1.92 +
1.93 + free(a->args.path);
1.94 +
1.95 + switch(a->type) {
1.96 + case ACTION_MAKE_DIRS:
1.97 + free(a->args.u.make_dirs.root);
1.98 + break;
1.99 + case ACTION_MOVE:
1.100 + free(a->args.u.move.dest);
1.101 + break;
1.102 +#if HAVE_SYMLINK
1.103 + case ACTION_CREATE_SYMLINK:
1.104 + free(a->args.u.create_symlink.target);
1.105 + break;
1.106 +#endif
1.107 + case ACTION_REMOVE:
1.108 + case ACTION_CREATE_DIR:
1.109 + break;
1.110 + }
1.111 +
1.112 + free(a);
1.113 + }
1.114 +}
1.115 +
1.116 +struct atomic_action *atomic_action_new(enum atomic_action_type type)
1.117 +{
1.118 + struct atomic_action *action;
1.119 +
1.120 + action = zalloc(sizeof *action);
1.121 + action->type = type;
1.122 +
1.123 + return action;
1.124 +}
1.125 +
1.126 +struct atomic_action *atomic_action_list_reverse(struct atomic_action *list)
1.127 +{
1.128 + struct atomic_action *prev = NULL, *next;
1.129 +
1.130 + while(list) {
1.131 + next = list->next;
1.132 + list->next = prev;
1.133 + prev = list;
1.134 + list = next;
1.135 + }
1.136 +
1.137 + return prev;
1.138 +}
1.139 +
1.140 +/*
1.141 + * All action_ functions take 1 action and return a list of primitive
1.142 + * actions they took (such that the first action in the list is the
1.143 + * last action they took). Primitive actions are always reversable.
1.144 + *
1.145 + * On failure, an error should be set on the atomic object (if it is
1.146 + * not already set), the action freed and NULL returned.
1.147 + *
1.148 + * Whether they succeed or fail, they take ownership of the passed
1.149 + * action and should thus either free it or return it back to their
1.150 + * caller as appropriate.
1.151 + *
1.152 + * A NULL return means that nothing was done which may, or may not,
1.153 + * indicate an error.
1.154 + */
1.155 +
1.156 +static struct atomic_action *
1.157 +atomic_action_make_dirs(struct razor_atomic *atomic,
1.158 + struct atomic_action *action)
1.159 +{
1.160 + char buffer[PATH_MAX], *p;
1.161 + const char *slash, *next;
1.162 + struct atomic_action *primitives = NULL;
1.163 + struct atomic_action *prim;
1.164 +
1.165 + if (razor_atomic_in_error_state(atomic)) {
1.166 + atomic_action_free(action);
1.167 + return NULL;
1.168 + }
1.169 +
1.170 + strcpy(buffer, action->args.u.make_dirs.root);
1.171 + p = buffer + strlen(buffer);
1.172 + slash = action->args.path;
1.173 + for (; *slash != '\0'; slash = next) {
1.174 +#ifdef MSWIN_API
1.175 + next = strpbrk(slash + 1, "/\\");
1.176 +#else
1.177 + next = strchr(slash + 1, '/');
1.178 +#endif
1.179 + if (next == NULL)
1.180 + break;
1.181 +
1.182 + memcpy(p, slash, next - slash);
1.183 + p += next - slash;
1.184 + *p = '\0';
1.185 +
1.186 + if (razor_valid_root_name(buffer))
1.187 + continue;
1.188 +
1.189 + prim = atomic_action_new(ACTION_CREATE_DIR);
1.190 + prim->args.path = strdup(buffer);
1.191 + prim->args.u.create_dir.mode = S_IRWXU | S_IRWXG | S_IRWXO;
1.192 + primitives = atomic_action_list_prepend(primitives, prim);
1.193 + }
1.194 + primitives = atomic_action_list_reverse(primitives);
1.195 +
1.196 + return atomic_action_do(atomic, primitives);
1.197 +}
1.198 +
1.199 +static struct atomic_action *
1.200 +atomic_action_remove(struct razor_atomic *atomic, struct atomic_action *action)
1.201 +{
1.202 +#ifdef MSWIN_API
1.203 + wchar_t *path;
1.204 + _WDIR *dir;
1.205 +#else
1.206 + DIR *dir;
1.207 +#endif
1.208 + struct atomic_action *prim;
1.209 +
1.210 + if (razor_atomic_in_error_state(atomic)) {
1.211 + atomic_action_free(action);
1.212 + return NULL;
1.213 + }
1.214 +
1.215 + /*
1.216 + * Non-empty directories should NOT be removed
1.217 + */
1.218 +#ifdef MSWIN_API
1.219 + path = razor_utf8_to_utf16(action->args.path, -1);
1.220 +
1.221 + dir = _wopendir(path);
1.222 + if (dir && _wreaddir(dir)) {
1.223 + atomic_action_free(action);
1.224 + action = NULL;
1.225 + }
1.226 + _wclosedir(dir);
1.227 +#else
1.228 + dir = opendir(action->args.path);
1.229 + if (dir && readdir(dir)) {
1.230 + atomic_action_free(action);
1.231 + action = NULL;
1.232 + }
1.233 + closedir(dir);
1.234 +#endif
1.235 +
1.236 + if (action) {
1.237 + prim = atomic_action_new(ACTION_MOVE);
1.238 + prim->args.path = strdup(action->args.path);
1.239 + prim->args.u.move.dest = atomic_action_attic_tmpnam(atomic);
1.240 +
1.241 + atomic_action_free(action);
1.242 + } else
1.243 + prim = NULL;
1.244 +
1.245 + return prim ? atomic_action_do(atomic, prim) : NULL;
1.246 +}
1.247 +
1.248 +static struct atomic_action *
1.249 +atomic_action_create_dir(struct razor_atomic *atomic,
1.250 + struct atomic_action *action)
1.251 +{
1.252 + mode_t mode;
1.253 +
1.254 + if (razor_atomic_in_error_state(atomic)) {
1.255 + atomic_action_free(action);
1.256 + return NULL;
1.257 + }
1.258 +
1.259 + mode = action->args.u.create_dir.mode & (S_IRWXU | S_IRWXG | S_IRWXO);
1.260 +
1.261 + if (!mkdir(action->args.path, mode))
1.262 + return 0;
1.263 +
1.264 + if (errno != EEXIST || chmod(action->args.path, mode) < 0) {
1.265 + if (!atomic->error_str)
1.266 + razor_atomic_set_error_str(atomic, action->args.path,
1.267 + strerror(errno));
1.268 + atomic_action_free(action);
1.269 + return NULL;
1.270 + }
1.271 +
1.272 + return action;
1.273 +}
1.274 +
1.275 +static struct atomic_action *atomic_action_rmdir(struct razor_atomic *atomic,
1.276 + struct atomic_action *action)
1.277 +{
1.278 + if (razor_atomic_in_error_state(atomic)) {
1.279 + atomic_action_free(action);
1.280 + return NULL;
1.281 + }
1.282 +
1.283 + if (rmdir(action->args.path) < 0) {
1.284 + if (!atomic->error_str)
1.285 + razor_atomic_set_error_str(atomic, action->args.path,
1.286 + strerror(errno));
1.287 + atomic_action_free(action);
1.288 + return NULL;
1.289 + } else
1.290 + return action;
1.291 +}
1.292 +
1.293 +#if HAVE_SYMLINK
1.294 +static struct atomic_action *
1.295 +atomic_action_create_symlink(struct razor_atomic *atomic,
1.296 + struct atomic_action *action)
1.297 +{
1.298 + int r;
1.299 +
1.300 + if (razor_atomic_in_error_state(atomic)) {
1.301 + atomic_action_free(action);
1.302 + return NULL;
1.303 + }
1.304 +
1.305 + r = symlink(action->args.u.create_symlink.target, action->args.path);
1.306 + if (r < 0) {
1.307 + if (!atomic->error_str)
1.308 + razor_atomic_set_error_str(atomic, NULL,
1.309 + strerror(errno));
1.310 + atomic_action_free(action);
1.311 + return NULL;
1.312 + }
1.313 +
1.314 + return action;
1.315 +}
1.316 +
1.317 +static struct atomic_action *
1.318 +atomic_action_remove_symlink(struct razor_atomic *atomic,
1.319 + struct atomic_action *action)
1.320 +{
1.321 + if (razor_atomic_in_error_state(atomic)) {
1.322 + atomic_action_free(action);
1.323 + return NULL;
1.324 + }
1.325 +
1.326 + if (unlink(action->args.path) < 0) {
1.327 + if (!atomic->error_str)
1.328 + razor_atomic_set_error_str(atomic, NULL,
1.329 + strerror(errno));
1.330 + atomic_action_free(action);
1.331 + return NULL;
1.332 + }
1.333 +
1.334 + return action;
1.335 +}
1.336 +#endif
1.337 +
1.338 +static int
1.339 +move_file(struct razor_atomic *atomic, const char *path, const char *dest)
1.340 +{
1.341 +#ifdef MSWIN_API
1.342 + wchar_t *oldbuf, *newbuf;
1.343 + const DWORD flags = MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING;
1.344 +
1.345 + newbuf = razor_utf8_to_utf16(dest, -1);
1.346 + oldbuf = razor_utf8_to_utf16(path, -1);
1.347 +
1.348 + /*
1.349 + * Passing MOVEFILE_REPLACE_EXISTING to MoveFileEx() will
1.350 + * cover every case we care about _except_ replacing an empty
1.351 + * directory with a file. Calling RemoveDirectory() will deal
1.352 + * with this case while having no effect in all other cases.
1.353 + */
1.354 + (void)RemoveDirectoryW(newbuf);
1.355 +
1.356 + if (!MoveFileExW(oldbuf, newbuf, flags)) {
1.357 + if (!atomic->error_str)
1.358 + razor_atomic_set_error_mswin(atomic, newbuf,
1.359 + GetLastError());
1.360 + return -1;
1.361 + }
1.362 +
1.363 + free(newbuf);
1.364 + free(oldbuf);
1.365 +#else
1.366 + if (rename(path, dest)) {
1.367 + if (!atomic->error_str)
1.368 + razor_atomic_set_error_str(atomic, dest,
1.369 + strerror(errno));
1.370 + return -1;
1.371 + }
1.372 +#endif
1.373 +
1.374 + return 0;
1.375 +}
1.376 +
1.377 +static struct atomic_action *
1.378 +atomic_action_move(struct razor_atomic *atomic, struct atomic_action *action)
1.379 +{
1.380 + if (razor_atomic_in_error_state(atomic)) {
1.381 + atomic_action_free(action);
1.382 + return NULL;
1.383 + }
1.384 +
1.385 + if (move_file(atomic, action->args.path, action->args.u.move.dest)) {
1.386 + atomic_action_free(action);
1.387 + return NULL;
1.388 + }
1.389 +
1.390 + return action;
1.391 +}
1.392 +
1.393 +static struct atomic_action *
1.394 +atomic_action_unmove(struct razor_atomic *atomic, struct atomic_action *action)
1.395 +{
1.396 + if (razor_atomic_in_error_state(atomic)) {
1.397 + atomic_action_free(action);
1.398 + return NULL;
1.399 + }
1.400 +
1.401 + if (move_file(atomic, action->args.u.move.dest, action->args.path)) {
1.402 + atomic_action_free(action);
1.403 + return NULL;
1.404 + }
1.405 +
1.406 + return action;
1.407 +}
1.408 +
1.409 +static struct atomic_action *atomic_action_apply(struct razor_atomic *atomic,
1.410 + struct atomic_action *action)
1.411 +{
1.412 + switch(action->type) {
1.413 + case ACTION_MAKE_DIRS:
1.414 + action = atomic_action_make_dirs(atomic, action);
1.415 + break;
1.416 + case ACTION_REMOVE:
1.417 + action = atomic_action_remove(atomic, action);
1.418 + break;
1.419 + case ACTION_CREATE_DIR:
1.420 + action = atomic_action_create_dir(atomic, action);
1.421 + break;
1.422 + case ACTION_MOVE:
1.423 + action = atomic_action_move(atomic, action);
1.424 + break;
1.425 +#if HAVE_SYMLINK
1.426 + case ACTION_CREATE_SYMLINK:
1.427 + action = atomic_action_create_symlink(atomic, action);
1.428 + break;
1.429 +#endif
1.430 + }
1.431 + return action;
1.432 +}
1.433 +
1.434 +static struct atomic_action *atomic_action_reverse(struct razor_atomic *atomic,
1.435 + struct atomic_action *action)
1.436 +{
1.437 + switch(action->type) {
1.438 + case ACTION_MAKE_DIRS:
1.439 + case ACTION_REMOVE:
1.440 + /* Complex actions: should never happen */
1.441 + break;
1.442 + case ACTION_CREATE_DIR:
1.443 + action = atomic_action_rmdir(atomic, action);
1.444 + break;
1.445 + case ACTION_MOVE:
1.446 + action = atomic_action_unmove(atomic, action);
1.447 + break;
1.448 +#if HAVE_SYMLINK
1.449 + case ACTION_CREATE_SYMLINK:
1.450 + action = atomic_action_remove_symlink(atomic, action);
1.451 + break;
1.452 +#endif
1.453 + }
1.454 + return action;
1.455 +}
1.456 +
1.457 +/*
1.458 + * Note that undo has no error checking.
1.459 + */
1.460 +
1.461 +void atomic_action_undo(struct razor_atomic *atomic,
1.462 + struct atomic_action *actions)
1.463 +{
1.464 + struct atomic_action *a;
1.465 +
1.466 + atomic->in_undo = 1;
1.467 +
1.468 + while (actions) {
1.469 + a = atomic_action_list_pop_head(&actions);
1.470 + a = atomic_action_reverse(atomic, a);
1.471 + atomic_action_free(a);
1.472 + }
1.473 +
1.474 + atomic->in_undo = 0;
1.475 +}
1.476 +
1.477 +struct atomic_action *atomic_action_do(struct razor_atomic *atomic,
1.478 + struct atomic_action *actions)
1.479 +{
1.480 + struct atomic_action *done = NULL, *a;
1.481 +
1.482 + if (razor_atomic_in_error_state(atomic)) {
1.483 + atomic_action_free(actions);
1.484 + return NULL;
1.485 + }
1.486 +
1.487 + while (actions) {
1.488 + a = atomic_action_list_pop_head(&actions);
1.489 + a = atomic_action_apply(atomic, a);
1.490 + if (a)
1.491 + done = atomic_action_list_concat(a, done);
1.492 + if (razor_atomic_in_error_state(atomic)) {
1.493 + atomic_action_undo(atomic, done);
1.494 + done = NULL;
1.495 + atomic_action_free(actions);
1.496 + }
1.497 + }
1.498 +
1.499 + return done;
1.500 +}
1.501 +
1.502 +#endif /* ENABLE_ATOMIC && !HAVE_WINDOWS_KTM */