librazor/atomic-ktm.c
author J. Ali Harlow <ali@juiblex.co.uk>
Sat Feb 11 09:49:58 2012 +0000 (2012-02-11)
changeset 422 6fa783097ca1
child 423 6112bcc5d1cf
permissions -rw-r--r--
Pass script failures up to caller
     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 */