librazor/atomic-emulate.c
author J. Ali Harlow <ali@juiblex.co.uk>
Thu Nov 13 10:44:53 2014 +0000 (2014-11-13)
changeset 462 94d7459828ba
parent 458 3f841a46eab5
child 475 008c75a5e08d
permissions -rw-r--r--
Add razor_install_prefix_iterator_create()
     1 /*
     2  * Copyright (C) 2012, 2014  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 <unistd.h>
    27 #include <sys/types.h>
    28 #include <sys/stat.h>
    29 #include <fcntl.h>
    30 #include <dirent.h>
    31 #include <errno.h>
    32 #include <unistd.h>
    33 #include "razor-internal.h"
    34 
    35 /*
    36  * Emulated atomic support
    37  *
    38  * This implementation is better than nothing, but is certainly not atomic.
    39  * It does have a couple of advantages over atomic-none:
    40  *	- If a file operation fails while a package is being installed we
    41  *	  have a good chance of being able to rollback the transaction to
    42  *	  a well-known state.
    43  *	- We behave similarly to atomic-ktm in that changes are not visible
    44  *	  on disk to non-atomic operations (eg., scripts) until the atomic
    45  *	  is committed. This makes the testsuite more likely to pick up
    46  *	  problems that would otherwise only be found when using razor on
    47  *	  an MS-Windows system which supports KTM.
    48  */
    49 
    50 #ifndef O_BINARY
    51 #define O_BINARY	0
    52 #endif
    53 
    54 static void recursive_remove(const char *directory)
    55 {
    56 	DIR *dp;
    57 	struct dirent *dirp;
    58 	char *buf;
    59 
    60 	dp = opendir(directory);
    61 	while((dirp = readdir(dp))) {
    62 		if (strcmp(dirp->d_name, ".") && strcmp(dirp->d_name, "..")) {
    63 			buf = malloc(strlen(directory) + strlen(dirp->d_name)
    64 				     + 2);
    65 			sprintf(buf, "%s/%s", directory, dirp->d_name);
    66 			if (remove(buf) < 0)
    67 				recursive_remove(buf);
    68 			free(buf);
    69 		}
    70 	}
    71 
    72 	closedir(dp);
    73 
    74 	rmdir(directory);
    75 }
    76 
    77 RAZOR_EXPORT struct razor_atomic *razor_atomic_open(const char *description)
    78 {
    79 	struct razor_atomic *atomic;
    80 
    81 	atomic = zalloc(sizeof *atomic);
    82 
    83 	atomic->description = strdup(description);
    84 
    85 	return atomic;
    86 }
    87 
    88 RAZOR_EXPORT int razor_atomic_commit(struct razor_atomic *atomic)
    89 {
    90 	struct atomic_action *actions;
    91 
    92 	if (razor_atomic_in_error_state(atomic))
    93 		return -1;
    94 
    95 	if (atomic->actions) {
    96 		actions = atomic_action_list_reverse(atomic->actions);
    97 		atomic->actions = NULL;
    98 		actions = atomic_action_do(atomic, actions);
    99 		atomic_action_free(actions);
   100 	}
   101 
   102 	if (atomic->toplevel) {
   103 		recursive_remove(atomic->toplevel);
   104 		free(atomic->toplevel);
   105 		atomic->toplevel = NULL;
   106 	}
   107 
   108 	return razor_atomic_in_error_state(atomic);
   109 }
   110 
   111 RAZOR_EXPORT void razor_atomic_destroy(struct razor_atomic *atomic)
   112 {
   113 	if (atomic->actions) {
   114 		atomic_action_free(atomic->actions);
   115 		atomic->actions = NULL;
   116 	}
   117 
   118 	if (atomic->toplevel) {
   119 		recursive_remove(atomic->toplevel);
   120 		free(atomic->toplevel);
   121 		atomic->toplevel = NULL;
   122 	}
   123 
   124 	if (atomic->error)
   125 		razor_error_free(atomic->error);
   126 
   127 	free(atomic->description);
   128 
   129 	free(atomic);
   130 }
   131 
   132 #ifndef MSWIN_API
   133 static char *absolute_path(const char *path)
   134 {
   135 	int len;
   136 	char *result, *subpath, *p, *s, *t;
   137 
   138 	result = realpath(path, NULL);
   139 
   140 	if (!result && errno == ENOENT) {
   141 		p = strdup(path);
   142 		s = strrchr(p, '/');
   143 
   144 		while (s) {
   145 			if (s == p) {
   146 				result = strdup("/");
   147 				break;
   148 			}
   149 
   150 			*s = '\0';
   151 			subpath = realpath(p, NULL);
   152 
   153 			if (subpath) {
   154 				*s = '/';
   155 				len = strlen(subpath);
   156 				result = malloc(len + strlen(s) + 1);
   157 				memcpy(result, subpath, len);
   158 				strcpy(result + len, s);
   159 				free(subpath);
   160 				break;
   161 			} else if (errno != ENOENT)
   162 				break;
   163 
   164 			t = strrchr(p, '/');
   165 			*s = '/';
   166 			s = t;
   167 		}
   168 
   169 		if (!s)
   170 			result = realpath(".", NULL);
   171 
   172 		free(p);
   173 	}
   174 
   175 	return result;
   176 }
   177 #endif
   178 
   179 /*
   180  * We need a toplevel directory in which to hold temporary files
   181  * before they are committed. Since we can generally assume that
   182  * we have write permissions anywhere on the filesystem in
   183  * question, the best location is at the relevant mount point.
   184  * The most common case where this assumption fails is when
   185  * testing, when the current directory is a good choice.
   186  */
   187 
   188 static int
   189 razor_atomic_set_toplevel_from_path(struct razor_atomic *atomic,
   190 				    const char *path)
   191 {
   192 #ifndef MSWIN_API
   193 	dev_t filesystem;
   194 	struct stat buf;
   195 #endif
   196 
   197 	if (razor_atomic_in_error_state(atomic))
   198 		return -1;
   199 
   200 	if (atomic->toplevel)
   201 		return 0;
   202 
   203 #ifdef MSWIN_API
   204 	if (path[0]=='\\' && path[1]=='\\' && path[2] && path[2]!='\\'
   205 	    && strchr(path+3,'\\')) {
   206 		/* We have a UNC path: \\servername\sharename... */
   207 		const char *sharename, *root;
   208 		int disklen;
   209 
   210 		sharename = strchr(path+3,'\\')+1;
   211 		root = strchr(sharename,'\\');
   212 		if (root)
   213 		    disklen = root - path;
   214 		else
   215 		    disklen = strlen(path);
   216 
   217 		atomic->toplevel =
   218 		  malloc(disklen + strlen("\\atomic-XXXXXX") + 1);
   219 		memcpy(atomic->toplevel, path, disklen);
   220 		strcpy(atomic->toplevel + disklen, "\\atomic-XXXXXX");
   221 	} else if ((*path>='A' && *path<='Z' || *path>='a' && *path<='z') &&
   222 		    path[1]==':') {
   223 		atomic->toplevel = strdup("X:\\atomic-XXXXXX");
   224 		*atomic->toplevel = *path;
   225 	} else {
   226 		DWORD n;
   227 		wchar_t *buf;
   228 		char *dir;
   229 
   230 		n = GetCurrentDirectoryW(0, NULL);
   231 		buf = malloc(n * sizeof(wchar_t));
   232 
   233 		if (GetCurrentDirectoryW(n, buf)) {
   234 			dir = razor_utf16_to_utf8(buf, n - 1);
   235 			razor_atomic_set_toplevel_from_path(atomic, dir);
   236 
   237 			free(dir);
   238 			free(buf);
   239 			return;
   240 		} else
   241 			atomic->toplevel = strdup("C:\\atomic-XXXXXX");
   242 
   243 		free(buf);
   244 	}
   245 #else
   246 	{
   247 		/*
   248 		 * Find the mount point (assuming we can write to the
   249 		 * whole filesystem). Otherwise stop at the first
   250 		 * unwritable directory and take one step back.
   251 		 */
   252 		char *s, *abspath, saved;
   253 		int len, can_step_back = 0;
   254 
   255 		abspath = absolute_path(path);
   256 		if (!abspath) {
   257 			atomic->error = razor_error_new_posix(path);
   258 			return -1;
   259 		}
   260 
   261 		if (stat(abspath, &buf) < 0) {
   262 			if (errno == ENOENT)
   263 				filesystem = 0;
   264 			else {
   265 				atomic->error = razor_error_new_posix(abspath);
   266 				free(abspath);
   267 				return -1;
   268 			}
   269 		} else
   270 			filesystem = buf.st_dev;
   271 
   272 		len = strlen(abspath);
   273 		while(len > 1 && (s = strrchr(abspath, '/'))) {
   274 			if (s == abspath) {
   275 				saved = s[1];
   276 				s[1] = '\0';
   277 				len = s + 1 - abspath;
   278 			} else {
   279 				s[0] = '\0';
   280 				len = s - abspath;
   281 			}
   282 
   283 			if (stat(abspath, &buf) < 0) {
   284 				if (errno == ENOENT)
   285 					continue;
   286 				else {
   287 				    atomic->error = razor_error_new_posix(abspath);
   288 				    free(abspath);
   289 				    return -1;
   290 				}
   291 			} else if (!filesystem)
   292 				filesystem = buf.st_dev;
   293 
   294 			if (buf.st_dev != filesystem || access(abspath, W_OK)) {
   295 				if (can_step_back) {
   296 					if (s == abspath)
   297 						s[1] = saved;
   298 					else
   299 						s[0] = '/';
   300 				}
   301 				len = strlen(abspath);
   302 				break;
   303 			} else
   304 				can_step_back = 1;
   305 		}
   306 
   307 		if (len == 1)
   308 			len = 0;	/* Avoid an unslightly double slash. */
   309 		atomic->toplevel = malloc(len + strlen("/.atomic-XXXXXX") + 1);
   310 		memcpy(atomic->toplevel, abspath, len);
   311 		strcpy(atomic->toplevel + len, "/.atomic-XXXXXX");
   312 
   313 		free(abspath);
   314 	}
   315 #endif
   316 
   317 	if (!mkdtemp(atomic->toplevel)) {
   318 		int err = errno;
   319 
   320 #ifdef EACCES
   321 		if (err == EACCES) {
   322 			char *s = strdup("atomic-XXXXXX");
   323 
   324 #ifndef MSWIN_API
   325 			if (stat(".", &buf) < 0) {
   326 				atomic->error = razor_error_new_posix(".");
   327 				free(s);
   328 				free(atomic->toplevel);
   329 				atomic->toplevel = NULL;
   330 				return -1;
   331 			}
   332 			if (buf.st_dev != filesystem)
   333 				/*
   334 				 * Don't use a different filesystem. It will
   335 				 * only fail later on (in rename) and cause
   336 				 * an unhelpful error message (EXDEV).
   337 				 */
   338 				free(s);
   339 			else
   340 #endif
   341 			if (mkdtemp(s)) {
   342 				free(atomic->toplevel);
   343 				atomic->toplevel = s;
   344 				return 0;
   345 			} else
   346 				free(s);
   347 		}
   348 #endif
   349 
   350 		atomic->error = razor_error_new_str(RAZOR_POSIX_ERROR, err,
   351 						    atomic->toplevel,
   352 						    strerror(err));
   353 
   354 		free(atomic->toplevel);
   355 		atomic->toplevel = NULL;
   356 	}
   357 
   358 	return !atomic->toplevel;
   359 }
   360 
   361 RAZOR_EXPORT int
   362 razor_atomic_make_dirs(struct razor_atomic *atomic, const char *root,
   363 		       const char *path)
   364 {
   365 	struct atomic_action *a;
   366 
   367 	razor_atomic_set_toplevel_from_path(atomic, *root ? root : path);
   368 
   369 	if (razor_atomic_in_error_state(atomic))
   370 		return -1;
   371 
   372 	a = atomic_action_new(ACTION_MAKE_DIRS);
   373 	a->args.path = strdup(path);
   374 	a->args.u.make_dirs.root = strdup(root);
   375 	atomic->actions = atomic_action_list_prepend(atomic->actions, a);
   376 
   377 	return 0;
   378 }
   379 
   380 RAZOR_EXPORT int
   381 razor_atomic_remove(struct razor_atomic *atomic, const char *path)
   382 {
   383 	struct atomic_action *a;
   384 
   385 	razor_atomic_set_toplevel_from_path(atomic, path);
   386 
   387 	if (razor_atomic_in_error_state(atomic))
   388 		return -1;
   389 
   390 	a = atomic_action_new(ACTION_REMOVE);
   391 	a->args.path = strdup(path);
   392 	atomic->actions = atomic_action_list_prepend(atomic->actions, a);
   393 
   394 	return 0;
   395 }
   396 
   397 RAZOR_EXPORT int
   398 razor_atomic_rename_file(struct razor_atomic *atomic, const char *oldpath,
   399 			 const char *newpath)
   400 {
   401 	struct atomic_action *a;
   402 
   403 	razor_atomic_set_toplevel_from_path(atomic, newpath);
   404 
   405 	if (razor_atomic_in_error_state(atomic))
   406 		return -1;
   407 
   408 	a = atomic_action_new(ACTION_MOVE);
   409 	a->args.path = strdup(oldpath);
   410 	a->args.u.move.dest = strdup(newpath);
   411 	atomic->actions = atomic_action_list_prepend(atomic->actions, a);
   412 
   413 	return 0;
   414 }
   415 
   416 RAZOR_EXPORT int
   417 razor_atomic_create_dir(struct razor_atomic *atomic, const char *dirname,
   418 			mode_t mode)
   419 {
   420 	struct atomic_action *a;
   421 
   422 	razor_atomic_set_toplevel_from_path(atomic, dirname);
   423 
   424 	if (razor_atomic_in_error_state(atomic))
   425 		return -1;
   426 
   427 	a = atomic_action_new(ACTION_CREATE_DIR);
   428 	a->args.path = strdup(dirname);
   429 	a->args.u.create_dir.mode = mode;
   430 	atomic->actions = atomic_action_list_prepend(atomic->actions, a);
   431 
   432 	return 0;
   433 }
   434 
   435 RAZOR_EXPORT int
   436 razor_atomic_create_symlink(struct razor_atomic *atomic, const char *target,
   437 			    const char *path)
   438 {
   439 #if HAVE_SYMLINK
   440 	struct atomic_action *a;
   441 
   442 	razor_atomic_set_toplevel_from_path(atomic, path);
   443 #endif
   444 
   445 	if (razor_atomic_in_error_state(atomic))
   446 		return -1;
   447 
   448 #if HAVE_SYMLINK
   449 	a = atomic_action_new(ACTION_CREATE_SYMLINK);
   450 	a->args.path = strdup(path);
   451 	a->args.u.create_symlink.target = strdup(target);
   452 	atomic->actions = atomic_action_list_prepend(atomic->actions, a);
   453 
   454 	return 0;
   455 #else
   456 	atomic->error = razor_error_new_str(RAZOR_POSIX_ERROR, ENOSYS, NULL,
   457 					    "Symbolic links not supported "
   458 					    "on this platform");
   459 
   460 	return -1;
   461 #endif
   462 }
   463 
   464 RAZOR_EXPORT int
   465 razor_atomic_create_file(struct razor_atomic *atomic, const char *filename,
   466                          mode_t mode)
   467 {
   468 	int fd;
   469 	struct atomic_action *a;
   470 	char *tmpnam;
   471 
   472 	razor_atomic_set_toplevel_from_path(atomic, filename);
   473 
   474 	if (razor_atomic_in_error_state(atomic))
   475 		return -1;
   476 
   477 	tmpnam = atomic_action_attic_tmpnam(atomic);
   478 	fd = open(tmpnam, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
   479 		  mode & (S_IRWXU | S_IRWXG | S_IRWXO));
   480 
   481 	if (fd == -1)
   482 		atomic->error = razor_error_new_posix(filename);
   483 	else {
   484 		a = atomic_action_new(ACTION_MOVE);
   485 		a->args.path = tmpnam;
   486 		a->args.u.move.dest = strdup(filename);
   487 		atomic->actions = atomic_action_list_prepend(atomic->actions,
   488 							     a);
   489 	}
   490 
   491 	return fd;
   492 }
   493 
   494 #endif	/* ENABLE_ATOMIC && !HAVE_WINDOWS_KTM */