1*2f698edbSchristos /* $NetBSD: log4.c,v 1.3 2014/01/26 21:43:45 christos Exp $ */
2dbd550edSchristos /*-
3dbd550edSchristos * Copyright (c) 1992, 1993, 1994
4dbd550edSchristos * The Regents of the University of California. All rights reserved.
5dbd550edSchristos * Copyright (c) 1992, 1993, 1994, 1995, 1996
6dbd550edSchristos * Keith Bostic. All rights reserved.
7dbd550edSchristos *
8dbd550edSchristos * See the LICENSE file for redistribution information.
9dbd550edSchristos */
10dbd550edSchristos
11dbd550edSchristos #include "config.h"
12dbd550edSchristos
13*2f698edbSchristos #include <sys/cdefs.h>
14*2f698edbSchristos #if 0
15dbd550edSchristos #ifndef lint
16dbd550edSchristos static const char sccsid[] = "Id: log4.c,v 10.3 2002/06/08 21:00:33 skimo Exp ";
17dbd550edSchristos #endif /* not lint */
18*2f698edbSchristos #else
19*2f698edbSchristos __RCSID("$NetBSD: log4.c,v 1.3 2014/01/26 21:43:45 christos Exp $");
20*2f698edbSchristos #endif
21dbd550edSchristos
22dbd550edSchristos #include <sys/types.h>
23dbd550edSchristos #include <sys/queue.h>
24dbd550edSchristos #include <sys/stat.h>
25dbd550edSchristos
26dbd550edSchristos #include <bitstring.h>
27dbd550edSchristos #include <errno.h>
28dbd550edSchristos #include <fcntl.h>
29dbd550edSchristos #include <limits.h>
30dbd550edSchristos #include <stdio.h>
31dbd550edSchristos #include <stdlib.h>
32dbd550edSchristos #include <string.h>
33dbd550edSchristos
34dbd550edSchristos #include "common.h"
35dbd550edSchristos
36dbd550edSchristos /*
37dbd550edSchristos * The log consists of records, each containing a type byte and a variable
38dbd550edSchristos * length byte string, as follows:
39dbd550edSchristos *
40dbd550edSchristos * LOG_CURSOR_INIT MARK
41dbd550edSchristos * LOG_CURSOR_END MARK
42dbd550edSchristos * LOG_LINE_APPEND_F db_recno_t char *
43dbd550edSchristos * LOG_LINE_APPEND_B db_recno_t char *
44dbd550edSchristos * LOG_LINE_DELETE_F db_recno_t char *
45dbd550edSchristos * LOG_LINE_DELETE_B db_recno_t char *
46dbd550edSchristos * LOG_LINE_RESET_F db_recno_t char *
47dbd550edSchristos * LOG_LINE_RESET_B db_recno_t char *
48dbd550edSchristos * LOG_MARK LMARK
49dbd550edSchristos *
50dbd550edSchristos * We do before image physical logging. This means that the editor layer
51dbd550edSchristos * MAY NOT modify records in place, even if simply deleting or overwriting
52dbd550edSchristos * characters. Since the smallest unit of logging is a line, we're using
53dbd550edSchristos * up lots of space. This may eventually have to be reduced, probably by
54dbd550edSchristos * doing logical logging, which is a much cooler database phrase.
55dbd550edSchristos *
56dbd550edSchristos * The implementation of the historic vi 'u' command, using roll-forward and
57dbd550edSchristos * roll-back, is simple. Each set of changes has a LOG_CURSOR_INIT record,
58dbd550edSchristos * followed by a number of other records, followed by a LOG_CURSOR_END record.
59dbd550edSchristos * LOG_LINE_RESET records come in pairs. The first is a LOG_LINE_RESET_B
60dbd550edSchristos * record, and is the line before the change. The second is LOG_LINE_RESET_F,
61dbd550edSchristos * and is the line after the change. Roll-back is done by backing up to the
62dbd550edSchristos * first LOG_CURSOR_INIT record before a change. Roll-forward is done in a
63dbd550edSchristos * similar fashion.
64dbd550edSchristos *
65dbd550edSchristos * The 'U' command is implemented by rolling backward to a LOG_CURSOR_END
66dbd550edSchristos * record for a line different from the current one. It should be noted that
67dbd550edSchristos * this means that a subsequent 'u' command will make a change based on the
68dbd550edSchristos * new position of the log's cursor. This is okay, and, in fact, historic vi
69dbd550edSchristos * behaved that way.
70dbd550edSchristos */
71dbd550edSchristos
72dbd550edSchristos static int log_cursor1 __P((SCR *, int));
73dbd550edSchristos
74dbd550edSchristos /*
75dbd550edSchristos * log_init --
76dbd550edSchristos * Initialize the logging subsystem.
77dbd550edSchristos *
78dbd550edSchristos * PUBLIC: int log_init __P((SCR *, EXF *));
79dbd550edSchristos */
80dbd550edSchristos int
log_init(SCR * sp,EXF * ep)81dbd550edSchristos log_init(SCR *sp, EXF *ep)
82dbd550edSchristos {
83dbd550edSchristos DB_LOGC *logc;
84dbd550edSchristos DBT data;
85dbd550edSchristos size_t nlen;
86dbd550edSchristos
87dbd550edSchristos /*
88dbd550edSchristos * !!!
89dbd550edSchristos * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
90dbd550edSchristos *
91dbd550edSchristos * Initialize the buffer. The logging subsystem has its own
92dbd550edSchristos * buffers because the global ones are almost by definition
93dbd550edSchristos * going to be in use when the log runs.
94dbd550edSchristos */
95dbd550edSchristos sp->wp->l_lp = NULL;
96dbd550edSchristos sp->wp->l_len = 0;
97dbd550edSchristos ep->l_cursor.lno = 1; /* XXX Any valid recno. */
98dbd550edSchristos ep->l_cursor.cno = 0;
99dbd550edSchristos ep->l_high = ep->l_cur = 1;
100dbd550edSchristos
101dbd550edSchristos if ((sp->db_error = ep->env->log_cursor(ep->env, &logc, 0))
102dbd550edSchristos != 0) {
103dbd550edSchristos msgq(sp, M_DBERR, "env->log_cursor");
104dbd550edSchristos F_SET(ep, F_NOLOG);
105dbd550edSchristos return (1);
106dbd550edSchristos }
107dbd550edSchristos nlen = 1024;
108dbd550edSchristos retry:
109dbd550edSchristos BINC_GOTO(sp, sp->wp->l_lp, sp->wp->l_len, nlen);
110dbd550edSchristos memset(&data, 0, sizeof(data));
111dbd550edSchristos data.data = sp->wp->l_lp;
112dbd550edSchristos data.ulen = sp->wp->l_len;
113dbd550edSchristos data.flags = DB_DBT_USERMEM;
114dbd550edSchristos switch ((sp->db_error =
115dbd550edSchristos logc->get(logc, &ep->lsn_first, &data, DB_LAST))) {
116dbd550edSchristos case ENOMEM:
117dbd550edSchristos nlen = data.size;
118dbd550edSchristos goto retry;
119dbd550edSchristos default:
120dbd550edSchristos alloc_err:
121dbd550edSchristos msgq(sp, M_DBERR, "logc->get");
122dbd550edSchristos F_SET(ep, F_NOLOG);
123dbd550edSchristos return (1);
124dbd550edSchristos case 0:
125dbd550edSchristos ;
126dbd550edSchristos }
127dbd550edSchristos MEMCPY(&ep->lsn_cur, &ep->lsn_first, 1);
128dbd550edSchristos MEMCPY(&ep->lsn_high, &ep->lsn_first, 1);
129dbd550edSchristos logc->close(logc, 0);
130dbd550edSchristos
131dbd550edSchristos ep->l_win = NULL;
132dbd550edSchristos /*LOCK_INIT(sp->wp, ep);*/
133dbd550edSchristos
134dbd550edSchristos return (0);
135dbd550edSchristos }
136dbd550edSchristos
137dbd550edSchristos /*
138dbd550edSchristos * log_end --
139dbd550edSchristos * Close the logging subsystem.
140dbd550edSchristos *
141dbd550edSchristos * PUBLIC: int log_end __P((SCR *, EXF *));
142dbd550edSchristos */
143dbd550edSchristos int
log_end(SCR * sp,EXF * ep)144dbd550edSchristos log_end(SCR *sp, EXF *ep)
145dbd550edSchristos {
146dbd550edSchristos /*
147dbd550edSchristos * !!!
148dbd550edSchristos * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
149dbd550edSchristos */
150dbd550edSchristos /*LOCK_END(sp->wp, ep);*/
151dbd550edSchristos if (sp->wp->l_lp != NULL) {
152dbd550edSchristos free(sp->wp->l_lp);
153dbd550edSchristos sp->wp->l_lp = NULL;
154dbd550edSchristos }
155dbd550edSchristos sp->wp->l_len = 0;
156dbd550edSchristos ep->l_cursor.lno = 1; /* XXX Any valid recno. */
157dbd550edSchristos ep->l_cursor.cno = 0;
158dbd550edSchristos ep->l_high = ep->l_cur = 1;
159dbd550edSchristos return (0);
160dbd550edSchristos }
161dbd550edSchristos
162dbd550edSchristos /*
163dbd550edSchristos * log_cursor --
164dbd550edSchristos * Log the current cursor position, starting an event.
165dbd550edSchristos *
166dbd550edSchristos * PUBLIC: int log_cursor __P((SCR *));
167dbd550edSchristos */
168dbd550edSchristos int
log_cursor(SCR * sp)169dbd550edSchristos log_cursor(SCR *sp)
170dbd550edSchristos {
171dbd550edSchristos EXF *ep;
172dbd550edSchristos
173dbd550edSchristos ep = sp->ep;
174dbd550edSchristos if (F_ISSET(ep, F_NOLOG))
175dbd550edSchristos return (0);
176dbd550edSchristos
177dbd550edSchristos /*
178dbd550edSchristos * If any changes were made since the last cursor init,
179dbd550edSchristos * put out the ending cursor record.
180dbd550edSchristos */
181dbd550edSchristos if (ep->l_cursor.lno == OOBLNO) {
182dbd550edSchristos if (ep->l_win && ep->l_win != sp->wp)
183dbd550edSchristos return 0;
184dbd550edSchristos ep->l_cursor.lno = sp->lno;
185dbd550edSchristos ep->l_cursor.cno = sp->cno;
186dbd550edSchristos ep->l_win = NULL;
187dbd550edSchristos return (log_cursor1(sp, LOG_CURSOR_END));
188dbd550edSchristos }
189dbd550edSchristos ep->l_cursor.lno = sp->lno;
190dbd550edSchristos ep->l_cursor.cno = sp->cno;
191dbd550edSchristos return (0);
192dbd550edSchristos }
193dbd550edSchristos
194dbd550edSchristos /*
195dbd550edSchristos * log_cursor1 --
196dbd550edSchristos * Actually push a cursor record out.
197dbd550edSchristos */
198dbd550edSchristos static int
log_cursor1(SCR * sp,int type)199dbd550edSchristos log_cursor1(SCR *sp, int type)
200dbd550edSchristos {
201dbd550edSchristos DBT data, key;
202dbd550edSchristos EXF *ep;
203dbd550edSchristos
204dbd550edSchristos ep = sp->ep;
205dbd550edSchristos
206dbd550edSchristos /*
207dbd550edSchristos if (type == LOG_CURSOR_INIT &&
208dbd550edSchristos LOCK_TRY(sp->wp, ep))
209dbd550edSchristos return 1;
210dbd550edSchristos */
211dbd550edSchristos
212dbd550edSchristos if (type == LOG_CURSOR_INIT &&
213dbd550edSchristos (sp->db_error = __vi_log_truncate(ep)) != 0) {
214dbd550edSchristos msgq(sp, M_DBERR, "truncate");
215dbd550edSchristos return 1;
216dbd550edSchristos }
217dbd550edSchristos if ((sp->db_error =
218dbd550edSchristos __vi_cursor_log(ep->env, NULL, &ep->lsn_cur, 0, type,
219dbd550edSchristos ep->l_cursor.lno, ep->l_cursor.cno)) != 0) {
220dbd550edSchristos msgq(sp, M_DBERR, "cursor_log");
221dbd550edSchristos return 1;
222dbd550edSchristos }
223dbd550edSchristos if (type == LOG_CURSOR_END) {
224dbd550edSchristos MEMCPY(&ep->lsn_high, &ep->lsn_cur, 1);
225dbd550edSchristos /* XXXX should not be needed */
226dbd550edSchristos ep->env->log_flush(ep->env, NULL);
227dbd550edSchristos }
228dbd550edSchristos
229dbd550edSchristos #if defined(DEBUG) && 0
230dbd550edSchristos vtrace(sp, "%lu: %s: %u/%u\n", ep->l_cur,
231dbd550edSchristos type == LOG_CURSOR_INIT ? "log_cursor_init" : "log_cursor_end",
232dbd550edSchristos sp->lno, sp->cno);
233dbd550edSchristos #endif
234dbd550edSchristos /* Reset high water mark. */
235dbd550edSchristos ep->l_high = ++ep->l_cur;
236dbd550edSchristos
237dbd550edSchristos /*
238dbd550edSchristos if (type == LOG_CURSOR_END)
239dbd550edSchristos LOCK_UNLOCK(sp->wp, ep);
240dbd550edSchristos */
241dbd550edSchristos return (0);
242dbd550edSchristos }
243dbd550edSchristos
244dbd550edSchristos /*
245dbd550edSchristos * log_line --
246dbd550edSchristos * Log a line change.
247dbd550edSchristos *
248dbd550edSchristos * PUBLIC: int log_line __P((SCR *, db_recno_t, u_int));
249dbd550edSchristos */
250dbd550edSchristos int
log_line(SCR * sp,db_recno_t lno,u_int action)251dbd550edSchristos log_line(SCR *sp, db_recno_t lno, u_int action)
252dbd550edSchristos {
253dbd550edSchristos DBT data, key;
254dbd550edSchristos EXF *ep;
255dbd550edSchristos size_t len;
256dbd550edSchristos CHAR_T *lp;
257dbd550edSchristos db_recno_t lcur;
258dbd550edSchristos
259dbd550edSchristos ep = sp->ep;
260dbd550edSchristos if (F_ISSET(ep, F_NOLOG))
261dbd550edSchristos return (0);
262dbd550edSchristos
263dbd550edSchristos /*
264dbd550edSchristos * XXX
265dbd550edSchristos *
266dbd550edSchristos * Kluge for vi. Clear the EXF undo flag so that the
267dbd550edSchristos * next 'u' command does a roll-back, regardless.
268dbd550edSchristos */
269dbd550edSchristos F_CLR(ep, F_UNDO);
270dbd550edSchristos
271dbd550edSchristos /* Put out one initial cursor record per set of changes. */
272dbd550edSchristos if (ep->l_cursor.lno != OOBLNO) {
273dbd550edSchristos if (log_cursor1(sp, LOG_CURSOR_INIT))
274dbd550edSchristos return (1);
275dbd550edSchristos ep->l_cursor.lno = OOBLNO;
276dbd550edSchristos ep->l_win = sp->wp;
277dbd550edSchristos } /*else if (ep->l_win != sp->wp) {
278dbd550edSchristos printf("log_line own: %p, this: %p\n", ep->l_win, sp->wp);
279dbd550edSchristos return 1;
280dbd550edSchristos }*/
281dbd550edSchristos
282dbd550edSchristos if ((sp->db_error =
283dbd550edSchristos __vi_change_log(ep->env, NULL, &ep->lsn_cur, 0, action,
284dbd550edSchristos lno)) != 0) {
285dbd550edSchristos msgq(sp, M_DBERR, "change_log");
286dbd550edSchristos return 1;
287dbd550edSchristos }
288dbd550edSchristos
289dbd550edSchristos #if defined(DEBUG) && 0
290dbd550edSchristos switch (action) {
291dbd550edSchristos case LOG_LINE_APPEND_F:
292dbd550edSchristos vtrace(sp, "%u: log_line: append_f: %lu {%u}\n",
293dbd550edSchristos ep->l_cur, lno, len);
294dbd550edSchristos break;
295dbd550edSchristos case LOG_LINE_APPEND_B:
296dbd550edSchristos vtrace(sp, "%u: log_line: append_b: %lu {%u}\n",
297dbd550edSchristos ep->l_cur, lno, len);
298dbd550edSchristos break;
299dbd550edSchristos case LOG_LINE_DELETE_F:
300dbd550edSchristos vtrace(sp, "%lu: log_line: delete_f: %lu {%u}\n",
301dbd550edSchristos ep->l_cur, lno, len);
302dbd550edSchristos break;
303dbd550edSchristos case LOG_LINE_DELETE_B:
304dbd550edSchristos vtrace(sp, "%lu: log_line: delete_b: %lu {%u}\n",
305dbd550edSchristos ep->l_cur, lno, len);
306dbd550edSchristos break;
307dbd550edSchristos case LOG_LINE_RESET_F:
308dbd550edSchristos vtrace(sp, "%lu: log_line: reset_f: %lu {%u}\n",
309dbd550edSchristos ep->l_cur, lno, len);
310dbd550edSchristos break;
311dbd550edSchristos case LOG_LINE_RESET_B:
312dbd550edSchristos vtrace(sp, "%lu: log_line: reset_b: %lu {%u}\n",
313dbd550edSchristos ep->l_cur, lno, len);
314dbd550edSchristos break;
315dbd550edSchristos }
316dbd550edSchristos #endif
317dbd550edSchristos /* Reset high water mark. */
318dbd550edSchristos ep->l_high = ++ep->l_cur;
319dbd550edSchristos
320dbd550edSchristos return (0);
321dbd550edSchristos }
322dbd550edSchristos
323dbd550edSchristos /*
324dbd550edSchristos * log_mark --
325dbd550edSchristos * Log a mark position. For the log to work, we assume that there
326dbd550edSchristos * aren't any operations that just put out a log record -- this
327dbd550edSchristos * would mean that undo operations would only reset marks, and not
328dbd550edSchristos * cause any other change.
329dbd550edSchristos *
330dbd550edSchristos * PUBLIC: int log_mark __P((SCR *, LMARK *));
331dbd550edSchristos */
332dbd550edSchristos int
log_mark(SCR * sp,LMARK * lmp)333dbd550edSchristos log_mark(SCR *sp, LMARK *lmp)
334dbd550edSchristos {
335dbd550edSchristos DBT data, key;
336dbd550edSchristos EXF *ep;
337dbd550edSchristos
338dbd550edSchristos ep = sp->ep;
339dbd550edSchristos if (F_ISSET(ep, F_NOLOG))
340dbd550edSchristos return (0);
341dbd550edSchristos
342dbd550edSchristos /* Put out one initial cursor record per set of changes. */
343dbd550edSchristos if (ep->l_cursor.lno != OOBLNO) {
344dbd550edSchristos if (log_cursor1(sp, LOG_CURSOR_INIT))
345dbd550edSchristos return (1);
346dbd550edSchristos ep->l_cursor.lno = OOBLNO;
347dbd550edSchristos ep->l_win = sp->wp;
348dbd550edSchristos }
349dbd550edSchristos
350dbd550edSchristos if ((sp->db_error =
351dbd550edSchristos __vi_mark_log(ep->env, NULL, &ep->lsn_cur, 0,
352dbd550edSchristos lmp)) != 0) {
353dbd550edSchristos msgq(sp, M_DBERR, "cursor_log");
354dbd550edSchristos return 1;
355dbd550edSchristos }
356dbd550edSchristos
357dbd550edSchristos #if defined(DEBUG) && 0
358dbd550edSchristos vtrace(sp, "%lu: mark %c: %lu/%u\n",
359dbd550edSchristos ep->l_cur, lmp->name, lmp->lno, lmp->cno);
360dbd550edSchristos #endif
361dbd550edSchristos /* Reset high water mark. */
362dbd550edSchristos ep->l_high = ++ep->l_cur;
363dbd550edSchristos return (0);
364dbd550edSchristos }
365dbd550edSchristos
366dbd550edSchristos /*
367dbd550edSchristos * Log_backward --
368dbd550edSchristos * Roll the log backward one operation.
369dbd550edSchristos *
370dbd550edSchristos * PUBLIC: int log_backward __P((SCR *, MARK *));
371dbd550edSchristos */
372dbd550edSchristos int
log_backward(SCR * sp,MARK * rp)373dbd550edSchristos log_backward(SCR *sp, MARK *rp)
374dbd550edSchristos {
375dbd550edSchristos EXF *ep;
376dbd550edSchristos LMARK lm;
377dbd550edSchristos MARK m;
378dbd550edSchristos db_recno_t lno;
379dbd550edSchristos int didop;
380dbd550edSchristos u_char *p;
381dbd550edSchristos size_t size;
382dbd550edSchristos
383dbd550edSchristos ep = sp->ep;
384dbd550edSchristos if (F_ISSET(ep, F_NOLOG)) {
385dbd550edSchristos msgq(sp, M_ERR,
386dbd550edSchristos "010|Logging not being performed, undo not possible");
387dbd550edSchristos return (1);
388dbd550edSchristos }
389dbd550edSchristos
390dbd550edSchristos if (log_compare(&ep->lsn_cur, &ep->lsn_first) <= 0) {
391dbd550edSchristos msgq(sp, M_BERR, "011|No changes to undo");
392dbd550edSchristos return (1);
393dbd550edSchristos }
394dbd550edSchristos return __vi_log_traverse(sp, UNDO_BACKWARD, rp);
395dbd550edSchristos }
396dbd550edSchristos
397dbd550edSchristos /*
398dbd550edSchristos * Log_setline --
399dbd550edSchristos * Reset the line to its original appearance.
400dbd550edSchristos *
401dbd550edSchristos * XXX
402dbd550edSchristos * There's a bug in this code due to our not logging cursor movements
403dbd550edSchristos * unless a change was made. If you do a change, move off the line,
404dbd550edSchristos * then move back on and do a 'U', the line will be restored to the way
405dbd550edSchristos * it was before the original change.
406dbd550edSchristos *
407dbd550edSchristos * PUBLIC: int log_setline __P((SCR *));
408dbd550edSchristos */
409dbd550edSchristos int
log_setline(SCR * sp)410dbd550edSchristos log_setline(SCR *sp)
411dbd550edSchristos {
412dbd550edSchristos EXF *ep;
413dbd550edSchristos LMARK lm;
414dbd550edSchristos MARK m;
415dbd550edSchristos db_recno_t lno;
416dbd550edSchristos u_char *p;
417dbd550edSchristos size_t size;
418dbd550edSchristos
419dbd550edSchristos ep = sp->ep;
420dbd550edSchristos if (F_ISSET(ep, F_NOLOG)) {
421dbd550edSchristos msgq(sp, M_ERR,
422dbd550edSchristos "012|Logging not being performed, undo not possible");
423dbd550edSchristos return (1);
424dbd550edSchristos }
425dbd550edSchristos
426dbd550edSchristos if (log_compare(&ep->lsn_cur, &ep->lsn_first) <= 0) {
427dbd550edSchristos msgq(sp, M_BERR, "011|No changes to undo");
428dbd550edSchristos return (1);
429dbd550edSchristos }
430dbd550edSchristos return __vi_log_traverse(sp, UNDO_SETLINE, &m);
431dbd550edSchristos }
432dbd550edSchristos
433dbd550edSchristos /*
434dbd550edSchristos * Log_forward --
435dbd550edSchristos * Roll the log forward one operation.
436dbd550edSchristos *
437dbd550edSchristos * PUBLIC: int log_forward __P((SCR *, MARK *));
438dbd550edSchristos */
439dbd550edSchristos int
log_forward(SCR * sp,MARK * rp)440dbd550edSchristos log_forward(SCR *sp, MARK *rp)
441dbd550edSchristos {
442dbd550edSchristos EXF *ep;
443dbd550edSchristos LMARK lm;
444dbd550edSchristos MARK m;
445dbd550edSchristos db_recno_t lno;
446dbd550edSchristos int didop;
447dbd550edSchristos u_char *p;
448dbd550edSchristos size_t size;
449dbd550edSchristos
450dbd550edSchristos ep = sp->ep;
451dbd550edSchristos if (F_ISSET(ep, F_NOLOG)) {
452dbd550edSchristos msgq(sp, M_ERR,
453dbd550edSchristos "013|Logging not being performed, roll-forward not possible");
454dbd550edSchristos return (1);
455dbd550edSchristos }
456dbd550edSchristos
457dbd550edSchristos if (log_compare(&ep->lsn_cur, &ep->lsn_high) >= 0) {
458dbd550edSchristos msgq(sp, M_BERR, "014|No changes to re-do");
459dbd550edSchristos return (1);
460dbd550edSchristos }
461dbd550edSchristos return __vi_log_traverse(sp, UNDO_FORWARD, rp);
462dbd550edSchristos }
463