1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License").  You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22/*
23 *	db_log.cc
24 *
25 * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
26 * Use is subject to license terms.
27 */
28
29#pragma ident	"%Z%%M%	%I%	%E% SMI"
30
31#include <stdio.h>
32#include <errno.h>
33
34#include <malloc.h>
35#include <string.h>
36#ifdef TDRPC
37#include <sysent.h>
38#endif
39#include <unistd.h>
40
41#include "db_headers.h"
42#include "db_log.h"
43
44#include "nisdb_mt.h"
45
46static void
47delete_log_entry(db_log_entry *lentry)
48{
49	db_query *q;
50	entry_object *obj;
51	if (lentry) {
52		if ((q = lentry->get_query())) {
53			delete q;
54		}
55		if ((obj = lentry->get_object())) {
56			free_entry(obj);
57		}
58		delete lentry;
59	}
60}
61
62/*
63 * Execute given function 'func' on log.
64 * function takes as arguments: pointer to log entry, character pointer to
65 * another argument, and pointer to an integer, which is used as a counter.
66 * 'func' should increment this value for each successful application.
67 * The log is traversed until either 'func' returns FALSE, or when the log
68 * is exhausted.  The second argument to 'execute_on_log' is passed as the
69 * second argument to 'func'.  The third argument, 'clean' determines whether
70 * the log entry is deleted after the function has been applied.
71 * Returns the number of times that 'func' incremented its third argument.
72 */
73int
74db_log::execute_on_log(bool_t (*func) (db_log_entry *, char *, int *),
75			    char* arg, bool_t clean)
76{
77	db_log_entry    *j;
78	int count = 0;
79	bool_t done = FALSE;
80
81	WRITELOCK(this, 0, "w db_log::execute_on_log");
82	if (open() == TRUE) {   // open log
83		while (!done) {
84			j = get();
85			if (j == NULL)
86				break;
87			if ((*func)(j, arg, &count) == FALSE) done = TRUE;
88			if (clean) delete_log_entry(j);
89		}
90
91		sync_log();
92		close();
93	}
94	WRITEUNLOCK(this, count, "wu db_log::execute_on_log");
95
96	return (count);
97}
98
99static bool_t
100print_log_entry(db_log_entry *j, char * /* dummy */, int *count)
101{
102	j->print();
103	++ *count;
104	return (TRUE);
105}
106
107/* Print contents of log file to stdout */
108int
109db_log::print()
110{
111	return (execute_on_log(&(print_log_entry), NULL));
112}
113
114/* Make copy of current log to log pointed to by 'f'. */
115int
116db_log::copy(db_log *f)
117{
118	db_log_entry *j;
119	int	l, ret = 0;
120
121	WRITELOCK(f, -1, "w f db_log::copy");
122	if ((l = acqnonexcl()) != 0) {
123		WRITEUNLOCK(f, l, "wu f db_log::copy");
124		return (l);
125	}
126	for (;;) {
127		j = get();
128		if (j == NULL)
129			break;
130		if (f->append(j) < 0) {
131			WARNING_M(
132			"db_log::copy: could not append to log file: ");
133			ret = -1;
134			break;
135		}
136		delete_log_entry(j);
137	}
138	if ((l = relnonexcl()) != 0) {
139		ret = l;
140	}
141	WRITEUNLOCK(f, ret, "wu f db_log::copy");
142	return (ret);
143}
144
145/* Rewinds current log */
146int
147db_log::rewind()
148{
149	return (fseek(file, 0L, 0));
150}
151
152/*
153 * Return the next element in current log; return NULL if end of log or error.
154 * Log must have been opened for READ.
155 */
156db_log_entry
157*db_log::get()
158{
159	db_log_entry *j;
160
161	READLOCK(this, NULL, "r db_log::get");
162	if (mode != PICKLE_READ) {
163		READUNLOCK(this, NULL, "ru db_log::get");
164		return (NULL);
165	}
166
167	j = new db_log_entry;
168
169	if (j == NULL) {
170		READUNLOCK(this, NULL, "ru db_log::get");
171		return (NULL);
172	}
173	if (xdr_db_log_entry(&(xdr), j) == FALSE) {
174		delete_log_entry (j);
175/*    WARNING("Could not sucessfully finish reading log"); */
176		READUNLOCK(this, NULL, "ru db_log::get");
177		return (NULL);
178	}
179	if (! j->sane()) {
180		WARNING("truncated log entry found");
181		delete_log_entry(j);
182		j = NULL;
183	}
184	READUNLOCK(this, j, "ru db_log::get");
185	return (j);
186}
187
188/* Append given log entry to log. */
189int
190db_log::append(db_log_entry *j)
191{
192	int status;
193
194	WRITELOCK(this, -1, "w db_log::append");
195	if (mode != PICKLE_APPEND) {
196		WRITEUNLOCK(this, -1, "wu db_log::append");
197		return (-1);
198	}
199
200	/* xdr returns TRUE if successful, FALSE otherwise */
201	status = ((xdr_db_log_entry(&(xdr), j)) ? 0 : -1);
202	if (status < 0) {
203		WARNING("db_log: could not write log entry");
204	} else {
205		syncstate++;
206	}
207	WRITEUNLOCK(this, status, "wu db_log::append");
208	return (status);
209}
210
211int
212copy_log_file(char *oldname, char *newname) {
213
214	int	from, to, ret = 0;
215	ssize_t	size, w, b;
216	char	buf[8192];
217
218	if ((from = open(oldname, O_RDONLY, 0666)) < 0) {
219		if (errno == ENOENT) {
220			return (0);
221		} else {
222			return (errno);
223		}
224	}
225	if ((to = open(newname, O_WRONLY|O_CREAT|O_TRUNC, 0660)) < 0) {
226		ret = errno;
227		(void) close(from);
228		return (ret);
229	}
230
231	while ((size = read(from, buf, sizeof (buf))) > 0) {
232		b = 0;
233		while (size > 0) {
234			w = write(to, &buf[b], size);
235			if (w < 0) {
236				size == -1;
237				break;
238			}
239			size -= w;
240			b += w;
241		}
242		if (size != 0) {
243			ret = errno;
244			break;
245		}
246	}
247
248	(void) close(from);
249
250	if (ret != 0) {
251		errno = ret;
252		WARNING_M("db_log: error copying log file")
253		(void) close(to);
254		return (ret);
255	}
256
257	if (fsync(to) != 0) {
258		ret = errno;
259		WARNING_M("db_log: error syncing log file");
260	}
261
262	(void) close(to);
263
264	return (ret);
265
266}
267
268/*
269 * Return value is expected to be the usual C convention of non-zero
270 * for success, 0 for failure.
271 */
272int
273db_log::sync_log()
274{
275	int status, err;
276
277	WRITELOCK(this, -1, "w db_log::sync_log");
278	status = fflush(file);
279	if (status < 0) {
280		WARNING("db_log: could not flush log entry to disk");
281		WRITEUNLOCK(this, status, "wu db_log::sync_log");
282		return (status);
283	}
284
285	status = fsync(fileno(file));
286	if (status < 0) {
287		WARNING("db_log: could not sync log entry to disk");
288	} else if (tmplog != 0) {
289		if (syncstate == 0) {
290			/* Log already stable; nothing to do */
291			err = 0;
292		} else if ((err = copy_log_file(tmplog, stablelog)) == 0) {
293			if (rename(stablelog, oldlog) != 0) {
294				WARNING_M("db_log: could not mv stable log");
295			} else {
296				syncstate = 0;
297			}
298		} else {
299			errno = err;
300			WARNING_M("db_log: could not stabilize log");
301		}
302		status = (err == 0);
303	} else {
304		/*
305		 * Successful sync of file, but no tmplog to sync
306		 * so we make sure we return 'success'.
307		 */
308		status = 1;
309	}
310	WRITEUNLOCK(this, status, "wu db_log::sync_log");
311	return (status);
312}
313
314int
315db_log::close() {
316
317	int ret;
318
319	WRITELOCK(this, -1, "w db_log::close");
320	if (mode != PICKLE_READ && oldlog != 0) {
321		if (syncstate != 0) {
322			WARNING("db_log: closing unstable tmp log");
323		}
324		filename = oldlog;
325		oldlog = 0;
326	}
327
328	ret = pickle_file::close();
329	if (tmplog != 0) {
330		(void) unlink(tmplog);
331		delete tmplog;
332		tmplog = 0;
333	}
334	if (stablelog != 0) {
335		delete stablelog;
336		stablelog = 0;
337	}
338	WRITEUNLOCK(this, ret, "wu db_log::close");
339	return (ret);
340}
341
342bool_t
343db_log::open(void) {
344
345	int	len, cpstat;
346	bool_t	ret;
347
348	WRITELOCK(this, FALSE, "w db_log::open");
349	if (mode == PICKLE_READ || (!copylog)) {
350		ret = pickle_file::open();
351		WRITEUNLOCK(this, ret, "wu db_log::open");
352		return (ret);
353	}
354
355	len = strlen(filename);
356	tmplog = new char[len + sizeof (".tmp")];
357	if (tmplog == 0) {
358		WARNING("db_log: could not allocate tmp log name");
359		ret = pickle_file::open();
360		WRITEUNLOCK(this, ret, "wu db_log::open");
361		return (ret);
362	}
363	stablelog = new char[len + sizeof (".stable")];
364	if (stablelog == 0) {
365		WARNING("db_log: could not allocate stable log name");
366		delete tmplog;
367		tmplog = 0;
368		ret = pickle_file::open();
369		WRITEUNLOCK(this, ret, "wu db_log::open");
370		return (ret);
371	}
372	sprintf(tmplog, "%s.tmp", filename);
373	sprintf(stablelog, "%s.stable", filename);
374
375	if ((cpstat = copy_log_file(filename, tmplog)) == 0) {
376		oldlog = filename;
377		filename = tmplog;
378	} else {
379		syslog(LOG_WARNING,
380			"db_log: Error copying \"%s\" to \"%s\": %s",
381			filename, tmplog, strerror(cpstat));
382		delete tmplog;
383		tmplog = 0;
384		delete stablelog;
385		stablelog = 0;
386	}
387
388	ret = pickle_file::open();
389	WRITEUNLOCK(this, ret, "wu db_log::open");
390	return (ret);
391}
392