1 /* $OpenBSD: line.c,v 1.15 2016/01/06 22:28:52 millert Exp $ */ 2 3 /*- 4 * Copyright (c) 1992, 1993, 1994 5 * The Regents of the University of California. All rights reserved. 6 * Copyright (c) 1992, 1993, 1994, 1995, 1996 7 * Keith Bostic. All rights reserved. 8 * 9 * See the LICENSE file for redistribution information. 10 */ 11 12 #include "config.h" 13 14 #include <sys/types.h> 15 #include <sys/queue.h> 16 #include <sys/time.h> 17 18 #include <bitstring.h> 19 #include <errno.h> 20 #include <limits.h> 21 #include <stdio.h> 22 #include <string.h> 23 24 #include "common.h" 25 #include "../vi/vi.h" 26 27 static int scr_update(SCR *, recno_t, lnop_t, int); 28 29 /* 30 * db_eget -- 31 * Front-end to db_get, special case handling for empty files. 32 * 33 * PUBLIC: int db_eget(SCR *, recno_t, char **, size_t *, int *); 34 */ 35 int 36 db_eget(SCR *sp, recno_t lno, char **pp, size_t *lenp, int *isemptyp) 37 { 38 recno_t l1; 39 40 if (isemptyp != NULL) 41 *isemptyp = 0; 42 43 /* If the line exists, simply return it. */ 44 if (!db_get(sp, lno, 0, pp, lenp)) 45 return (0); 46 47 /* 48 * If the user asked for line 0 or line 1, i.e. the only possible 49 * line in an empty file, find the last line of the file; db_last 50 * fails loudly. 51 */ 52 if ((lno == 0 || lno == 1) && db_last(sp, &l1)) 53 return (1); 54 55 /* If the file isn't empty, fail loudly. */ 56 if ((lno != 0 && lno != 1) || l1 != 0) { 57 db_err(sp, lno); 58 return (1); 59 } 60 61 if (isemptyp != NULL) 62 *isemptyp = 1; 63 64 return (1); 65 } 66 67 /* 68 * db_get -- 69 * Look in the text buffers for a line, followed by the cache, followed 70 * by the database. 71 * 72 * PUBLIC: int db_get(SCR *, recno_t, u_int32_t, char **, size_t *); 73 */ 74 int 75 db_get(SCR *sp, recno_t lno, u_int32_t flags, char **pp, size_t *lenp) 76 { 77 DBT data, key; 78 EXF *ep; 79 TEXT *tp; 80 recno_t l1, l2; 81 82 /* 83 * The underlying recno stuff handles zero by returning NULL, but 84 * have to have an OOB condition for the look-aside into the input 85 * buffer anyway. 86 */ 87 if (lno == 0) 88 goto err1; 89 90 /* Check for no underlying file. */ 91 if ((ep = sp->ep) == NULL) { 92 ex_emsg(sp, NULL, EXM_NOFILEYET); 93 goto err3; 94 } 95 96 if (LF_ISSET(DBG_NOCACHE)) 97 goto nocache; 98 99 /* 100 * Look-aside into the TEXT buffers and see if the line we want 101 * is there. 102 */ 103 if (F_ISSET(sp, SC_TINPUT)) { 104 l1 = TAILQ_FIRST(&sp->tiq)->lno; 105 l2 = TAILQ_LAST(&sp->tiq, _texth)->lno; 106 if (l1 <= lno && l2 >= lno) { 107 #if defined(DEBUG) && 0 108 TRACE(sp, "retrieve TEXT buffer line %lu\n", (u_long)lno); 109 #endif 110 TAILQ_FOREACH(tp, &sp->tiq, q) { 111 if (tp->lno == lno) 112 break; 113 } 114 if (lenp != NULL) 115 *lenp = tp->len; 116 if (pp != NULL) 117 *pp = tp->lb; 118 return (0); 119 } 120 /* 121 * Adjust the line number for the number of lines used 122 * by the text input buffers. 123 */ 124 if (lno > l2) 125 lno -= l2 - l1; 126 } 127 128 /* Look-aside into the cache, and see if the line we want is there. */ 129 if (lno == ep->c_lno) { 130 #if defined(DEBUG) && 0 131 TRACE(sp, "retrieve cached line %lu\n", (u_long)lno); 132 #endif 133 if (lenp != NULL) 134 *lenp = ep->c_len; 135 if (pp != NULL) 136 *pp = ep->c_lp; 137 return (0); 138 } 139 ep->c_lno = OOBLNO; 140 141 nocache: 142 /* Get the line from the underlying database. */ 143 key.data = &lno; 144 key.size = sizeof(lno); 145 switch (ep->db->get(ep->db, &key, &data, 0)) { 146 case -1: 147 goto err2; 148 case 1: 149 err1: if (LF_ISSET(DBG_FATAL)) 150 err2: db_err(sp, lno); 151 err3: if (lenp != NULL) 152 *lenp = 0; 153 if (pp != NULL) 154 *pp = NULL; 155 return (1); 156 } 157 158 /* Reset the cache. */ 159 ep->c_lno = lno; 160 ep->c_len = data.size; 161 ep->c_lp = data.data; 162 163 #if defined(DEBUG) && 0 164 TRACE(sp, "retrieve DB line %lu\n", (u_long)lno); 165 #endif 166 if (lenp != NULL) 167 *lenp = data.size; 168 if (pp != NULL) 169 *pp = ep->c_lp; 170 return (0); 171 } 172 173 /* 174 * db_delete -- 175 * Delete a line from the file. 176 * 177 * PUBLIC: int db_delete(SCR *, recno_t); 178 */ 179 int 180 db_delete(SCR *sp, recno_t lno) 181 { 182 DBT key; 183 EXF *ep; 184 185 #if defined(DEBUG) && 0 186 TRACE(sp, "delete line %lu\n", (u_long)lno); 187 #endif 188 /* Check for no underlying file. */ 189 if ((ep = sp->ep) == NULL) { 190 ex_emsg(sp, NULL, EXM_NOFILEYET); 191 return (1); 192 } 193 194 /* Update marks, @ and global commands. */ 195 if (mark_insdel(sp, LINE_DELETE, lno)) 196 return (1); 197 if (ex_g_insdel(sp, LINE_DELETE, lno)) 198 return (1); 199 200 /* Log change. */ 201 log_line(sp, lno, LOG_LINE_DELETE); 202 203 /* Update file. */ 204 key.data = &lno; 205 key.size = sizeof(lno); 206 if (ep->db->del(ep->db, &key, 0) == 1) { 207 msgq(sp, M_SYSERR, 208 "unable to delete line %lu", (u_long)lno); 209 return (1); 210 } 211 212 /* Flush the cache, update line count, before screen update. */ 213 if (lno <= ep->c_lno) 214 ep->c_lno = OOBLNO; 215 if (ep->c_nlines != OOBLNO) 216 --ep->c_nlines; 217 218 /* File now modified. */ 219 if (F_ISSET(ep, F_FIRSTMODIFY)) 220 (void)rcv_init(sp); 221 F_SET(ep, F_MODIFIED); 222 223 /* Update screen. */ 224 return (scr_update(sp, lno, LINE_DELETE, 1)); 225 } 226 227 /* 228 * db_append -- 229 * Append a line into the file. 230 * 231 * PUBLIC: int db_append(SCR *, int, recno_t, char *, size_t); 232 */ 233 int 234 db_append(SCR *sp, int update, recno_t lno, char *p, size_t len) 235 { 236 DBT data, key; 237 EXF *ep; 238 int rval; 239 240 #if defined(DEBUG) && 0 241 TRACE(sp, "append to %lu: len %u {%.*s}\n", lno, len, MIN(len, 20), p); 242 #endif 243 /* Check for no underlying file. */ 244 if ((ep = sp->ep) == NULL) { 245 ex_emsg(sp, NULL, EXM_NOFILEYET); 246 return (1); 247 } 248 249 /* Update file. */ 250 key.data = &lno; 251 key.size = sizeof(lno); 252 data.data = p; 253 data.size = len; 254 if (ep->db->put(ep->db, &key, &data, R_IAFTER) == -1) { 255 msgq(sp, M_SYSERR, 256 "unable to append to line %lu", (u_long)lno); 257 return (1); 258 } 259 260 /* Flush the cache, update line count, before screen update. */ 261 if (lno < ep->c_lno) 262 ep->c_lno = OOBLNO; 263 if (ep->c_nlines != OOBLNO) 264 ++ep->c_nlines; 265 266 /* File now dirty. */ 267 if (F_ISSET(ep, F_FIRSTMODIFY)) 268 (void)rcv_init(sp); 269 F_SET(ep, F_MODIFIED); 270 271 /* Log change. */ 272 log_line(sp, lno + 1, LOG_LINE_APPEND); 273 274 /* Update marks, @ and global commands. */ 275 rval = 0; 276 if (mark_insdel(sp, LINE_INSERT, lno + 1)) 277 rval = 1; 278 if (ex_g_insdel(sp, LINE_INSERT, lno + 1)) 279 rval = 1; 280 281 /* 282 * Update screen. 283 * 284 * XXX 285 * Nasty hack. If multiple lines are input by the user, they aren't 286 * committed until an <ESC> is entered. The problem is the screen was 287 * updated/scrolled as each line was entered. So, when this routine 288 * is called to copy the new lines from the cut buffer into the file, 289 * it has to know not to update the screen again. 290 */ 291 return (scr_update(sp, lno, LINE_APPEND, update) || rval); 292 } 293 294 /* 295 * db_insert -- 296 * Insert a line into the file. 297 * 298 * PUBLIC: int db_insert(SCR *, recno_t, char *, size_t); 299 */ 300 int 301 db_insert(SCR *sp, recno_t lno, char *p, size_t len) 302 { 303 DBT data, key; 304 EXF *ep; 305 int rval; 306 307 #if defined(DEBUG) && 0 308 TRACE(sp, "insert before %lu: len %lu {%.*s}\n", 309 (u_long)lno, (u_long)len, MIN(len, 20), p); 310 #endif 311 /* Check for no underlying file. */ 312 if ((ep = sp->ep) == NULL) { 313 ex_emsg(sp, NULL, EXM_NOFILEYET); 314 return (1); 315 } 316 317 /* Update file. */ 318 key.data = &lno; 319 key.size = sizeof(lno); 320 data.data = p; 321 data.size = len; 322 if (ep->db->put(ep->db, &key, &data, R_IBEFORE) == -1) { 323 msgq(sp, M_SYSERR, 324 "unable to insert at line %lu", (u_long)lno); 325 return (1); 326 } 327 328 /* Flush the cache, update line count, before screen update. */ 329 if (lno >= ep->c_lno) 330 ep->c_lno = OOBLNO; 331 if (ep->c_nlines != OOBLNO) 332 ++ep->c_nlines; 333 334 /* File now dirty. */ 335 if (F_ISSET(ep, F_FIRSTMODIFY)) 336 (void)rcv_init(sp); 337 F_SET(ep, F_MODIFIED); 338 339 /* Log change. */ 340 log_line(sp, lno, LOG_LINE_INSERT); 341 342 /* Update marks, @ and global commands. */ 343 rval = 0; 344 if (mark_insdel(sp, LINE_INSERT, lno)) 345 rval = 1; 346 if (ex_g_insdel(sp, LINE_INSERT, lno)) 347 rval = 1; 348 349 /* Update screen. */ 350 return (scr_update(sp, lno, LINE_INSERT, 1) || rval); 351 } 352 353 /* 354 * db_set -- 355 * Store a line in the file. 356 * 357 * PUBLIC: int db_set(SCR *, recno_t, char *, size_t); 358 */ 359 int 360 db_set(SCR *sp, recno_t lno, char *p, size_t len) 361 { 362 DBT data, key; 363 EXF *ep; 364 365 #if defined(DEBUG) && 0 366 TRACE(sp, "replace line %lu: len %lu {%.*s}\n", 367 (u_long)lno, (u_long)len, MIN(len, 20), p); 368 #endif 369 370 /* Check for no underlying file. */ 371 if ((ep = sp->ep) == NULL) { 372 ex_emsg(sp, NULL, EXM_NOFILEYET); 373 return (1); 374 } 375 376 /* Log before change. */ 377 log_line(sp, lno, LOG_LINE_RESET_B); 378 379 /* Update file. */ 380 key.data = &lno; 381 key.size = sizeof(lno); 382 data.data = p; 383 data.size = len; 384 if (ep->db->put(ep->db, &key, &data, 0) == -1) { 385 msgq(sp, M_SYSERR, 386 "unable to store line %lu", (u_long)lno); 387 return (1); 388 } 389 390 /* Flush the cache, before logging or screen update. */ 391 if (lno == ep->c_lno) 392 ep->c_lno = OOBLNO; 393 394 /* File now dirty. */ 395 if (F_ISSET(ep, F_FIRSTMODIFY)) 396 (void)rcv_init(sp); 397 F_SET(ep, F_MODIFIED); 398 399 /* Log after change. */ 400 log_line(sp, lno, LOG_LINE_RESET_F); 401 402 /* Update screen. */ 403 return (scr_update(sp, lno, LINE_RESET, 1)); 404 } 405 406 /* 407 * db_exist -- 408 * Return if a line exists. 409 * 410 * PUBLIC: int db_exist(SCR *, recno_t); 411 */ 412 int 413 db_exist(SCR *sp, recno_t lno) 414 { 415 EXF *ep; 416 417 /* Check for no underlying file. */ 418 if ((ep = sp->ep) == NULL) { 419 ex_emsg(sp, NULL, EXM_NOFILEYET); 420 return (1); 421 } 422 423 if (lno == OOBLNO) 424 return (0); 425 426 /* 427 * Check the last-line number cache. Adjust the cached line 428 * number for the lines used by the text input buffers. 429 */ 430 if (ep->c_nlines != OOBLNO) 431 return (lno <= (F_ISSET(sp, SC_TINPUT) ? 432 ep->c_nlines + (TAILQ_LAST(&sp->tiq, _texth)->lno 433 - TAILQ_FIRST(&sp->tiq)->lno) : ep->c_nlines)); 434 435 /* Go get the line. */ 436 return (!db_get(sp, lno, 0, NULL, NULL)); 437 } 438 439 /* 440 * db_last -- 441 * Return the number of lines in the file. 442 * 443 * PUBLIC: int db_last(SCR *, recno_t *); 444 */ 445 int 446 db_last(SCR *sp, recno_t *lnop) 447 { 448 DBT data, key; 449 EXF *ep; 450 recno_t lno; 451 452 /* Check for no underlying file. */ 453 if ((ep = sp->ep) == NULL) { 454 ex_emsg(sp, NULL, EXM_NOFILEYET); 455 return (1); 456 } 457 458 /* 459 * Check the last-line number cache. Adjust the cached line 460 * number for the lines used by the text input buffers. 461 */ 462 if (ep->c_nlines != OOBLNO) { 463 *lnop = ep->c_nlines; 464 if (F_ISSET(sp, SC_TINPUT)) 465 *lnop += TAILQ_LAST(&sp->tiq, _texth)->lno - 466 TAILQ_FIRST(&sp->tiq)->lno; 467 return (0); 468 } 469 470 key.data = &lno; 471 key.size = sizeof(lno); 472 473 switch (ep->db->seq(ep->db, &key, &data, R_LAST)) { 474 case -1: 475 msgq(sp, M_SYSERR, "unable to get last line"); 476 *lnop = 0; 477 return (1); 478 case 1: 479 *lnop = 0; 480 return (0); 481 default: 482 break; 483 } 484 485 /* Fill the cache. */ 486 memcpy(&lno, key.data, sizeof(lno)); 487 ep->c_nlines = ep->c_lno = lno; 488 ep->c_len = data.size; 489 ep->c_lp = data.data; 490 491 /* Return the value. */ 492 *lnop = (F_ISSET(sp, SC_TINPUT) && 493 TAILQ_LAST(&sp->tiq, _texth)->lno > lno ? 494 TAILQ_LAST(&sp->tiq, _texth)->lno : lno); 495 return (0); 496 } 497 498 /* 499 * db_err -- 500 * Report a line error. 501 * 502 * PUBLIC: void db_err(SCR *, recno_t); 503 */ 504 void 505 db_err(SCR *sp, recno_t lno) 506 { 507 msgq(sp, M_ERR, 508 "Error: unable to retrieve line %lu", (u_long)lno); 509 } 510 511 /* 512 * scr_update -- 513 * Update all of the screens that are backed by the file that 514 * just changed. 515 */ 516 static int 517 scr_update(SCR *sp, recno_t lno, lnop_t op, int current) 518 { 519 EXF *ep; 520 SCR *tsp; 521 522 if (F_ISSET(sp, SC_EX)) 523 return (0); 524 525 ep = sp->ep; 526 if (ep->refcnt != 1) 527 TAILQ_FOREACH(tsp, &sp->gp->dq, q) 528 if (sp != tsp && tsp->ep == ep) 529 if (vs_change(tsp, lno, op)) 530 return (1); 531 return (current ? vs_change(sp, lno, op) : 0); 532 } 533