librazor/atomic-emulate.c
author J. Ali Harlow <ali@juiblex.co.uk>
Thu Sep 11 18:54:16 2014 +0100 (2014-09-11)
changeset 448 8476d35b048f
parent 444 a2908416b7cc
child 449 f3baf790a815
permissions -rw-r--r--
Remove prototype for long-removed razor_set_get_package
     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 	rmdir(directory);
    73 }
    74 
    75 RAZOR_EXPORT struct razor_atomic *razor_atomic_open(const char *description)
    76 {
    77 	struct razor_atomic *atomic;
    78 
    79 	atomic = zalloc(sizeof *atomic);
    80 
    81 	atomic->description = strdup(description);
    82 
    83 	return atomic;
    84 }
    85 
    86 RAZOR_EXPORT int razor_atomic_commit(struct razor_atomic *atomic)
    87 {
    88 	struct atomic_action *actions;
    89 
    90 	if (razor_atomic_in_error_state(atomic))
    91 		return -1;
    92 
    93 	if (atomic->actions) {
    94 		actions = atomic_action_list_reverse(atomic->actions);
    95 		atomic->actions = NULL;
    96 		actions = atomic_action_do(atomic, actions);
    97 		atomic_action_free(actions);
    98 	}
    99 
   100 	if (atomic->toplevel) {
   101 		recursive_remove(atomic->toplevel);
   102 		free(atomic->toplevel);
   103 		atomic->toplevel = NULL;
   104 	}
   105 
   106 	return razor_atomic_in_error_state(atomic);
   107 }
   108 
   109 RAZOR_EXPORT void razor_atomic_destroy(struct razor_atomic *atomic)
   110 {
   111 	if (atomic->toplevel) {
   112 		recursive_remove(atomic->toplevel);
   113 		free(atomic->toplevel);
   114 		atomic->toplevel = NULL;
   115 	}
   116 
   117 	if (atomic->error)
   118 		razor_error_free(atomic->error);
   119 
   120 	free(atomic);
   121 }
   122 
   123 #ifndef MSWIN_API
   124 static char *absolute_path(const char *path)
   125 {
   126 	int len;
   127 	char *result, *subpath, *p, *s, *t;
   128 
   129 	result = realpath(path, NULL);
   130 
   131 	if (!result && errno == ENOENT) {
   132 		p = strdup(path);
   133 		s = strrchr(p, '/');
   134 
   135 		while (s) {
   136 			if (s == p) {
   137 				result = strdup("/");
   138 				break;
   139 			}
   140 
   141 			*s = '\0';
   142 			subpath = realpath(p, NULL);
   143 
   144 			if (subpath) {
   145 				*s = '/';
   146 				len = strlen(subpath);
   147 				result = malloc(len + strlen(s) + 1);
   148 				memcpy(result, subpath, len);
   149 				strcpy(result + len, s);
   150 				break;
   151 			} else if (errno != ENOENT)
   152 				break;
   153 
   154 			t = strrchr(p, '/');
   155 			*s = '/';
   156 			s = t;
   157 		}
   158 
   159 		if (!s)
   160 			result = realpath(".", NULL);
   161 
   162 		free(p);
   163 	}
   164 
   165 	return result;
   166 }
   167 #endif
   168 
   169 /*
   170  * We need a toplevel directory in which to hold temporary files
   171  * before they are committed. Since we can generally assume that
   172  * we have write permissions anywhere on the filesystem in
   173  * question, the best location is at the relevant mount point.
   174  * The most common case where this assumption fails is when
   175  * testing, when the current directory is a good choice.
   176  */
   177 
   178 static int
   179 razor_atomic_set_toplevel_from_path(struct razor_atomic *atomic,
   180 				    const char *path)
   181 {
   182 #ifndef MSWIN_API
   183 	dev_t filesystem;
   184 	struct stat buf;
   185 #endif
   186 
   187 	if (razor_atomic_in_error_state(atomic))
   188 		return -1;
   189 
   190 	if (atomic->toplevel)
   191 		return 0;
   192 
   193 #ifdef MSWIN_API
   194 	if (path[0]=='\\' && path[1]=='\\' && path[2] && path[2]!='\\'
   195 	    && strchr(path+3,'\\')) {
   196 		/* We have a UNC path: \\servername\sharename... */
   197 		const char *sharename, *root;
   198 		int disklen;
   199 
   200 		sharename = strchr(path+3,'\\')+1;
   201 		root = strchr(sharename,'\\');
   202 		if (root)
   203 		    disklen = root - path;
   204 		else
   205 		    disklen = strlen(path);
   206 
   207 		atomic->toplevel =
   208 		  malloc(disklen + strlen("\\atomic-XXXXXX") + 1);
   209 		memcpy(atomic->toplevel, path, disklen);
   210 		strcpy(atomic->toplevel + disklen, "\\atomic-XXXXXX");
   211 	} else if ((*path>='A' && *path<='Z' || *path>='a' && *path<='z') &&
   212 		    path[1]==':') {
   213 		atomic->toplevel = strdup("X:\\atomic-XXXXXX");
   214 		*atomic->toplevel = *path;
   215 	} else {
   216 		DWORD n;
   217 		wchar_t *buf;
   218 		char *dir;
   219 
   220 		n = GetCurrentDirectoryW(0, NULL);
   221 		buf = malloc(n * sizeof(wchar_t));
   222 
   223 		if (GetCurrentDirectoryW(n, buf)) {
   224 			dir = razor_utf16_to_utf8(buf, n - 1);
   225 			razor_atomic_set_toplevel_from_path(atomic, dir);
   226 
   227 			free(dir);
   228 			free(buf);
   229 			return;
   230 		} else
   231 			atomic->toplevel = strdup("C:\\atomic-XXXXXX");
   232 
   233 		free(buf);
   234 	}
   235 #else
   236 	{
   237 		/*
   238 		 * Find the mount point (assuming we can write to the
   239 		 * whole filesystem). Otherwise stop at the first
   240 		 * unwritable directory and take one step back.
   241 		 */
   242 		char *s, *abspath, saved;
   243 		int len;
   244 
   245 		abspath = absolute_path(path);
   246 		if (!abspath) {
   247 			atomic->error = razor_error_new_posix(path);
   248 			return -1;
   249 		}
   250 
   251 		if (stat(abspath, &buf) < 0) {
   252 			atomic->error = razor_error_new_posix(abspath);
   253 			free(abspath);
   254 			return -1;
   255 		}
   256 		filesystem = buf.st_dev;
   257 
   258 		len = strlen(abspath);
   259 		while(len > 1 && (s = strrchr(abspath, '/'))) {
   260 			if (s == abspath) {
   261 				saved = s[1];
   262 				s[1] = '\0';
   263 				len = s + 1 - abspath;
   264 			} else {
   265 				s[0] = '\0';
   266 				len = s - abspath;
   267 			}
   268 
   269 			if (stat(abspath, &buf) < 0) {
   270 				atomic->error = razor_error_new_posix(abspath);
   271 				free(abspath);
   272 				return -1;
   273 			}
   274 
   275 			if (buf.st_dev != filesystem || access(abspath, W_OK)) {
   276 				if (s == abspath)
   277 					s[1] = saved;
   278 				else
   279 					s[0] = '/';
   280 				len = strlen(abspath);
   281 				break;
   282 			}
   283 		}
   284 
   285 		if (len == 1)
   286 			len = 0;	/* Avoid an unslightly double slash. */
   287 		atomic->toplevel = malloc(len + strlen("/.atomic-XXXXXX") + 1);
   288 		memcpy(atomic->toplevel, abspath, len);
   289 		strcpy(atomic->toplevel + len, "/.atomic-XXXXXX");
   290 
   291 		free(abspath);
   292 	}
   293 #endif
   294 
   295 	if (!mkdtemp(atomic->toplevel)) {
   296 		int err = errno;
   297 
   298 #ifdef EACCES
   299 		if (err == EACCES) {
   300 			char *s = strdup("atomic-XXXXXX");
   301 
   302 #ifndef MSWIN_API
   303 			if (stat(".", &buf) < 0) {
   304 				atomic->error = razor_error_new_posix(".");
   305 				free(s);
   306 				free(atomic->toplevel);
   307 				atomic->toplevel = NULL;
   308 				return -1;
   309 			}
   310 			if (buf.st_dev != filesystem)
   311 				/*
   312 				 * Don't use a different filesystem. It will
   313 				 * only fail later on (in rename) and cause
   314 				 * an unhelpful error message (EXDEV).
   315 				 */
   316 				free(s);
   317 			else
   318 #endif
   319 			if (mkdtemp(s)) {
   320 				free(atomic->toplevel);
   321 				atomic->toplevel = s;
   322 				return 0;
   323 			} else
   324 				free(s);
   325 		}
   326 #endif
   327 
   328 		atomic->error = razor_error_new_str(RAZOR_POSIX_ERROR, err,
   329 						    atomic->toplevel,
   330 						    strerror(err));
   331 
   332 		free(atomic->toplevel);
   333 		atomic->toplevel = NULL;
   334 	}
   335 
   336 	return !atomic->toplevel;
   337 }
   338 
   339 RAZOR_EXPORT int
   340 razor_atomic_make_dirs(struct razor_atomic *atomic, const char *root,
   341 		       const char *path)
   342 {
   343 	struct atomic_action *a;
   344 
   345 	razor_atomic_set_toplevel_from_path(atomic, *root ? root : path);
   346 
   347 	if (razor_atomic_in_error_state(atomic))
   348 		return -1;
   349 
   350 	a = atomic_action_new(ACTION_MAKE_DIRS);
   351 	a->args.path = strdup(path);
   352 	a->args.u.make_dirs.root = strdup(root);
   353 	atomic->actions = atomic_action_list_prepend(atomic->actions, a);
   354 
   355 	return 0;
   356 }
   357 
   358 RAZOR_EXPORT int
   359 razor_atomic_remove(struct razor_atomic *atomic, const char *path)
   360 {
   361 	struct atomic_action *a;
   362 
   363 	razor_atomic_set_toplevel_from_path(atomic, path);
   364 
   365 	if (razor_atomic_in_error_state(atomic))
   366 		return -1;
   367 
   368 	a = atomic_action_new(ACTION_REMOVE);
   369 	a->args.path = strdup(path);
   370 	atomic->actions = atomic_action_list_prepend(atomic->actions, a);
   371 
   372 	return 0;
   373 }
   374 
   375 RAZOR_EXPORT int
   376 razor_atomic_rename_file(struct razor_atomic *atomic, const char *oldpath,
   377 			 const char *newpath)
   378 {
   379 	struct atomic_action *a;
   380 
   381 	razor_atomic_set_toplevel_from_path(atomic, newpath);
   382 
   383 	if (razor_atomic_in_error_state(atomic))
   384 		return -1;
   385 
   386 	a = atomic_action_new(ACTION_MOVE);
   387 	a->args.path = strdup(oldpath);
   388 	a->args.u.move.dest = strdup(newpath);
   389 	atomic->actions = atomic_action_list_prepend(atomic->actions, a);
   390 
   391 	return 0;
   392 }
   393 
   394 RAZOR_EXPORT int
   395 razor_atomic_create_dir(struct razor_atomic *atomic, const char *dirname,
   396 			mode_t mode)
   397 {
   398 	struct atomic_action *a;
   399 
   400 	razor_atomic_set_toplevel_from_path(atomic, dirname);
   401 
   402 	if (razor_atomic_in_error_state(atomic))
   403 		return -1;
   404 
   405 	a = atomic_action_new(ACTION_CREATE_DIR);
   406 	a->args.path = strdup(dirname);
   407 	a->args.u.create_dir.mode = mode;
   408 	atomic->actions = atomic_action_list_prepend(atomic->actions, a);
   409 
   410 	return 0;
   411 }
   412 
   413 RAZOR_EXPORT int
   414 razor_atomic_create_symlink(struct razor_atomic *atomic, const char *target,
   415 			    const char *path)
   416 {
   417 #if HAVE_SYMLINK
   418 	struct atomic_action *a;
   419 
   420 	razor_atomic_set_toplevel_from_path(atomic, path);
   421 #endif
   422 
   423 	if (razor_atomic_in_error_state(atomic))
   424 		return -1;
   425 
   426 #if HAVE_SYMLINK
   427 	a = atomic_action_new(ACTION_CREATE_SYMLINK);
   428 	a->args.path = strdup(path);
   429 	a->args.u.create_symlink.target = strdup(target);
   430 	atomic->actions = atomic_action_list_prepend(atomic->actions, a);
   431 
   432 	return 0;
   433 #else
   434 	atomic->error = razor_error_new_str(RAZOR_POSIX_ERROR, ENOSYS, NULL,
   435 					    "Symbolic links not supported "
   436 					    "on this platform");
   437 
   438 	return -1;
   439 #endif
   440 }
   441 
   442 RAZOR_EXPORT int
   443 razor_atomic_create_file(struct razor_atomic *atomic, const char *filename,
   444                          mode_t mode)
   445 {
   446 	int fd;
   447 	struct atomic_action *a;
   448 	char *tmpnam;
   449 
   450 	razor_atomic_set_toplevel_from_path(atomic, filename);
   451 
   452 	if (razor_atomic_in_error_state(atomic))
   453 		return -1;
   454 
   455 	tmpnam = atomic_action_attic_tmpnam(atomic);
   456 	fd = open(tmpnam, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
   457 		  mode & (S_IRWXU | S_IRWXG | S_IRWXO));
   458 
   459 	if (fd == -1)
   460 		atomic->error = razor_error_new_posix(filename);
   461 	else {
   462 		a = atomic_action_new(ACTION_MOVE);
   463 		a->args.path = tmpnam;
   464 		a->args.u.move.dest = strdup(filename);
   465 		atomic->actions = atomic_action_list_prepend(atomic->actions,
   466 							     a);
   467 	}
   468 
   469 	return fd;
   470 }
   471 
   472 #endif	/* ENABLE_ATOMIC && !HAVE_WINDOWS_KTM */