| File: | src/usr.bin/less/less/../linenum.c |
| Warning: | line 197, column 23 Access to field 'prev' results in a dereference of a null pointer (loaded from variable 'spare') |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
| 1 | /* | |||
| 2 | * Copyright (C) 1984-2012 Mark Nudelman | |||
| 3 | * Modified for use with illumos by Garrett D'Amore. | |||
| 4 | * Copyright 2014 Garrett D'Amore <garrett@damore.org> | |||
| 5 | * | |||
| 6 | * You may distribute under the terms of either the GNU General Public | |||
| 7 | * License or the Less License, as specified in the README file. | |||
| 8 | * | |||
| 9 | * For more information, see the README file. | |||
| 10 | */ | |||
| 11 | ||||
| 12 | /* | |||
| 13 | * Code to handle displaying line numbers. | |||
| 14 | * | |||
| 15 | * Finding the line number of a given file position is rather tricky. | |||
| 16 | * We don't want to just start at the beginning of the file and | |||
| 17 | * count newlines, because that is slow for large files (and also | |||
| 18 | * wouldn't work if we couldn't get to the start of the file; e.g. | |||
| 19 | * if input is a long pipe). | |||
| 20 | * | |||
| 21 | * So we use the function add_lnum to cache line numbers. | |||
| 22 | * We try to be very clever and keep only the more interesting | |||
| 23 | * line numbers when we run out of space in our table. A line | |||
| 24 | * number is more interesting than another when it is far from | |||
| 25 | * other line numbers. For example, we'd rather keep lines | |||
| 26 | * 100,200,300 than 100,101,300. 200 is more interesting than | |||
| 27 | * 101 because 101 can be derived very cheaply from 100, while | |||
| 28 | * 200 is more expensive to derive from 100. | |||
| 29 | * | |||
| 30 | * The function currline() returns the line number of a given | |||
| 31 | * position in the file. As a side effect, it calls add_lnum | |||
| 32 | * to cache the line number. Therefore currline is occasionally | |||
| 33 | * called to make sure we cache line numbers often enough. | |||
| 34 | */ | |||
| 35 | ||||
| 36 | #include <sys/time.h> | |||
| 37 | ||||
| 38 | #include <time.h> | |||
| 39 | ||||
| 40 | #include "less.h" | |||
| 41 | ||||
| 42 | /* | |||
| 43 | * Structure to keep track of a line number and the associated file position. | |||
| 44 | * A doubly-linked circular list of line numbers is kept ordered by line number. | |||
| 45 | */ | |||
| 46 | struct linenum_info { | |||
| 47 | struct linenum_info *next; /* Link to next in the list */ | |||
| 48 | struct linenum_info *prev; /* Line to previous in the list */ | |||
| 49 | off_t pos; /* File position */ | |||
| 50 | off_t gap; /* Gap between prev and next */ | |||
| 51 | off_t line; /* Line number */ | |||
| 52 | }; | |||
| 53 | /* | |||
| 54 | * "gap" needs some explanation: the gap of any particular line number | |||
| 55 | * is the distance between the previous one and the next one in the list. | |||
| 56 | * ("Distance" means difference in file position.) In other words, the | |||
| 57 | * gap of a line number is the gap which would be introduced if this | |||
| 58 | * line number were deleted. It is used to decide which one to replace | |||
| 59 | * when we have a new one to insert and the table is full. | |||
| 60 | */ | |||
| 61 | ||||
| 62 | #define NPOOL200 200 /* Size of line number pool */ | |||
| 63 | ||||
| 64 | #define LONGTIME(2) (2) /* In seconds */ | |||
| 65 | ||||
| 66 | static struct linenum_info anchor; /* Anchor of the list */ | |||
| 67 | static struct linenum_info *freelist; /* Anchor of the unused entries */ | |||
| 68 | static struct linenum_info pool[NPOOL200]; /* The pool itself */ | |||
| 69 | static struct linenum_info *spare; /* We always keep one spare entry */ | |||
| 70 | ||||
| 71 | extern int linenums; | |||
| 72 | extern int sc_height; | |||
| 73 | extern int screen_trashed; | |||
| 74 | ||||
| 75 | /* | |||
| 76 | * Initialize the line number structures. | |||
| 77 | */ | |||
| 78 | void | |||
| 79 | clr_linenum(void) | |||
| 80 | { | |||
| 81 | struct linenum_info *p; | |||
| 82 | ||||
| 83 | /* | |||
| 84 | * Put all the entries on the free list. | |||
| 85 | * Leave one for the "spare". | |||
| 86 | */ | |||
| 87 | for (p = pool; p < &pool[NPOOL200-2]; p++) | |||
| 88 | p->next = p+1; | |||
| 89 | pool[NPOOL200-2].next = NULL((void *)0); | |||
| 90 | freelist = pool; | |||
| 91 | ||||
| 92 | spare = &pool[NPOOL200-1]; | |||
| 93 | ||||
| 94 | /* | |||
| 95 | * Initialize the anchor. | |||
| 96 | */ | |||
| 97 | anchor.next = anchor.prev = &anchor; | |||
| 98 | anchor.gap = 0; | |||
| 99 | anchor.pos = 0; | |||
| 100 | anchor.line = 1; | |||
| 101 | } | |||
| 102 | ||||
| 103 | /* | |||
| 104 | * Calculate the gap for an entry. | |||
| 105 | */ | |||
| 106 | static void | |||
| 107 | calcgap(struct linenum_info *p) | |||
| 108 | { | |||
| 109 | /* | |||
| 110 | * Don't bother to compute a gap for the anchor. | |||
| 111 | * Also don't compute a gap for the last one in the list. | |||
| 112 | * The gap for that last one should be considered infinite, | |||
| 113 | * but we never look at it anyway. | |||
| 114 | */ | |||
| 115 | if (p == &anchor || p->next == &anchor) | |||
| 116 | return; | |||
| 117 | p->gap = p->next->pos - p->prev->pos; | |||
| 118 | } | |||
| 119 | ||||
| 120 | /* | |||
| 121 | * Add a new line number to the cache. | |||
| 122 | * The specified position (pos) should be the file position of the | |||
| 123 | * FIRST character in the specified line. | |||
| 124 | */ | |||
| 125 | void | |||
| 126 | add_lnum(off_t linenum, off_t pos) | |||
| 127 | { | |||
| 128 | struct linenum_info *p; | |||
| 129 | struct linenum_info *new; | |||
| 130 | struct linenum_info *nextp; | |||
| 131 | struct linenum_info *prevp; | |||
| 132 | off_t mingap; | |||
| 133 | ||||
| 134 | /* | |||
| 135 | * Find the proper place in the list for the new one. | |||
| 136 | * The entries are sorted by position. | |||
| 137 | */ | |||
| 138 | for (p = anchor.next; p != &anchor && p->pos < pos; p = p->next) | |||
| 139 | if (p->line == linenum) | |||
| 140 | /* We already have this one. */ | |||
| 141 | return; | |||
| 142 | nextp = p; | |||
| 143 | prevp = p->prev; | |||
| 144 | ||||
| 145 | if (freelist != NULL((void *)0)) { | |||
| 146 | /* | |||
| 147 | * We still have free (unused) entries. | |||
| 148 | * Use one of them. | |||
| 149 | */ | |||
| 150 | new = freelist; | |||
| 151 | freelist = freelist->next; | |||
| 152 | } else { | |||
| 153 | /* | |||
| 154 | * No free entries. | |||
| 155 | * Use the "spare" entry. | |||
| 156 | */ | |||
| 157 | new = spare; | |||
| 158 | spare = NULL((void *)0); | |||
| 159 | } | |||
| 160 | ||||
| 161 | /* | |||
| 162 | * Fill in the fields of the new entry, | |||
| 163 | * and insert it into the proper place in the list. | |||
| 164 | */ | |||
| 165 | new->next = nextp; | |||
| 166 | new->prev = prevp; | |||
| 167 | new->pos = pos; | |||
| 168 | new->line = linenum; | |||
| 169 | ||||
| 170 | nextp->prev = new; | |||
| 171 | prevp->next = new; | |||
| 172 | ||||
| 173 | /* | |||
| 174 | * Recalculate gaps for the new entry and the neighboring entries. | |||
| 175 | */ | |||
| 176 | calcgap(new); | |||
| 177 | calcgap(nextp); | |||
| 178 | calcgap(prevp); | |||
| 179 | ||||
| 180 | if (spare
| |||
| 181 | /* | |||
| 182 | * We have used the spare entry. | |||
| 183 | * Scan the list to find the one with the smallest | |||
| 184 | * gap, take it out and make it the spare. | |||
| 185 | * We should never remove the last one, so stop when | |||
| 186 | * we get to p->next == &anchor. This also avoids | |||
| 187 | * looking at the gap of the last one, which is | |||
| 188 | * not computed by calcgap. | |||
| 189 | */ | |||
| 190 | mingap = anchor.next->gap; | |||
| 191 | for (p = anchor.next; p->next != &anchor; p = p->next) { | |||
| 192 | if (p->gap <= mingap) { | |||
| 193 | spare = p; | |||
| 194 | mingap = p->gap; | |||
| 195 | } | |||
| 196 | } | |||
| 197 | spare->next->prev = spare->prev; | |||
| ||||
| 198 | spare->prev->next = spare->next; | |||
| 199 | } | |||
| 200 | } | |||
| 201 | ||||
| 202 | static int loopcount; | |||
| 203 | static struct timespec timeout; | |||
| 204 | ||||
| 205 | static void | |||
| 206 | timeout_set(int seconds) | |||
| 207 | { | |||
| 208 | clock_gettime(CLOCK_MONOTONIC3, &timeout); | |||
| 209 | timeout.tv_sec += seconds; | |||
| 210 | } | |||
| 211 | ||||
| 212 | static int | |||
| 213 | timeout_elapsed(void) | |||
| 214 | { | |||
| 215 | struct timespec now; | |||
| 216 | ||||
| 217 | clock_gettime(CLOCK_MONOTONIC3, &now); | |||
| 218 | return timespeccmp(&now, &timeout, >=)(((&now)->tv_sec == (&timeout)->tv_sec) ? ((& now)->tv_nsec >= (&timeout)->tv_nsec) : ((&now )->tv_sec >= (&timeout)->tv_sec)); | |||
| 219 | } | |||
| 220 | ||||
| 221 | static void | |||
| 222 | longish(void) | |||
| 223 | { | |||
| 224 | if (loopcount >= 0 && ++loopcount > 100) { | |||
| 225 | loopcount = 0; | |||
| 226 | if (timeout_elapsed()) { | |||
| 227 | ierror("Calculating line numbers", NULL((void *)0)); | |||
| 228 | loopcount = -1; | |||
| 229 | } | |||
| 230 | } | |||
| 231 | } | |||
| 232 | ||||
| 233 | /* | |||
| 234 | * Turn off line numbers because the user has interrupted | |||
| 235 | * a lengthy line number calculation. | |||
| 236 | */ | |||
| 237 | static void | |||
| 238 | abort_long(void) | |||
| 239 | { | |||
| 240 | if (linenums == OPT_ONPLUS2) | |||
| 241 | /* | |||
| 242 | * We were displaying line numbers, so need to repaint. | |||
| 243 | */ | |||
| 244 | screen_trashed = 1; | |||
| 245 | linenums = 0; | |||
| 246 | error("Line numbers turned off", NULL((void *)0)); | |||
| 247 | } | |||
| 248 | ||||
| 249 | /* | |||
| 250 | * Find the line number associated with a given position. | |||
| 251 | * Return 0 if we can't figure it out. | |||
| 252 | */ | |||
| 253 | off_t | |||
| 254 | find_linenum(off_t pos) | |||
| 255 | { | |||
| 256 | struct linenum_info *p; | |||
| 257 | off_t linenum; | |||
| 258 | off_t cpos; | |||
| 259 | ||||
| 260 | if (!linenums) | |||
| 261 | /* | |||
| 262 | * We're not using line numbers. | |||
| 263 | */ | |||
| 264 | return (0); | |||
| 265 | if (pos == -1) | |||
| 266 | /* | |||
| 267 | * Caller doesn't know what he's talking about. | |||
| 268 | */ | |||
| 269 | return (0); | |||
| 270 | if (pos <= ch_zero()(0)) | |||
| 271 | /* | |||
| 272 | * Beginning of file is always line number 1. | |||
| 273 | */ | |||
| 274 | return (1); | |||
| 275 | ||||
| 276 | /* | |||
| 277 | * Find the entry nearest to the position we want. | |||
| 278 | */ | |||
| 279 | for (p = anchor.next; p != &anchor && p->pos < pos; p = p->next) | |||
| 280 | continue; | |||
| 281 | if (p->pos == pos) | |||
| 282 | /* Found it exactly. */ | |||
| 283 | return (p->line); | |||
| 284 | ||||
| 285 | /* | |||
| 286 | * This is the (possibly) time-consuming part. | |||
| 287 | * We start at the line we just found and start | |||
| 288 | * reading the file forward or backward till we | |||
| 289 | * get to the place we want. | |||
| 290 | * | |||
| 291 | * First decide whether we should go forward from the | |||
| 292 | * previous one or backwards from the next one. | |||
| 293 | * The decision is based on which way involves | |||
| 294 | * traversing fewer bytes in the file. | |||
| 295 | */ | |||
| 296 | timeout_set(LONGTIME(2)); | |||
| 297 | if (p == &anchor || pos - p->prev->pos < p->pos - pos) { | |||
| 298 | /* | |||
| 299 | * Go forward. | |||
| 300 | */ | |||
| 301 | p = p->prev; | |||
| 302 | if (ch_seek(p->pos)) | |||
| 303 | return (0); | |||
| 304 | loopcount = 0; | |||
| 305 | for (linenum = p->line, cpos = p->pos; cpos < pos; linenum++) { | |||
| 306 | /* | |||
| 307 | * Allow a signal to abort this loop. | |||
| 308 | */ | |||
| 309 | cpos = forw_raw_line(cpos, NULL((void *)0), NULL((void *)0)); | |||
| 310 | if (abort_sigs()) { | |||
| 311 | abort_long(); | |||
| 312 | return (0); | |||
| 313 | } | |||
| 314 | if (cpos == -1) | |||
| 315 | return (0); | |||
| 316 | longish(); | |||
| 317 | } | |||
| 318 | /* | |||
| 319 | * We might as well cache it. | |||
| 320 | */ | |||
| 321 | add_lnum(linenum, cpos); | |||
| 322 | /* | |||
| 323 | * If the given position is not at the start of a line, | |||
| 324 | * make sure we return the correct line number. | |||
| 325 | */ | |||
| 326 | if (cpos > pos) | |||
| 327 | linenum--; | |||
| 328 | } else { | |||
| 329 | /* | |||
| 330 | * Go backward. | |||
| 331 | */ | |||
| 332 | if (ch_seek(p->pos)) | |||
| 333 | return (0); | |||
| 334 | loopcount = 0; | |||
| 335 | for (linenum = p->line, cpos = p->pos; cpos > pos; linenum--) { | |||
| 336 | /* | |||
| 337 | * Allow a signal to abort this loop. | |||
| 338 | */ | |||
| 339 | cpos = back_raw_line(cpos, NULL((void *)0), NULL((void *)0)); | |||
| 340 | if (abort_sigs()) { | |||
| 341 | abort_long(); | |||
| 342 | return (0); | |||
| 343 | } | |||
| 344 | if (cpos == -1) | |||
| 345 | return (0); | |||
| 346 | longish(); | |||
| 347 | } | |||
| 348 | /* | |||
| 349 | * We might as well cache it. | |||
| 350 | */ | |||
| 351 | add_lnum(linenum, cpos); | |||
| 352 | } | |||
| 353 | ||||
| 354 | return (linenum); | |||
| 355 | } | |||
| 356 | ||||
| 357 | /* | |||
| 358 | * Find the position of a given line number. | |||
| 359 | * Return -1 if we can't figure it out. | |||
| 360 | */ | |||
| 361 | off_t | |||
| 362 | find_pos(off_t linenum) | |||
| 363 | { | |||
| 364 | struct linenum_info *p; | |||
| 365 | off_t cpos; | |||
| 366 | off_t clinenum; | |||
| 367 | ||||
| 368 | if (linenum <= 1) | |||
| 369 | /* | |||
| 370 | * Line number 1 is beginning of file. | |||
| 371 | */ | |||
| 372 | return (ch_zero()(0)); | |||
| 373 | ||||
| 374 | /* | |||
| 375 | * Find the entry nearest to the line number we want. | |||
| 376 | */ | |||
| 377 | for (p = anchor.next; p != &anchor && p->line < linenum; p = p->next) | |||
| 378 | continue; | |||
| 379 | if (p->line == linenum) | |||
| 380 | /* Found it exactly. */ | |||
| 381 | return (p->pos); | |||
| 382 | ||||
| 383 | if (p == &anchor || linenum - p->prev->line < p->line - linenum) { | |||
| 384 | /* | |||
| 385 | * Go forward. | |||
| 386 | */ | |||
| 387 | p = p->prev; | |||
| 388 | if (ch_seek(p->pos)) | |||
| 389 | return (-1); | |||
| 390 | for (clinenum = p->line, cpos = p->pos; | |||
| 391 | clinenum < linenum; | |||
| 392 | clinenum++) { | |||
| 393 | /* | |||
| 394 | * Allow a signal to abort this loop. | |||
| 395 | */ | |||
| 396 | cpos = forw_raw_line(cpos, NULL((void *)0), NULL((void *)0)); | |||
| 397 | if (abort_sigs()) | |||
| 398 | return (-1); | |||
| 399 | if (cpos == -1) | |||
| 400 | return (-1); | |||
| 401 | } | |||
| 402 | } else { | |||
| 403 | /* | |||
| 404 | * Go backward. | |||
| 405 | */ | |||
| 406 | if (ch_seek(p->pos)) | |||
| 407 | return (-1); | |||
| 408 | for (clinenum = p->line, cpos = p->pos; | |||
| 409 | clinenum > linenum; | |||
| 410 | clinenum--) { | |||
| 411 | /* | |||
| 412 | * Allow a signal to abort this loop. | |||
| 413 | */ | |||
| 414 | cpos = back_raw_line(cpos, (char **)NULL((void *)0), (int *)NULL((void *)0)); | |||
| 415 | if (abort_sigs()) | |||
| 416 | return (-1); | |||
| 417 | if (cpos == -1) | |||
| 418 | return (-1); | |||
| 419 | } | |||
| 420 | } | |||
| 421 | /* | |||
| 422 | * We might as well cache it. | |||
| 423 | */ | |||
| 424 | add_lnum(clinenum, cpos); | |||
| 425 | return (cpos); | |||
| 426 | } | |||
| 427 | ||||
| 428 | /* | |||
| 429 | * Return the line number of the "current" line. | |||
| 430 | * The argument "where" tells which line is to be considered | |||
| 431 | * the "current" line (e.g. TOP, BOTTOM, MIDDLE, etc). | |||
| 432 | */ | |||
| 433 | off_t | |||
| 434 | currline(int where) | |||
| 435 | { | |||
| 436 | off_t pos; | |||
| 437 | off_t len; | |||
| 438 | off_t linenum; | |||
| 439 | ||||
| 440 | pos = position(where); | |||
| 441 | len = ch_length(); | |||
| 442 | while (pos == -1 && where >= 0 && where < sc_height) | |||
| ||||
| 443 | pos = position(++where); | |||
| 444 | if (pos == -1) | |||
| 445 | pos = len; | |||
| 446 | linenum = find_linenum(pos); | |||
| 447 | if (pos == len) | |||
| 448 | linenum--; | |||
| 449 | return (linenum); | |||
| 450 | } |