summaryrefslogtreecommitdiffstats
path: root/lib/libarchive/archive_write_disk_set_standard_lookup.c
blob: 1842cbd8f06961c3ed6177dff0c0f1bb0645ee0a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
/*-
 * Copyright (c) 2003-2007 Tim Kientzle
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "archive_platform.h"
__FBSDID("$FreeBSD$");

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#ifdef HAVE_GRP_H
#include <grp.h>
#endif
#ifdef HAVE_PWD_H
#include <pwd.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif

#include "archive.h"
#include "archive_private.h"
#include "archive_read_private.h"
#include "archive_write_disk_private.h"

struct bucket {
	char	*name;
	int	 hash;
	id_t	 id;
};

static const size_t cache_size = 127;
static unsigned int	hash(const char *);
static gid_t	lookup_gid(void *, const char *uname, gid_t);
static uid_t	lookup_uid(void *, const char *uname, uid_t);
static void	cleanup(void *);

/*
 * Installs functions that use getpwnam()/getgrnam()---along with
 * a simple cache to accelerate such lookups---into the archive_write_disk
 * object.  This is in a separate file because getpwnam()/getgrnam()
 * can pull in a LOT of library code (including NIS/LDAP functions, which
 * pull in DNS resolveers, etc).  This can easily top 500kB, which makes
 * it inappropriate for some space-constrained applications.
 *
 * Applications that are size-sensitive may want to just use the
 * real default functions (defined in archive_write_disk.c) that just
 * use the uid/gid without the lookup.  Or define your own custom functions
 * if you prefer.
 *
 * TODO: Replace these hash tables with simpler move-to-front LRU
 * lists with a bounded size (128 items?).  The hash is a bit faster,
 * but has a bad pathology in which it thrashes a single bucket.  Even
 * walking a list of 128 items is a lot faster than calling
 * getpwnam()!
 */
int
archive_write_disk_set_standard_lookup(struct archive *a)
{
	struct bucket *ucache = malloc(cache_size * sizeof(struct bucket));
	struct bucket *gcache = malloc(cache_size * sizeof(struct bucket));
	memset(ucache, 0, cache_size * sizeof(struct bucket));
	memset(gcache, 0, cache_size * sizeof(struct bucket));
	archive_write_disk_set_group_lookup(a, gcache, lookup_gid, cleanup);
	archive_write_disk_set_user_lookup(a, ucache, lookup_uid, cleanup);
	return (ARCHIVE_OK);
}

static gid_t
lookup_gid(void *private_data, const char *gname, gid_t gid)
{
	int h;
	struct bucket *b;
	struct bucket *gcache = (struct bucket *)private_data;

	/* If no gname, just use the gid provided. */
	if (gname == NULL || *gname == '\0')
		return (gid);

	/* Try to find gname in the cache. */
	h = hash(gname);
	b = &gcache[h % cache_size ];
	if (b->name != NULL && b->hash == h && strcmp(gname, b->name) == 0)
		return ((gid_t)b->id);

	/* Free the cache slot for a new entry. */
	if (b->name != NULL)
		free(b->name);
	b->name = strdup(gname);
	/* Note: If strdup fails, that's okay; we just won't cache. */
	b->hash = h;
#if HAVE_GRP_H
	{
		char _buffer[128];
		size_t bufsize = 128;
		char *buffer = _buffer;
		struct group	grent, *result;
		int r;

		for (;;) {
			result = &grent; /* Old getgrnam_r ignores last arg. */
			r = getgrnam_r(gname, &grent, buffer, bufsize, &result);
			if (r == 0)
				break;
			if (r != ERANGE)
				break;
			bufsize *= 2;
			if (buffer != _buffer)
				free(buffer);
			buffer = malloc(bufsize);
			if (buffer == NULL)
				break;
		}
		if (result != NULL)
			gid = result->gr_gid;
		if (buffer != _buffer)
			free(buffer);
	}
#elif defined(_WIN32) && !defined(__CYGWIN__)
	/* TODO: do a gname->gid lookup for Windows. */
#else
	#error No way to perform gid lookups on this platform
#endif
	b->id = gid;

	return (gid);
}

static uid_t
lookup_uid(void *private_data, const char *uname, uid_t uid)
{
	int h;
	struct bucket *b;
	struct bucket *ucache = (struct bucket *)private_data;

	/* If no uname, just use the uid provided. */
	if (uname == NULL || *uname == '\0')
		return (uid);

	/* Try to find uname in the cache. */
	h = hash(uname);
	b = &ucache[h % cache_size ];
	if (b->name != NULL && b->hash == h && strcmp(uname, b->name) == 0)
		return ((uid_t)b->id);

	/* Free the cache slot for a new entry. */
	if (b->name != NULL)
		free(b->name);
	b->name = strdup(uname);
	/* Note: If strdup fails, that's okay; we just won't cache. */
	b->hash = h;
#if HAVE_PWD_H
	{
		char _buffer[128];
		size_t bufsize = 128;
		char *buffer = _buffer;
		struct passwd	pwent, *result;
		int r;

		for (;;) {
			result = &pwent; /* Old getpwnam_r ignores last arg. */
			r = getpwnam_r(uname, &pwent, buffer, bufsize, &result);
			if (r == 0)
				break;
			if (r != ERANGE)
				break;
			bufsize *= 2;
			if (buffer != _buffer)
				free(buffer);
			buffer = malloc(bufsize);
			if (buffer == NULL)
				break;
		}
		if (result != NULL)
			uid = result->pw_uid;
		if (buffer != _buffer)
			free(buffer);
	}
#elif defined(_WIN32) && !defined(__CYGWIN__)
	/* TODO: do a uname->uid lookup for Windows. */
#else
	#error No way to look up uids on this platform
#endif
	b->id = uid;

	return (uid);
}

static void
cleanup(void *private)
{
	size_t i;
	struct bucket *cache = (struct bucket *)private;

	for (i = 0; i < cache_size; i++)
		free(cache[i].name);
	free(cache);
}


static unsigned int
hash(const char *p)
{
	/* A 32-bit version of Peter Weinberger's (PJW) hash algorithm,
	   as used by ELF for hashing function names. */
	unsigned g, h = 0;
	while (*p != '\0') {
		h = (h << 4) + *p++;
		if ((g = h & 0xF0000000) != 0) {
			h ^= g >> 24;
			h &= 0x0FFFFFFF;
		}
	}
	return h;
}
OpenPOWER on IntegriCloud