librazor/atomic-ktm.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) 2011-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 HAVE_WINDOWS_KTM
    22 
    23 #include <stdlib.h>
    24 #include <windows.h>
    25 #include <stdio.h>
    26 #include <limits.h>
    27 #include <errno.h>
    28 #include <unistd.h>
    29 #include <fcntl.h>
    30 #include <sys/stat.h>
    31 #include <string.h>
    32 #include <assert.h>
    33 #include <wchar.h>
    34 #include <ktmw32.h>
    35 
    36 #include "razor.h"
    37 #include "razor-internal.h"
    38 
    39 #define RAZOR_ASCII_ISALPHA(c)	\
    40 			((c) >= 'A' && (c) <= 'Z' || (c) >= 'a' && (c) <= 'z')
    41 
    42 static int
    43 razor_valid_root_name2(const wchar_t *name)
    44 {
    45 	if (razor_allow_all_root_names())
    46 		return !wcschr(name, '/');
    47 
    48 	return RAZOR_ASCII_ISALPHA(name[0]) && name[1] == ':' &&
    49 	       name[2] == '\0';
    50 }
    51 
    52 struct razor_wstr {
    53 	wchar_t *str;
    54 	int len, allocated;
    55 };
    56 
    57 static struct razor_wstr *
    58 razor_wstr_create(const char *init, int len)
    59 {
    60 	int n;
    61 	struct razor_wstr *wstr;
    62 
    63 	wstr = malloc(sizeof(struct razor_wstr));
    64 
    65 	n = MultiByteToWideChar(CP_UTF8, 0, init, len, NULL, 0);
    66 	if (len >= 0 && init[len])
    67 		wstr->len = n++;
    68 	else
    69 		wstr->len = n - 1;
    70 
    71 	wstr->allocated = n * 2;
    72 	wstr->str = malloc(wstr->allocated * sizeof(wchar_t));
    73 	if (!wstr->str) {
    74 		free(wstr);
    75 		return NULL;
    76 	}
    77 
    78 	(void)MultiByteToWideChar(CP_UTF8, 0, init, len, wstr->str, n);
    79 	if (len >= 0 && init[len])
    80 		wstr->str[wstr->len] = 0;
    81 
    82 	return wstr;
    83 }
    84 
    85 static int
    86 razor_wstr_append(struct razor_wstr *wstr, const char *s, int len)
    87 {
    88 	int n, allocated;
    89 	wchar_t *str;
    90 
    91 	n = MultiByteToWideChar(CP_UTF8, 0, s, len, NULL, 0);
    92 	if (len < 0 || !s[len])
    93 		n--;
    94 
    95 	if (wstr->allocated <= wstr->len + n) {
    96 		allocated = (wstr->len + n + 1) * 2;
    97 		str = realloc(wstr->str, allocated * sizeof(wchar_t));
    98 		if (!str)
    99 			return -1;
   100 		wstr->allocated = allocated;
   101 		wstr->str = str;
   102 	}
   103 
   104 	(void)MultiByteToWideChar(CP_UTF8, 0, s, len, wstr->str + wstr->len, n);
   105 	wstr->len += n;
   106 	wstr->str[wstr->len] = 0;
   107 
   108 	return 0;
   109 }
   110 
   111 static void
   112 razor_wstr_destroy(struct razor_wstr *wstr)
   113 {
   114 	free(wstr->str);
   115 	free(wstr);
   116 }
   117 
   118 RAZOR_EXPORT struct razor_atomic *
   119 razor_atomic_open(const char *description)
   120 {
   121 	wchar_t *buf;
   122 	struct razor_atomic *atomic;
   123 
   124 	atomic = zalloc(sizeof *atomic);
   125 	buf = razor_utf8_to_utf16(description, -1);
   126 	atomic->transaction = CreateTransaction(NULL, 0,
   127 						TRANSACTION_DO_NOT_PROMOTE,
   128 						0, 0, 0, buf);
   129 	free(buf);
   130 
   131 	return atomic;
   132 }
   133 
   134 void
   135 razor_atomic_set_error_str(struct razor_atomic *atomic, const wchar_t *path,
   136 			   const char *str)
   137 {
   138 	assert(!atomic->error_str);
   139 
   140 	free(atomic->error_path);
   141 
   142 	if (path)
   143 		atomic->error_path = razor_utf16_to_utf8(path, -1);
   144 	else
   145 		atomic->error_path = NULL;
   146 
   147 	atomic->error_str = strdup(str);
   148 }
   149 
   150 static void
   151 razor_atomic_set_error(struct razor_atomic *atomic, const wchar_t *path,
   152 		       DWORD error)
   153 {
   154 	wchar_t *buf;
   155 
   156 	assert(!atomic->error_str);
   157 
   158 	free(atomic->error_path);
   159 
   160 	if (path)
   161 		atomic->error_path = razor_utf16_to_utf8(path, -1);
   162 	else
   163 		atomic->error_path = NULL;
   164 
   165 	FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER|
   166 		       FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS,
   167 		       NULL, error, MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT),
   168 		       (LPWSTR)&buf, 0, NULL);
   169 	atomic->error_str = razor_utf16_to_utf8(buf, -1);
   170 	LocalFree(buf);
   171 }
   172 
   173 RAZOR_EXPORT int
   174 razor_atomic_commit(struct razor_atomic *atomic)
   175 {
   176 	int retval;
   177 
   178 	if (razor_atomic_in_error_state(atomic))
   179 		return -1;
   180 
   181 	retval = !CommitTransaction(atomic->transaction);
   182 
   183 	if (retval) {
   184 		razor_atomic_set_error(atomic, NULL, GetLastError());
   185 		RollbackTransaction(atomic->transaction);
   186 	}
   187 
   188 	CloseHandle(atomic->transaction);
   189 	atomic->transaction = INVALID_HANDLE_VALUE;
   190 
   191 	return retval;
   192 }
   193 
   194 RAZOR_EXPORT void
   195 razor_atomic_destroy(struct razor_atomic *atomic)
   196 {
   197 	int i;
   198 
   199 	for(i = 0; i < atomic->n_files; i++) {
   200 		if (atomic->files[i].h != INVALID_HANDLE_VALUE) {
   201 			CloseHandle(atomic->files[i].h);
   202 			free(atomic->files[i].path);
   203 		}
   204 	}
   205 	free(atomic->files);
   206 	if (atomic->transaction != INVALID_HANDLE_VALUE) {
   207 		RollbackTransaction(atomic->transaction);
   208 		CloseHandle(atomic->transaction);
   209 	}
   210 	free(atomic->error_path);
   211 	free(atomic->error_str);
   212 	free(atomic->error_msg);
   213 	free(atomic);
   214 }
   215 
   216 RAZOR_EXPORT int
   217 razor_atomic_make_dirs(struct razor_atomic *atomic, const char *root,
   218 		       const char *path)
   219 {
   220 	struct razor_wstr *buffer;
   221 	const char *slash, *next;
   222 	WIN32_FILE_ATTRIBUTE_DATA fa;
   223 	DWORD err;
   224 	int r, creating = 0;
   225 
   226 	if (razor_atomic_in_error_state(atomic))
   227 		return -1;
   228 
   229 	buffer = razor_wstr_create(root, -1);
   230 	slash = path;
   231 
   232 	for (; *slash != '\0'; slash = next) {
   233 		next = strpbrk(slash + 1, "/\\");
   234 		if (next == NULL)
   235 			break;
   236 
   237 		razor_wstr_append(buffer, slash, next - slash);
   238 
   239 		if (!creating) {
   240 			if (razor_valid_root_name2(buffer->str))
   241 				continue;
   242 
   243 			r = GetFileAttributesTransactedW(buffer->str,
   244 							 GetFileExInfoStandard,
   245 							 &fa,
   246 							 atomic->transaction);
   247 
   248 			if (!r) {
   249 				err = GetLastError();
   250 				if (err == ERROR_FILE_NOT_FOUND) {
   251 					creating = 1;
   252 				} else {
   253 					razor_atomic_set_error(atomic,
   254 							       buffer->str,
   255 							       err);
   256 					razor_wstr_destroy(buffer);
   257 					return -1;
   258 				}
   259 			} else if (!(fa.dwFileAttributes&
   260 				     FILE_ATTRIBUTE_DIRECTORY)) {
   261 				razor_atomic_set_error_str(atomic, buffer->str,
   262 							   "Not a directory");
   263 				razor_wstr_destroy(buffer);
   264 				return -1;
   265 			}
   266 		}
   267 		if (creating) {
   268 			if (!CreateDirectoryTransactedW(NULL, buffer->str, NULL,
   269 							atomic->transaction)) {
   270 				razor_atomic_set_error(atomic, buffer->str,
   271 						       GetLastError());
   272 				razor_wstr_destroy(buffer);
   273 				return -1;
   274 			}
   275 
   276 			/* FIXME: What to do about permissions for dirs we
   277 			 * have to create but are not in the cpio archive? */
   278 		}
   279 	}
   280 
   281 	razor_wstr_destroy(buffer);
   282 
   283 	return 0;
   284 }
   285 
   286 RAZOR_EXPORT int
   287 razor_atomic_remove(struct razor_atomic *atomic, const char *path)
   288 {
   289 	wchar_t *buf;
   290 	DWORD err;
   291 
   292 	if (razor_atomic_in_error_state(atomic))
   293 		return -1;
   294 
   295 	buf = razor_utf8_to_utf16(path, -1);
   296 
   297 	if (DeleteFileTransactedW(buf, atomic->transaction)) {
   298 		free(buf);
   299 		return 0;
   300 	}
   301 
   302 	err = GetLastError();
   303 	if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) {
   304 		free(buf);
   305 		return 0;
   306 	}
   307 
   308 	if (SetFileAttributesTransactedW(buf, FILE_ATTRIBUTE_NORMAL,
   309 					 atomic->transaction)) {
   310 		if (DeleteFileTransactedW(buf, atomic->transaction)) {
   311 			free(buf);
   312 			return 0;
   313 		}
   314 		err = GetLastError();
   315 	}
   316 
   317 	if (RemoveDirectoryTransactedW(buf, atomic->transaction) ||
   318 	    GetLastError() == ERROR_DIR_NOT_EMPTY) {
   319 		free(buf);
   320 		return 0;
   321 	}
   322 
   323 	/*
   324 	 * It would be tempting to use:
   325 	 * 	MoveFileEx(path, NULL, MOVEFILE_DELAY_UNTIL_REBOOT)
   326 	 * but unless we can guarantee that the system will be rebooted
   327 	 * before we (or some other application) write another file with the
   328 	 * same path, this is likely to cause more problems than it solves.
   329 	 */
   330 
   331 	razor_atomic_set_error(atomic, buf, err);
   332 	free(buf);
   333 	return -1;
   334 }
   335 
   336 RAZOR_EXPORT int
   337 razor_atomic_rename_file(struct razor_atomic *atomic, const char *oldpath,
   338 			 const char *newpath)
   339 {
   340 	wchar_t *oldbuf, *newbuf;
   341 	const DWORD flags = MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING;
   342 
   343 	if (razor_atomic_in_error_state(atomic))
   344 		return -1;
   345 
   346 	newbuf = razor_utf8_to_utf16(newpath, -1);
   347 	oldbuf = razor_utf8_to_utf16(oldpath, -1);
   348 
   349 	/*
   350 	 * Passing MOVEFILE_REPLACE_EXISTING to MoveFileTransaction() will
   351 	 * cover every case we care about _except_ replacing an empty
   352 	 * directory with a file. Calling RemoveDirectoryTransacted() will deal
   353 	 * with this case while having no effect in all other cases.
   354 	 */
   355 	(void)RemoveDirectoryTransactedW(newbuf, atomic->transaction);
   356 
   357 	if (!MoveFileTransactedW(oldbuf, newbuf, NULL, NULL, flags,
   358 			         atomic->transaction))
   359 		razor_atomic_set_error(atomic, newbuf, GetLastError());
   360 
   361 	free(newbuf);
   362 	free(oldbuf);
   363 
   364 	return !!atomic->error_str;
   365 }
   366 
   367 RAZOR_EXPORT int
   368 razor_atomic_create_dir(struct razor_atomic *atomic, const char *dirname,
   369 			mode_t mode)
   370 {
   371 	wchar_t *buf;
   372 	DWORD err;
   373 	WIN32_FILE_ATTRIBUTE_DATA fa;
   374 
   375 	if (razor_atomic_in_error_state(atomic))
   376 		return -1;
   377 
   378 	buf = razor_utf8_to_utf16(dirname, -1);
   379 
   380 	if (!CreateDirectoryTransactedW(NULL, buf, NULL, atomic->transaction)) {
   381 		err = GetLastError();
   382 		if (err != ERROR_FILE_EXISTS && err != ERROR_ALREADY_EXISTS) {
   383 abort:
   384 			razor_atomic_set_error(atomic, buf, err);
   385 			free(buf);
   386 			return -1;
   387 		}
   388 
   389 		if (!GetFileAttributesTransactedW(buf, GetFileExInfoStandard,
   390 						  &fa, atomic->transaction))
   391 			goto abort;
   392 
   393 		if (!(fa.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY)) {
   394 			if (razor_atomic_remove(atomic, dirname)) {
   395 				free(buf);
   396 				return -1;
   397 			}
   398 			if (!CreateDirectoryTransactedW(NULL, buf, NULL,
   399 							atomic->transaction)) {
   400 				err = GetLastError();
   401 				goto abort;
   402 			}
   403 		}
   404 	}
   405 
   406 	free(buf);
   407 
   408 	return 0;
   409 }
   410 
   411 RAZOR_EXPORT int
   412 razor_atomic_create_symlink(struct razor_atomic *atomic, const char *target,
   413 			    const char *path)
   414 {
   415 	if (razor_atomic_in_error_state(atomic))
   416 		return -1;
   417 
   418 	/*
   419 	 * This isn't true, but symbolic links under Windows 7
   420 	 * need to know whether the target is a directory or not
   421 	 * and we don't always know that at the time when the
   422 	 * link is created, so it's a convienent lie for now.
   423 	 */
   424 	razor_atomic_set_error_str(atomic, NULL, "Symbolic links not supported "
   425 						 "on this platform");
   426 
   427 	return -1;
   428 }
   429 
   430 RAZOR_EXPORT int
   431 razor_atomic_create_file(struct razor_atomic *atomic, const char *filename,
   432 			 mode_t mode)
   433 {
   434 	DWORD attribs;
   435 	struct razor_atomic_file *files;
   436 	int i = atomic->n_files;
   437 
   438 	if (razor_atomic_in_error_state(atomic))
   439 		return -1;
   440 
   441 	files = realloc(atomic->files,
   442 			(atomic->n_files+1) * sizeof(struct razor_atomic_file));
   443 	if (!files) {
   444 		razor_atomic_set_error_str(atomic, NULL, "Not enough memory");
   445 		return -1;
   446 	}
   447 	atomic->n_files++;
   448 	atomic->files = files;
   449 
   450 	files[i].path = razor_utf8_to_utf16(filename, -1);
   451 
   452 	/*
   453 	 * Passing CREATE_ALWAYS to CreateFileTransacted() will cover
   454 	 * every case we care about _except_ replacing an empty directory
   455 	 * with a file. Calling RemoveDirectoryTransacted() will deal
   456 	 * with this case while having no effect in all other cases.
   457 	 */
   458 	(void)RemoveDirectoryTransactedW(files[i].path, atomic->transaction);
   459 
   460 	if (mode & S_IWUSR)
   461 		attribs = FILE_ATTRIBUTE_NORMAL;
   462 	else
   463 		attribs = FILE_ATTRIBUTE_READONLY;
   464 
   465 	files[i].h = CreateFileTransactedW(files[i].path, GENERIC_WRITE,
   466 					   0, NULL, CREATE_ALWAYS, attribs,
   467 					   NULL, atomic->transaction, NULL,
   468 					   NULL);
   469 
   470 	if (files[i].h == INVALID_HANDLE_VALUE) {
   471 		razor_atomic_set_error(atomic, files[i].path, GetLastError());
   472 		free(files[i].path);
   473 		atomic->n_files--;
   474 		return -1;
   475 	}
   476 
   477 	return i;
   478 }
   479 
   480 RAZOR_EXPORT int
   481 razor_atomic_write(struct razor_atomic *atomic, int handle, const void *data,
   482 		   size_t size)
   483 {
   484 	DWORD written;
   485 
   486 	if (razor_atomic_in_error_state(atomic))
   487 		return -1;
   488 
   489 	assert(handle < atomic->n_files);
   490 	assert(atomic->files[handle].h != INVALID_HANDLE_VALUE);
   491 
   492 	while(size) {
   493 		if (!WriteFile(atomic->files[handle].h, data, size, &written,
   494 			       NULL)) {
   495 			razor_atomic_set_error(atomic,
   496 					       atomic->files[handle].path,
   497 					       GetLastError());
   498 
   499 			(void)CloseHandle(atomic->files[handle].h);
   500 			free(atomic->files[handle].path);
   501 			atomic->files[handle].path = NULL;
   502 			atomic->files[handle].h = INVALID_HANDLE_VALUE;
   503 
   504 			return -1;
   505 		}
   506 
   507 		data += written;
   508 		size -= written;
   509 	}
   510 
   511 	return 0;
   512 }
   513 
   514 RAZOR_EXPORT int
   515 razor_atomic_sync(struct razor_atomic *atomic, int handle)
   516 {
   517 	HANDLE h;
   518 
   519 	if (razor_atomic_in_error_state(atomic))
   520 		return -1;
   521 
   522 	assert(handle < atomic->n_files);
   523 	assert(atomic->files[handle].h != INVALID_HANDLE_VALUE);
   524 
   525 	if (!CloseHandle(atomic->files[handle].h)) {
   526 		razor_atomic_set_error(atomic, atomic->files[handle].path,
   527 				       GetLastError());
   528 		free(atomic->files[handle].path);
   529 		atomic->files[handle].path = NULL;
   530 		atomic->files[handle].h = INVALID_HANDLE_VALUE;
   531 		return -1;
   532 	}
   533 
   534 	h = CreateFileTransactedW(atomic->files[handle].path, GENERIC_WRITE, 0,
   535 				  NULL, OPEN_EXISTING, 0, NULL,
   536 				  atomic->transaction, NULL, NULL);
   537 	atomic->files[handle].h = h;
   538 
   539 	if (atomic->files[handle].h == INVALID_HANDLE_VALUE) {
   540 		razor_atomic_set_error(atomic, atomic->files[handle].path,
   541 				       GetLastError());
   542 		free(atomic->files[handle].path);
   543 		atomic->files[handle].path = NULL;
   544 		return -1;
   545 	}
   546 
   547 	return !!atomic->error_str;
   548 }
   549 
   550 RAZOR_EXPORT int
   551 razor_atomic_close(struct razor_atomic *atomic, int handle)
   552 {
   553 	if (razor_atomic_in_error_state(atomic))
   554 		return -1;
   555 
   556 	assert(handle < atomic->n_files);
   557 	assert(atomic->files[handle].h != INVALID_HANDLE_VALUE);
   558 
   559 	if (!CloseHandle(atomic->files[handle].h))
   560 		razor_atomic_set_error(atomic, atomic->files[handle].path,
   561 				       GetLastError());
   562 
   563 	free(atomic->files[handle].path);
   564 	atomic->files[handle].path = NULL;
   565 	atomic->files[handle].h = INVALID_HANDLE_VALUE;
   566 
   567 	while(atomic->n_files > 0 &&
   568 	      atomic->files[atomic->n_files-1].h == INVALID_HANDLE_VALUE)
   569 		atomic->n_files--;
   570 
   571 	return !!atomic->error_str;
   572 }
   573 
   574 #endif		/* HAVE_WINDOWS_KTM */