1 /*-
2  * See the file LICENSE for redistribution information.
3  *
4  * Copyright (c) 1997, 1998
5  *	Sleepycat Software.  All rights reserved.
6  */
7 
8 #include "config.h"
9 
10 #ifndef lint
11 static const char sccsid[] = "@(#)log_archive.c	10.44 (Sleepycat) 10/9/98";
12 #endif /* not lint */
13 
14 #ifndef NO_SYSTEM_INCLUDES
15 #include <sys/types.h>
16 
17 #include <errno.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <unistd.h>
21 #endif
22 
23 #include "db_int.h"
24 #include "db_dispatch.h"
25 #include "shqueue.h"
26 #include "log.h"
27 #include "common_ext.h"
28 #include "clib_ext.h"			/* XXX: needed for getcwd. */
29 
30 static int __absname __P((char *, char *, char **));
31 static int __build_data __P((DB_LOG *, char *, char ***, void *(*)(size_t)));
32 static int __cmpfunc __P((const void *, const void *));
33 static int __usermem __P((char ***, void *(*)(size_t)));
34 
35 /*
36  * log_archive --
37  *	Supporting function for db_archive(1).
38  */
39 int
log_archive(dblp,listp,flags,db_malloc)40 log_archive(dblp, listp, flags, db_malloc)
41 	DB_LOG *dblp;
42 	char ***listp;
43 	u_int32_t flags;
44 	void *(*db_malloc) __P((size_t));
45 {
46 	DBT rec;
47 	DB_LSN stable_lsn;
48 	u_int32_t fnum;
49 	int array_size, n, ret;
50 	char **array, **arrayp, *name, *p, *pref, buf[MAXPATHLEN];
51 
52 	name = NULL;
53 	COMPQUIET(fnum, 0);
54 
55 	LOG_PANIC_CHECK(dblp);
56 
57 #define	OKFLAGS	(DB_ARCH_ABS | DB_ARCH_DATA | DB_ARCH_LOG)
58 	if (flags != 0) {
59 		if ((ret =
60 		    __db_fchk(dblp->dbenv, "log_archive", flags, OKFLAGS)) != 0)
61 			return (ret);
62 		if ((ret =
63 		    __db_fcchk(dblp->dbenv,
64 		        "log_archive", flags, DB_ARCH_DATA, DB_ARCH_LOG)) != 0)
65 			return (ret);
66 	}
67 
68 	/*
69 	 * Get the absolute pathname of the current directory.  It would
70 	 * be nice to get the shortest pathname of the database directory,
71 	 * but that's just not possible.
72 	 */
73 	if (LF_ISSET(DB_ARCH_ABS)) {
74 		errno = 0;
75 		if ((pref = getcwd(buf, sizeof(buf))) == NULL)
76 			return (errno == 0 ? ENOMEM : errno);
77 	} else
78 		pref = NULL;
79 
80 	switch (LF_ISSET(~DB_ARCH_ABS)) {
81 	case DB_ARCH_DATA:
82 		return (__build_data(dblp, pref, listp, db_malloc));
83 	case DB_ARCH_LOG:
84 		memset(&rec, 0, sizeof(rec));
85 		if (F_ISSET(dblp, DB_AM_THREAD))
86 			F_SET(&rec, DB_DBT_MALLOC);
87 		if ((ret = log_get(dblp, &stable_lsn, &rec, DB_LAST)) != 0)
88 			return (ret);
89 		if (F_ISSET(dblp, DB_AM_THREAD))
90 			__os_free(rec.data, rec.size);
91 		fnum = stable_lsn.file;
92 		break;
93 	case 0:
94 		if ((ret = __log_findckp(dblp, &stable_lsn)) != 0) {
95 			/*
96 			 * A return of DB_NOTFOUND means that we didn't find
97 			 * any records in the log (so we are not going to be
98 			 * deleting any log files).
99 			 */
100 			if (ret != DB_NOTFOUND)
101 				return (ret);
102 			*listp = NULL;
103 			return (0);
104 		}
105 		/* Remove any log files before the last stable LSN. */
106 		fnum = stable_lsn.file - 1;
107 		break;
108 	}
109 
110 #define	LIST_INCREMENT	64
111 	/* Get some initial space. */
112 	array_size = 10;
113 	if ((ret = __os_malloc(sizeof(char *) * array_size, NULL, &array)) != 0)
114 		return (ret);
115 	array[0] = NULL;
116 
117 	/* Build an array of the file names. */
118 	for (n = 0; fnum > 0; --fnum) {
119 		if ((ret = __log_name(dblp, fnum, &name, NULL, 0)) != 0)
120 			goto err;
121 		if (__os_exists(name, NULL) != 0) {
122 			__os_freestr(name);
123 			name = NULL;
124 			break;
125 		}
126 
127 		if (n >= array_size - 1) {
128 			array_size += LIST_INCREMENT;
129 			if ((ret = __os_realloc(&array,
130 			    sizeof(char *) * array_size)) != 0)
131 				goto err;
132 		}
133 
134 		if (LF_ISSET(DB_ARCH_ABS)) {
135 			if ((ret = __absname(pref, name, &array[n])) != 0)
136 				goto err;
137 			__os_freestr(name);
138 		} else if ((p = __db_rpath(name)) != NULL) {
139 			if ((ret = __os_strdup(p + 1, &array[n])) != 0)
140 				goto err;
141 			__os_freestr(name);
142 		} else
143 			array[n] = name;
144 
145 		name = NULL;
146 		array[++n] = NULL;
147 	}
148 
149 	/* If there's nothing to return, we're done. */
150 	if (n == 0) {
151 		*listp = NULL;
152 		ret = 0;
153 		goto err;
154 	}
155 
156 	/* Sort the list. */
157 	qsort(array, (size_t)n, sizeof(char *), __cmpfunc);
158 
159 	/* Rework the memory. */
160 	if ((ret = __usermem(&array, db_malloc)) != 0)
161 		goto err;
162 
163 	*listp = array;
164 	return (0);
165 
166 err:	if (array != NULL) {
167 		for (arrayp = array; *arrayp != NULL; ++arrayp)
168 			__os_freestr(*arrayp);
169 		__os_free(array, sizeof(char *) * array_size);
170 	}
171 	if (name != NULL)
172 		__os_freestr(name);
173 	return (ret);
174 }
175 
176 /*
177  * __build_data --
178  *	Build a list of datafiles for return.
179  */
180 static int
__build_data(dblp,pref,listp,db_malloc)181 __build_data(dblp, pref, listp, db_malloc)
182 	DB_LOG *dblp;
183 	char *pref, ***listp;
184 	void *(*db_malloc) __P((size_t));
185 {
186 	DBT rec;
187 	DB_LSN lsn;
188 	__log_register_args *argp;
189 	u_int32_t rectype;
190 	int array_size, last, n, nxt, ret;
191 	char **array, **arrayp, *p, *real_name;
192 
193 	/* Get some initial space. */
194 	array_size = 10;
195 	if ((ret = __os_malloc(sizeof(char *) * array_size, NULL, &array)) != 0)
196 		return (ret);
197 	array[0] = NULL;
198 
199 	memset(&rec, 0, sizeof(rec));
200 	if (F_ISSET(dblp, DB_AM_THREAD))
201 		F_SET(&rec, DB_DBT_MALLOC);
202 	for (n = 0, ret = log_get(dblp, &lsn, &rec, DB_FIRST);
203 	    ret == 0; ret = log_get(dblp, &lsn, &rec, DB_NEXT)) {
204 		if (rec.size < sizeof(rectype)) {
205 			ret = EINVAL;
206 			__db_err(dblp->dbenv, "log_archive: bad log record");
207 			goto lg_free;
208 		}
209 
210 		memcpy(&rectype, rec.data, sizeof(rectype));
211 		if (rectype != DB_log_register) {
212 			if (F_ISSET(dblp, DB_AM_THREAD)) {
213 				__os_free(rec.data, rec.size);
214 				rec.data = NULL;
215 			}
216 			continue;
217 		}
218 		if ((ret = __log_register_read(rec.data, &argp)) != 0) {
219 			ret = EINVAL;
220 			__db_err(dblp->dbenv,
221 			    "log_archive: unable to read log record");
222 			goto lg_free;
223 		}
224 
225 		if (n >= array_size - 1) {
226 			array_size += LIST_INCREMENT;
227 			if ((ret = __os_realloc(&array,
228 			    sizeof(char *) * array_size)) != 0)
229 				goto lg_free;
230 		}
231 
232 		if ((ret = __os_strdup(argp->name.data, &array[n])) != 0) {
233 lg_free:		if (F_ISSET(&rec, DB_DBT_MALLOC) && rec.data != NULL)
234 				__os_free(rec.data, rec.size);
235 			goto err1;
236 		}
237 
238 		array[++n] = NULL;
239 		__os_free(argp, 0);
240 
241 		if (F_ISSET(dblp, DB_AM_THREAD)) {
242 			__os_free(rec.data, rec.size);
243 			rec.data = NULL;
244 		}
245 	}
246 
247 	/* If there's nothing to return, we're done. */
248 	if (n == 0) {
249 		ret = 0;
250 		*listp = NULL;
251 		goto err1;
252 	}
253 
254 	/* Sort the list. */
255 	qsort(array, (size_t)n, sizeof(char *), __cmpfunc);
256 
257 	/*
258 	 * Build the real pathnames, discarding nonexistent files and
259 	 * duplicates.
260 	 */
261 	for (last = nxt = 0; nxt < n;) {
262 		/*
263 		 * Discard duplicates.  Last is the next slot we're going
264 		 * to return to the user, nxt is the next slot that we're
265 		 * going to consider.
266 		 */
267 		if (last != nxt) {
268 			array[last] = array[nxt];
269 			array[nxt] = NULL;
270 		}
271 		for (++nxt; nxt < n &&
272 		    strcmp(array[last], array[nxt]) == 0; ++nxt) {
273 			__os_freestr(array[nxt]);
274 			array[nxt] = NULL;
275 		}
276 
277 		/* Get the real name. */
278 		if ((ret = __db_appname(dblp->dbenv,
279 		    DB_APP_DATA, NULL, array[last], 0, NULL, &real_name)) != 0)
280 			goto err2;
281 
282 		/* If the file doesn't exist, ignore it. */
283 		if (__os_exists(real_name, NULL) != 0) {
284 			__os_freestr(real_name);
285 			__os_freestr(array[last]);
286 			array[last] = NULL;
287 			continue;
288 		}
289 
290 		/* Rework the name as requested by the user. */
291 		__os_freestr(array[last]);
292 		array[last] = NULL;
293 		if (pref != NULL) {
294 			ret = __absname(pref, real_name, &array[last]);
295 			__os_freestr(real_name);
296 			if (ret != 0)
297 				goto err2;
298 		} else if ((p = __db_rpath(real_name)) != NULL) {
299 			ret = __os_strdup(p + 1, &array[last]);
300 			__os_freestr(real_name);
301 			if (ret != 0)
302 				goto err2;
303 		} else
304 			array[last] = real_name;
305 		++last;
306 	}
307 
308 	/* NULL-terminate the list. */
309 	array[last] = NULL;
310 
311 	/* Rework the memory. */
312 	if ((ret = __usermem(&array, db_malloc)) != 0)
313 		goto err1;
314 
315 	*listp = array;
316 	return (0);
317 
318 err2:	/*
319 	 * XXX
320 	 * We've possibly inserted NULLs into the array list, so clean up a
321 	 * bit so that the other error processing works.
322 	 */
323 	if (array != NULL)
324 		for (; nxt < n; ++nxt)
325 			__os_freestr(array[nxt]);
326 	/* FALLTHROUGH */
327 
328 err1:	if (array != NULL) {
329 		for (arrayp = array; *arrayp != NULL; ++arrayp)
330 			__os_freestr(*arrayp);
331 		__os_free(array, array_size * sizeof(char *));
332 	}
333 	return (ret);
334 }
335 
336 /*
337  * __absname --
338  *	Return an absolute path name for the file.
339  */
340 static int
__absname(pref,name,newnamep)341 __absname(pref, name, newnamep)
342 	char *pref, *name, **newnamep;
343 {
344 	size_t l_pref, l_name;
345 	int isabspath, ret;
346 	char *newname;
347 
348 	l_name = strlen(name);
349 	isabspath = __os_abspath(name);
350 	l_pref = isabspath ? 0 : strlen(pref);
351 
352 	/* Malloc space for concatenating the two. */
353 	if ((ret = __os_malloc(l_pref + l_name + 2, NULL, &newname)) != 0)
354 		return (ret);
355 	*newnamep = newname;
356 
357 	/* Build the name.  If `name' is an absolute path, ignore any prefix. */
358 	if (!isabspath) {
359 		memcpy(newname, pref, l_pref);
360 		if (strchr(PATH_SEPARATOR, newname[l_pref - 1]) == NULL)
361 			newname[l_pref++] = PATH_SEPARATOR[0];
362 	}
363 	memcpy(newname + l_pref, name, l_name + 1);
364 
365 	return (0);
366 }
367 
368 /*
369  * __usermem --
370  *	Create a single chunk of memory that holds the returned information.
371  *	If the user has their own malloc routine, use it.
372  */
373 static int
__usermem(listp,db_malloc)374 __usermem(listp, db_malloc)
375 	char ***listp;
376 	void *(*db_malloc) __P((size_t));
377 {
378 	size_t len;
379 	int ret;
380 	char **array, **arrayp, **orig, *strp;
381 
382 	/* Find out how much space we need. */
383 	for (len = 0, orig = *listp; *orig != NULL; ++orig)
384 		len += sizeof(char *) + strlen(*orig) + 1;
385 	len += sizeof(char *);
386 
387 	/* Allocate it and set up the pointers. */
388 	if ((ret = __os_malloc(len, db_malloc, &array)) != 0)
389 		return (ret);
390 
391 	strp = (char *)(array + (orig - *listp) + 1);
392 
393 	/* Copy the original information into the new memory. */
394 	for (orig = *listp, arrayp = array; *orig != NULL; ++orig, ++arrayp) {
395 		len = strlen(*orig);
396 		memcpy(strp, *orig, len + 1);
397 		*arrayp = strp;
398 		strp += len + 1;
399 
400 		__os_freestr(*orig);
401 	}
402 
403 	/* NULL-terminate the list. */
404 	*arrayp = NULL;
405 
406 	__os_free(*listp, 0);
407 	*listp = array;
408 
409 	return (0);
410 }
411 
412 static int
__cmpfunc(p1,p2)413 __cmpfunc(p1, p2)
414 	const void *p1, *p2;
415 {
416 	return (strcmp(*((char * const *)p1), *((char * const *)p2)));
417 }
418