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