1/*-
2 * Copyright (c) 1992, 1993, 1994
3 *	The Regents of the University of California.  All rights reserved.
4 * Copyright (c) 1992, 1993, 1994, 1995, 1996
5 *	Keith Bostic.  All rights reserved.
6 *
7 * See the LICENSE file for redistribution information.
8 */
9
10#include "config.h"
11
12#ifndef lint
13static const char sccsid[] = "$Id: log.c,v 10.27 2011/07/13 06:25:50 zy Exp $";
14#endif /* not lint */
15
16#include <sys/types.h>
17#include <sys/queue.h>
18#include <sys/stat.h>
19
20#include <bitstring.h>
21#include <errno.h>
22#include <fcntl.h>
23#include <limits.h>
24#include <stdint.h>
25#include <stdio.h>
26#include <stdlib.h>
27#include <string.h>
28
29#include "common.h"
30
31/*
32 * The log consists of records, each containing a type byte and a variable
33 * length byte string, as follows:
34 *
35 *	LOG_CURSOR_INIT		MARK
36 *	LOG_CURSOR_END		MARK
37 *	LOG_LINE_APPEND 	recno_t		char *
38 *	LOG_LINE_DELETE		recno_t		char *
39 *	LOG_LINE_INSERT		recno_t		char *
40 *	LOG_LINE_RESET_F	recno_t		char *
41 *	LOG_LINE_RESET_B	recno_t		char *
42 *	LOG_MARK		LMARK
43 *
44 * We do before image physical logging.  This means that the editor layer
45 * MAY NOT modify records in place, even if simply deleting or overwriting
46 * characters.  Since the smallest unit of logging is a line, we're using
47 * up lots of space.  This may eventually have to be reduced, probably by
48 * doing logical logging, which is a much cooler database phrase.
49 *
50 * The implementation of the historic vi 'u' command, using roll-forward and
51 * roll-back, is simple.  Each set of changes has a LOG_CURSOR_INIT record,
52 * followed by a number of other records, followed by a LOG_CURSOR_END record.
53 * LOG_LINE_RESET records come in pairs.  The first is a LOG_LINE_RESET_B
54 * record, and is the line before the change.  The second is LOG_LINE_RESET_F,
55 * and is the line after the change.  Roll-back is done by backing up to the
56 * first LOG_CURSOR_INIT record before a change.  Roll-forward is done in a
57 * similar fashion.
58 *
59 * The 'U' command is implemented by rolling backward to a LOG_CURSOR_END
60 * record for a line different from the current one.  It should be noted that
61 * this means that a subsequent 'u' command will make a change based on the
62 * new position of the log's cursor.  This is okay, and, in fact, historic vi
63 * behaved that way.
64 */
65
66static int	log_cursor1 __P((SCR *, int));
67static void	log_err __P((SCR *, char *, int));
68#if defined(DEBUG) && 0
69static void	log_trace __P((SCR *, char *, recno_t, u_char *));
70#endif
71static int	apply_with __P((int (*)(SCR *, recno_t, CHAR_T *, size_t),
72					SCR *, recno_t, u_char *, size_t));
73
74/* Try and restart the log on failure, i.e. if we run out of memory. */
75#define	LOG_ERR {							\
76	log_err(sp, __FILE__, __LINE__);				\
77	return (1);							\
78}
79
80/* offset of CHAR_T string in log needs to be aligned on some systems
81 * because it is passed to db_set as a string
82 */
83typedef struct {
84    char    data[sizeof(u_char) /* type */ + sizeof(recno_t)];
85    CHAR_T  str[1];
86} log_t;
87#define CHAR_T_OFFSET ((char *)(((log_t*)0)->str) - (char *)0)
88
89/*
90 * log_init --
91 *	Initialize the logging subsystem.
92 *
93 * PUBLIC: int log_init __P((SCR *, EXF *));
94 */
95int
96log_init(
97	SCR *sp,
98	EXF *ep)
99{
100	/*
101	 * !!!
102	 * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
103	 *
104	 * Initialize the buffer.  The logging subsystem has its own
105	 * buffers because the global ones are almost by definition
106	 * going to be in use when the log runs.
107	 */
108	ep->l_lp = NULL;
109	ep->l_len = 0;
110	ep->l_cursor.lno = 1;		/* XXX Any valid recno. */
111	ep->l_cursor.cno = 0;
112	ep->l_high = ep->l_cur = 1;
113
114	ep->log = dbopen(NULL, O_CREAT | O_NONBLOCK | O_RDWR,
115	    S_IRUSR | S_IWUSR, DB_RECNO, NULL);
116	if (ep->log == NULL) {
117		msgq(sp, M_SYSERR, "009|Log file");
118		F_SET(ep, F_NOLOG);
119		return (1);
120	}
121
122	return (0);
123}
124
125/*
126 * log_end --
127 *	Close the logging subsystem.
128 *
129 * PUBLIC: int log_end __P((SCR *, EXF *));
130 */
131int
132log_end(
133	SCR *sp,
134	EXF *ep)
135{
136	/*
137	 * !!!
138	 * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
139	 */
140	if (ep->log != NULL) {
141		(void)(ep->log->close)(ep->log);
142		ep->log = NULL;
143	}
144	if (ep->l_lp != NULL) {
145		free(ep->l_lp);
146		ep->l_lp = NULL;
147	}
148	ep->l_len = 0;
149	ep->l_cursor.lno = 1;		/* XXX Any valid recno. */
150	ep->l_cursor.cno = 0;
151	ep->l_high = ep->l_cur = 1;
152	return (0);
153}
154
155/*
156 * log_cursor --
157 *	Log the current cursor position, starting an event.
158 *
159 * PUBLIC: int log_cursor __P((SCR *));
160 */
161int
162log_cursor(SCR *sp)
163{
164	EXF *ep;
165
166	ep = sp->ep;
167	if (F_ISSET(ep, F_NOLOG))
168		return (0);
169
170	/*
171	 * If any changes were made since the last cursor init,
172	 * put out the ending cursor record.
173	 */
174	if (ep->l_cursor.lno == OOBLNO) {
175		ep->l_cursor.lno = sp->lno;
176		ep->l_cursor.cno = sp->cno;
177		return (log_cursor1(sp, LOG_CURSOR_END));
178	}
179	ep->l_cursor.lno = sp->lno;
180	ep->l_cursor.cno = sp->cno;
181	return (0);
182}
183
184/*
185 * log_cursor1 --
186 *	Actually push a cursor record out.
187 */
188static int
189log_cursor1(
190	SCR *sp,
191	int type)
192{
193	DBT data, key;
194	EXF *ep;
195
196	ep = sp->ep;
197
198	BINC_RETC(sp, ep->l_lp, ep->l_len, sizeof(u_char) + sizeof(MARK));
199	ep->l_lp[0] = type;
200	memmove(ep->l_lp + sizeof(u_char), &ep->l_cursor, sizeof(MARK));
201
202	key.data = &ep->l_cur;
203	key.size = sizeof(recno_t);
204	data.data = ep->l_lp;
205	data.size = sizeof(u_char) + sizeof(MARK);
206	if (ep->log->put(ep->log, &key, &data, 0) == -1)
207		LOG_ERR;
208
209#if defined(DEBUG) && 0
210	TRACE(sp, "%lu: %s: %u/%u\n", ep->l_cur,
211	    type == LOG_CURSOR_INIT ? "log_cursor_init" : "log_cursor_end",
212	    sp->lno, sp->cno);
213#endif
214	/* Reset high water mark. */
215	ep->l_high = ++ep->l_cur;
216
217	return (0);
218}
219
220/*
221 * log_line --
222 *	Log a line change.
223 *
224 * PUBLIC: int log_line __P((SCR *, recno_t, u_int));
225 */
226int
227log_line(
228	SCR *sp,
229	recno_t lno,
230	u_int action)
231{
232	DBT data, key;
233	EXF *ep;
234	size_t len;
235	CHAR_T *lp;
236	recno_t lcur;
237
238	ep = sp->ep;
239	if (F_ISSET(ep, F_NOLOG))
240		return (0);
241
242	/*
243	 * XXX
244	 *
245	 * Kluge for vi.  Clear the EXF undo flag so that the
246	 * next 'u' command does a roll-back, regardless.
247	 */
248	F_CLR(ep, F_UNDO);
249
250	/* Put out one initial cursor record per set of changes. */
251	if (ep->l_cursor.lno != OOBLNO) {
252		if (log_cursor1(sp, LOG_CURSOR_INIT))
253			return (1);
254		ep->l_cursor.lno = OOBLNO;
255	}
256
257	/*
258	 * Put out the changes.  If it's a LOG_LINE_RESET_B call, it's a
259	 * special case, avoid the caches.  Also, if it fails and it's
260	 * line 1, it just means that the user started with an empty file,
261	 * so fake an empty length line.
262	 */
263	if (action == LOG_LINE_RESET_B) {
264		if (db_get(sp, lno, DBG_NOCACHE, &lp, &len)) {
265			if (lno != 1) {
266				db_err(sp, lno);
267				return (1);
268			}
269			len = 0;
270			lp = L("");
271		}
272	} else
273		if (db_get(sp, lno, DBG_FATAL, &lp, &len))
274			return (1);
275	BINC_RETC(sp,
276	    ep->l_lp, ep->l_len,
277	    len * sizeof(CHAR_T) + CHAR_T_OFFSET);
278	ep->l_lp[0] = action;
279	memmove(ep->l_lp + sizeof(u_char), &lno, sizeof(recno_t));
280	memmove(ep->l_lp + CHAR_T_OFFSET, lp, len * sizeof(CHAR_T));
281
282	lcur = ep->l_cur;
283	key.data = &lcur;
284	key.size = sizeof(recno_t);
285	data.data = ep->l_lp;
286	data.size = len * sizeof(CHAR_T) + CHAR_T_OFFSET;
287	if (ep->log->put(ep->log, &key, &data, 0) == -1)
288		LOG_ERR;
289
290#if defined(DEBUG) && 0
291	switch (action) {
292	case LOG_LINE_APPEND:
293		TRACE(sp, "%lu: log_line: append: %lu {%u}\n",
294		    ep->l_cur, lno, len);
295		break;
296	case LOG_LINE_DELETE:
297		TRACE(sp, "%lu: log_line: delete: %lu {%u}\n",
298		    ep->l_cur, lno, len);
299		break;
300	case LOG_LINE_INSERT:
301		TRACE(sp, "%lu: log_line: insert: %lu {%u}\n",
302		    ep->l_cur, lno, len);
303		break;
304	case LOG_LINE_RESET_F:
305		TRACE(sp, "%lu: log_line: reset_f: %lu {%u}\n",
306		    ep->l_cur, lno, len);
307		break;
308	case LOG_LINE_RESET_B:
309		TRACE(sp, "%lu: log_line: reset_b: %lu {%u}\n",
310		    ep->l_cur, lno, len);
311		break;
312	}
313#endif
314	/* Reset high water mark. */
315	ep->l_high = ++ep->l_cur;
316
317	return (0);
318}
319
320/*
321 * log_mark --
322 *	Log a mark position.  For the log to work, we assume that there
323 *	aren't any operations that just put out a log record -- this
324 *	would mean that undo operations would only reset marks, and not
325 *	cause any other change.
326 *
327 * PUBLIC: int log_mark __P((SCR *, LMARK *));
328 */
329int
330log_mark(
331	SCR *sp,
332	LMARK *lmp)
333{
334	DBT data, key;
335	EXF *ep;
336
337	ep = sp->ep;
338	if (F_ISSET(ep, F_NOLOG))
339		return (0);
340
341	/* Put out one initial cursor record per set of changes. */
342	if (ep->l_cursor.lno != OOBLNO) {
343		if (log_cursor1(sp, LOG_CURSOR_INIT))
344			return (1);
345		ep->l_cursor.lno = OOBLNO;
346	}
347
348	BINC_RETC(sp, ep->l_lp,
349	    ep->l_len, sizeof(u_char) + sizeof(LMARK));
350	ep->l_lp[0] = LOG_MARK;
351	memmove(ep->l_lp + sizeof(u_char), lmp, sizeof(LMARK));
352
353	key.data = &ep->l_cur;
354	key.size = sizeof(recno_t);
355	data.data = ep->l_lp;
356	data.size = sizeof(u_char) + sizeof(LMARK);
357	if (ep->log->put(ep->log, &key, &data, 0) == -1)
358		LOG_ERR;
359
360#if defined(DEBUG) && 0
361	TRACE(sp, "%lu: mark %c: %lu/%u\n",
362	    ep->l_cur, lmp->name, lmp->lno, lmp->cno);
363#endif
364	/* Reset high water mark. */
365	ep->l_high = ++ep->l_cur;
366	return (0);
367}
368
369/*
370 * Log_backward --
371 *	Roll the log backward one operation.
372 *
373 * PUBLIC: int log_backward __P((SCR *, MARK *));
374 */
375int
376log_backward(
377	SCR *sp,
378	MARK *rp)
379{
380	DBT key, data;
381	EXF *ep;
382	LMARK lm;
383	MARK m;
384	recno_t lno;
385	int didop;
386	u_char *p;
387
388	ep = sp->ep;
389	if (F_ISSET(ep, F_NOLOG)) {
390		msgq(sp, M_ERR,
391		    "010|Logging not being performed, undo not possible");
392		return (1);
393	}
394
395	if (ep->l_cur == 1) {
396		msgq(sp, M_BERR, "011|No changes to undo");
397		return (1);
398	}
399
400	F_SET(ep, F_NOLOG);		/* Turn off logging. */
401
402	key.data = &ep->l_cur;		/* Initialize db request. */
403	key.size = sizeof(recno_t);
404	for (didop = 0;;) {
405		--ep->l_cur;
406		if (ep->log->get(ep->log, &key, &data, 0))
407			LOG_ERR;
408#if defined(DEBUG) && 0
409		log_trace(sp, "log_backward", ep->l_cur, data.data);
410#endif
411		switch (*(p = (u_char *)data.data)) {
412		case LOG_CURSOR_INIT:
413			if (didop) {
414				memmove(rp, p + sizeof(u_char), sizeof(MARK));
415				F_CLR(ep, F_NOLOG);
416				return (0);
417			}
418			break;
419		case LOG_CURSOR_END:
420			break;
421		case LOG_LINE_APPEND:
422		case LOG_LINE_INSERT:
423			didop = 1;
424			memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
425			if (db_delete(sp, lno))
426				goto err;
427			++sp->rptlines[L_DELETED];
428			break;
429		case LOG_LINE_DELETE:
430			didop = 1;
431			memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
432			if (apply_with(db_insert, sp, lno,
433				p + CHAR_T_OFFSET, data.size - CHAR_T_OFFSET))
434				goto err;
435			++sp->rptlines[L_ADDED];
436			break;
437		case LOG_LINE_RESET_F:
438			break;
439		case LOG_LINE_RESET_B:
440			didop = 1;
441			memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
442			if (apply_with(db_set, sp, lno,
443				p + CHAR_T_OFFSET, data.size - CHAR_T_OFFSET))
444				goto err;
445			if (sp->rptlchange != lno) {
446				sp->rptlchange = lno;
447				++sp->rptlines[L_CHANGED];
448			}
449			break;
450		case LOG_MARK:
451			didop = 1;
452			memmove(&lm, p + sizeof(u_char), sizeof(LMARK));
453			m.lno = lm.lno;
454			m.cno = lm.cno;
455			if (mark_set(sp, lm.name, &m, 0))
456				goto err;
457			break;
458		default:
459			abort();
460		}
461	}
462
463err:	F_CLR(ep, F_NOLOG);
464	return (1);
465}
466
467/*
468 * Log_setline --
469 *	Reset the line to its original appearance.
470 *
471 * XXX
472 * There's a bug in this code due to our not logging cursor movements
473 * unless a change was made.  If you do a change, move off the line,
474 * then move back on and do a 'U', the line will be restored to the way
475 * it was before the original change.
476 *
477 * PUBLIC: int log_setline __P((SCR *));
478 */
479int
480log_setline(SCR *sp)
481{
482	DBT key, data;
483	EXF *ep;
484	LMARK lm;
485	MARK m;
486	recno_t lno;
487	u_char *p;
488
489	ep = sp->ep;
490	if (F_ISSET(ep, F_NOLOG)) {
491		msgq(sp, M_ERR,
492		    "012|Logging not being performed, undo not possible");
493		return (1);
494	}
495
496	if (ep->l_cur == 1)
497		return (1);
498
499	F_SET(ep, F_NOLOG);		/* Turn off logging. */
500
501	key.data = &ep->l_cur;		/* Initialize db request. */
502	key.size = sizeof(recno_t);
503	for (;;) {
504		--ep->l_cur;
505		if (ep->log->get(ep->log, &key, &data, 0))
506			LOG_ERR;
507#if defined(DEBUG) && 0
508		log_trace(sp, "log_setline", ep->l_cur, data.data);
509#endif
510		switch (*(p = (u_char *)data.data)) {
511		case LOG_CURSOR_INIT:
512			memmove(&m, p + sizeof(u_char), sizeof(MARK));
513			if (m.lno != sp->lno || ep->l_cur == 1) {
514				F_CLR(ep, F_NOLOG);
515				return (0);
516			}
517			break;
518		case LOG_CURSOR_END:
519			memmove(&m, p + sizeof(u_char), sizeof(MARK));
520			if (m.lno != sp->lno) {
521				++ep->l_cur;
522				F_CLR(ep, F_NOLOG);
523				return (0);
524			}
525			break;
526		case LOG_LINE_APPEND:
527		case LOG_LINE_INSERT:
528		case LOG_LINE_DELETE:
529		case LOG_LINE_RESET_F:
530			break;
531		case LOG_LINE_RESET_B:
532			memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
533			if (lno == sp->lno &&
534				apply_with(db_set, sp, lno,
535				p + CHAR_T_OFFSET, data.size - CHAR_T_OFFSET))
536				goto err;
537			if (sp->rptlchange != lno) {
538				sp->rptlchange = lno;
539				++sp->rptlines[L_CHANGED];
540			}
541		case LOG_MARK:
542			memmove(&lm, p + sizeof(u_char), sizeof(LMARK));
543			m.lno = lm.lno;
544			m.cno = lm.cno;
545			if (mark_set(sp, lm.name, &m, 0))
546				goto err;
547			break;
548		default:
549			abort();
550		}
551	}
552
553err:	F_CLR(ep, F_NOLOG);
554	return (1);
555}
556
557/*
558 * Log_forward --
559 *	Roll the log forward one operation.
560 *
561 * PUBLIC: int log_forward __P((SCR *, MARK *));
562 */
563int
564log_forward(
565	SCR *sp,
566	MARK *rp)
567{
568	DBT key, data;
569	EXF *ep;
570	LMARK lm;
571	MARK m;
572	recno_t lno;
573	int didop;
574	u_char *p;
575
576	ep = sp->ep;
577	if (F_ISSET(ep, F_NOLOG)) {
578		msgq(sp, M_ERR,
579	    "013|Logging not being performed, roll-forward not possible");
580		return (1);
581	}
582
583	if (ep->l_cur == ep->l_high) {
584		msgq(sp, M_BERR, "014|No changes to re-do");
585		return (1);
586	}
587
588	F_SET(ep, F_NOLOG);		/* Turn off logging. */
589
590	key.data = &ep->l_cur;		/* Initialize db request. */
591	key.size = sizeof(recno_t);
592	for (didop = 0;;) {
593		++ep->l_cur;
594		if (ep->log->get(ep->log, &key, &data, 0))
595			LOG_ERR;
596#if defined(DEBUG) && 0
597		log_trace(sp, "log_forward", ep->l_cur, data.data);
598#endif
599		switch (*(p = (u_char *)data.data)) {
600		case LOG_CURSOR_END:
601			if (didop) {
602				++ep->l_cur;
603				memmove(rp, p + sizeof(u_char), sizeof(MARK));
604				F_CLR(ep, F_NOLOG);
605				return (0);
606			}
607			break;
608		case LOG_CURSOR_INIT:
609			break;
610		case LOG_LINE_APPEND:
611		case LOG_LINE_INSERT:
612			didop = 1;
613			memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
614			if (apply_with(db_insert, sp, lno,
615				p + CHAR_T_OFFSET, data.size - CHAR_T_OFFSET))
616				goto err;
617			++sp->rptlines[L_ADDED];
618			break;
619		case LOG_LINE_DELETE:
620			didop = 1;
621			memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
622			if (db_delete(sp, lno))
623				goto err;
624			++sp->rptlines[L_DELETED];
625			break;
626		case LOG_LINE_RESET_B:
627			break;
628		case LOG_LINE_RESET_F:
629			didop = 1;
630			memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
631			if (apply_with(db_set, sp, lno,
632				p + CHAR_T_OFFSET, data.size - CHAR_T_OFFSET))
633				goto err;
634			if (sp->rptlchange != lno) {
635				sp->rptlchange = lno;
636				++sp->rptlines[L_CHANGED];
637			}
638			break;
639		case LOG_MARK:
640			didop = 1;
641			memmove(&lm, p + sizeof(u_char), sizeof(LMARK));
642			m.lno = lm.lno;
643			m.cno = lm.cno;
644			if (mark_set(sp, lm.name, &m, 0))
645				goto err;
646			break;
647		default:
648			abort();
649		}
650	}
651
652err:	F_CLR(ep, F_NOLOG);
653	return (1);
654}
655
656/*
657 * log_err --
658 *	Try and restart the log on failure, i.e. if we run out of memory.
659 */
660static void
661log_err(
662	SCR *sp,
663	char *file,
664	int line)
665{
666	EXF *ep;
667
668	msgq(sp, M_SYSERR, "015|%s/%d: log put error", tail(file), line);
669	ep = sp->ep;
670	(void)ep->log->close(ep->log);
671	if (!log_init(sp, ep))
672		msgq(sp, M_ERR, "267|Log restarted");
673}
674
675#if defined(DEBUG) && 0
676static void
677log_trace(
678	SCR *sp,
679	char *msg,
680	recno_t rno,
681	u_char *p)
682{
683	LMARK lm;
684	MARK m;
685	recno_t lno;
686
687	switch (*p) {
688	case LOG_CURSOR_INIT:
689		memmove(&m, p + sizeof(u_char), sizeof(MARK));
690		TRACE(sp, "%lu: %s:  C_INIT: %u/%u\n", rno, msg, m.lno, m.cno);
691		break;
692	case LOG_CURSOR_END:
693		memmove(&m, p + sizeof(u_char), sizeof(MARK));
694		TRACE(sp, "%lu: %s:   C_END: %u/%u\n", rno, msg, m.lno, m.cno);
695		break;
696	case LOG_LINE_APPEND:
697		memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
698		TRACE(sp, "%lu: %s:  APPEND: %lu\n", rno, msg, lno);
699		break;
700	case LOG_LINE_INSERT:
701		memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
702		TRACE(sp, "%lu: %s:  INSERT: %lu\n", rno, msg, lno);
703		break;
704	case LOG_LINE_DELETE:
705		memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
706		TRACE(sp, "%lu: %s:  DELETE: %lu\n", rno, msg, lno);
707		break;
708	case LOG_LINE_RESET_F:
709		memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
710		TRACE(sp, "%lu: %s: RESET_F: %lu\n", rno, msg, lno);
711		break;
712	case LOG_LINE_RESET_B:
713		memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
714		TRACE(sp, "%lu: %s: RESET_B: %lu\n", rno, msg, lno);
715		break;
716	case LOG_MARK:
717		memmove(&lm, p + sizeof(u_char), sizeof(LMARK));
718		TRACE(sp,
719		    "%lu: %s:    MARK: %u/%u\n", rno, msg, lm.lno, lm.cno);
720		break;
721	default:
722		abort();
723	}
724}
725#endif
726
727/*
728 * apply_with --
729 *	Apply a realigned line from the log db to the file db.
730 */
731static int
732apply_with(
733	int (*db_func)(SCR *, recno_t, CHAR_T *, size_t),
734	SCR *sp,
735	recno_t lno,
736	u_char *p,
737	size_t len)
738{
739#ifdef USE_WIDECHAR
740	typedef unsigned long nword;
741
742	static size_t blen;
743	static nword *bp;
744	nword *lp = (nword *)((uintptr_t)p / sizeof(nword) * sizeof(nword));
745
746	if (lp != (nword *)p) {
747		int offl = ((uintptr_t)p - (uintptr_t)lp) << 3;
748		int offr = (sizeof(nword) << 3) - offl;
749		size_t i, cnt = (len + sizeof(nword) / 2) / sizeof(nword);
750
751		if (len > blen) {
752			blen = p2roundup(MAX(len, 512));
753			REALLOC(sp, bp, nword *, blen);
754			if (bp == NULL)
755				return (1);
756		}
757		for (i = 0; i < cnt; ++i)
758#if BYTE_ORDER == BIG_ENDIAN
759			bp[i] = (lp[i] << offl) ^ (lp[i+1] >> offr);
760#else
761			bp[i] = (lp[i] >> offl) ^ (lp[i+1] << offr);
762#endif
763		p = (u_char *)bp;
764	}
765#endif
766	return db_func(sp, lno, (CHAR_T *)p, len / sizeof(CHAR_T));
767}
768