librazor/atomic-emulate.c
author J. Ali Harlow <ali@juiblex.co.uk>
Sat Oct 04 18:12:58 2014 +0100 (2014-10-04)
changeset 454 56ff755c268c
parent 447 0a5e583393e1
child 458 3f841a46eab5
permissions -rw-r--r--
Only export symbols starting with razor_ in dynamic library.

Apart from being good practice to avoid clashes with higher-level
libraries and the application, this also fixes an obscure bug: The
gnulib library is used both by librazor (the dynamic library) and
by razor (the executable). In doing so, we want to have two separate
copies of the library despite the code duplication this involves.
Without the explicit limit to export only razor_ symbols, the razor
executable under mingw64 was picking up the getopt_long function
from librazor and the optind variable from libgnu which meant that
it did not see optind changing. Hiding librazor's copy of getopt
causes the linker to find libgnu's copy and everything works.

Note that under mingw librazor-#.dll still contains undocumented
(private) razor_ symbols but these will do no harm as long as nobody
tries to use them.
     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, can_step_back = 0;
   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 			if (errno == ENOENT)
   253 				filesystem = 0;
   254 			else {
   255 				atomic->error = razor_error_new_posix(abspath);
   256 				free(abspath);
   257 				return -1;
   258 			}
   259 		} else
   260 			filesystem = buf.st_dev;
   261 
   262 		len = strlen(abspath);
   263 		while(len > 1 && (s = strrchr(abspath, '/'))) {
   264 			if (s == abspath) {
   265 				saved = s[1];
   266 				s[1] = '\0';
   267 				len = s + 1 - abspath;
   268 			} else {
   269 				s[0] = '\0';
   270 				len = s - abspath;
   271 			}
   272 
   273 			if (stat(abspath, &buf) < 0) {
   274 				if (errno == ENOENT)
   275 					continue;
   276 				else {
   277 				    atomic->error = razor_error_new_posix(abspath);
   278 				    free(abspath);
   279 				    return -1;
   280 				}
   281 			} else if (!filesystem)
   282 				filesystem = buf.st_dev;
   283 
   284 			if (buf.st_dev != filesystem || access(abspath, W_OK)) {
   285 				if (can_step_back) {
   286 					if (s == abspath)
   287 						s[1] = saved;
   288 					else
   289 						s[0] = '/';
   290 				}
   291 				len = strlen(abspath);
   292 				break;
   293 			} else
   294 				can_step_back = 1;
   295 		}
   296 
   297 		if (len == 1)
   298 			len = 0;	/* Avoid an unslightly double slash. */
   299 		atomic->toplevel = malloc(len + strlen("/.atomic-XXXXXX") + 1);
   300 		memcpy(atomic->toplevel, abspath, len);
   301 		strcpy(atomic->toplevel + len, "/.atomic-XXXXXX");
   302 
   303 		free(abspath);
   304 	}
   305 #endif
   306 
   307 	if (!mkdtemp(atomic->toplevel)) {
   308 		int err = errno;
   309 
   310 #ifdef EACCES
   311 		if (err == EACCES) {
   312 			char *s = strdup("atomic-XXXXXX");
   313 
   314 #ifndef MSWIN_API
   315 			if (stat(".", &buf) < 0) {
   316 				atomic->error = razor_error_new_posix(".");
   317 				free(s);
   318 				free(atomic->toplevel);
   319 				atomic->toplevel = NULL;
   320 				return -1;
   321 			}
   322 			if (buf.st_dev != filesystem)
   323 				/*
   324 				 * Don't use a different filesystem. It will
   325 				 * only fail later on (in rename) and cause
   326 				 * an unhelpful error message (EXDEV).
   327 				 */
   328 				free(s);
   329 			else
   330 #endif
   331 			if (mkdtemp(s)) {
   332 				free(atomic->toplevel);
   333 				atomic->toplevel = s;
   334 				return 0;
   335 			} else
   336 				free(s);
   337 		}
   338 #endif
   339 
   340 		atomic->error = razor_error_new_str(RAZOR_POSIX_ERROR, err,
   341 						    atomic->toplevel,
   342 						    strerror(err));
   343 
   344 		free(atomic->toplevel);
   345 		atomic->toplevel = NULL;
   346 	}
   347 
   348 	return !atomic->toplevel;
   349 }
   350 
   351 RAZOR_EXPORT int
   352 razor_atomic_make_dirs(struct razor_atomic *atomic, const char *root,
   353 		       const char *path)
   354 {
   355 	struct atomic_action *a;
   356 
   357 	razor_atomic_set_toplevel_from_path(atomic, *root ? root : path);
   358 
   359 	if (razor_atomic_in_error_state(atomic))
   360 		return -1;
   361 
   362 	a = atomic_action_new(ACTION_MAKE_DIRS);
   363 	a->args.path = strdup(path);
   364 	a->args.u.make_dirs.root = strdup(root);
   365 	atomic->actions = atomic_action_list_prepend(atomic->actions, a);
   366 
   367 	return 0;
   368 }
   369 
   370 RAZOR_EXPORT int
   371 razor_atomic_remove(struct razor_atomic *atomic, const char *path)
   372 {
   373 	struct atomic_action *a;
   374 
   375 	razor_atomic_set_toplevel_from_path(atomic, path);
   376 
   377 	if (razor_atomic_in_error_state(atomic))
   378 		return -1;
   379 
   380 	a = atomic_action_new(ACTION_REMOVE);
   381 	a->args.path = strdup(path);
   382 	atomic->actions = atomic_action_list_prepend(atomic->actions, a);
   383 
   384 	return 0;
   385 }
   386 
   387 RAZOR_EXPORT int
   388 razor_atomic_rename_file(struct razor_atomic *atomic, const char *oldpath,
   389 			 const char *newpath)
   390 {
   391 	struct atomic_action *a;
   392 
   393 	razor_atomic_set_toplevel_from_path(atomic, newpath);
   394 
   395 	if (razor_atomic_in_error_state(atomic))
   396 		return -1;
   397 
   398 	a = atomic_action_new(ACTION_MOVE);
   399 	a->args.path = strdup(oldpath);
   400 	a->args.u.move.dest = strdup(newpath);
   401 	atomic->actions = atomic_action_list_prepend(atomic->actions, a);
   402 
   403 	return 0;
   404 }
   405 
   406 RAZOR_EXPORT int
   407 razor_atomic_create_dir(struct razor_atomic *atomic, const char *dirname,
   408 			mode_t mode)
   409 {
   410 	struct atomic_action *a;
   411 
   412 	razor_atomic_set_toplevel_from_path(atomic, dirname);
   413 
   414 	if (razor_atomic_in_error_state(atomic))
   415 		return -1;
   416 
   417 	a = atomic_action_new(ACTION_CREATE_DIR);
   418 	a->args.path = strdup(dirname);
   419 	a->args.u.create_dir.mode = mode;
   420 	atomic->actions = atomic_action_list_prepend(atomic->actions, a);
   421 
   422 	return 0;
   423 }
   424 
   425 RAZOR_EXPORT int
   426 razor_atomic_create_symlink(struct razor_atomic *atomic, const char *target,
   427 			    const char *path)
   428 {
   429 #if HAVE_SYMLINK
   430 	struct atomic_action *a;
   431 
   432 	razor_atomic_set_toplevel_from_path(atomic, path);
   433 #endif
   434 
   435 	if (razor_atomic_in_error_state(atomic))
   436 		return -1;
   437 
   438 #if HAVE_SYMLINK
   439 	a = atomic_action_new(ACTION_CREATE_SYMLINK);
   440 	a->args.path = strdup(path);
   441 	a->args.u.create_symlink.target = strdup(target);
   442 	atomic->actions = atomic_action_list_prepend(atomic->actions, a);
   443 
   444 	return 0;
   445 #else
   446 	atomic->error = razor_error_new_str(RAZOR_POSIX_ERROR, ENOSYS, NULL,
   447 					    "Symbolic links not supported "
   448 					    "on this platform");
   449 
   450 	return -1;
   451 #endif
   452 }
   453 
   454 RAZOR_EXPORT int
   455 razor_atomic_create_file(struct razor_atomic *atomic, const char *filename,
   456                          mode_t mode)
   457 {
   458 	int fd;
   459 	struct atomic_action *a;
   460 	char *tmpnam;
   461 
   462 	razor_atomic_set_toplevel_from_path(atomic, filename);
   463 
   464 	if (razor_atomic_in_error_state(atomic))
   465 		return -1;
   466 
   467 	tmpnam = atomic_action_attic_tmpnam(atomic);
   468 	fd = open(tmpnam, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
   469 		  mode & (S_IRWXU | S_IRWXG | S_IRWXO));
   470 
   471 	if (fd == -1)
   472 		atomic->error = razor_error_new_posix(filename);
   473 	else {
   474 		a = atomic_action_new(ACTION_MOVE);
   475 		a->args.path = tmpnam;
   476 		a->args.u.move.dest = strdup(filename);
   477 		atomic->actions = atomic_action_list_prepend(atomic->actions,
   478 							     a);
   479 	}
   480 
   481 	return fd;
   482 }
   483 
   484 #endif	/* ENABLE_ATOMIC && !HAVE_WINDOWS_KTM */