1 /* 2 * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. 3 * 4 * All rights reserved. 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining a 7 * copy of this software and associated documentation files (the 8 * "Software"), to deal in the Software without restriction, including 9 * without limitation the rights to use, copy, modify, merge, publish, 10 * distribute, and/or sell copies of the Software, and to permit persons 11 * to whom the Software is furnished to do so, provided that the above 12 * copyright notice(s) and this permission notice appear in all copies of 13 * the Software and that both the above copyright notice(s) and this 14 * permission notice appear in supporting documentation. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 19 * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 20 * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL 21 * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING 22 * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, 23 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION 24 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 25 * 26 * Except as contained in this notice, the name of a copyright holder 27 * shall not be used in advertising or otherwise to promote the sale, use 28 * or other dealings in this Software without prior written authorization 29 * of the copyright holder. 30 */ 31 32 /* 33 * Copyright 2004 Sun Microsystems, Inc. All rights reserved. 34 * Use is subject to license terms. 35 */ 36 37 #include <stdlib.h> 38 #include <stdio.h> 39 #include <string.h> 40 #include <ctype.h> 41 #include <time.h> 42 #include <errno.h> 43 44 #include "ioutil.h" 45 #include "history.h" 46 #include "freelist.h" 47 #include "errmsg.h" 48 49 /* 50 * History lines are split into sub-strings of GLH_SEG_SIZE 51 * characters. To avoid wasting space in the GlhLineSeg structure, 52 * this should be a multiple of the size of a pointer. 53 */ 54 #define GLH_SEG_SIZE 16 55 56 /* 57 * GlhLineSeg structures contain fixed sized segments of a larger 58 * string. These are linked into lists to record strings, with all but 59 * the last segment having GLH_SEG_SIZE characters. The last segment 60 * of a string is terminated within the GLH_SEG_SIZE characters with a 61 * '\0'. 62 */ 63 typedef struct GlhLineSeg GlhLineSeg; 64 struct GlhLineSeg { 65 GlhLineSeg *next; /* The next sub-string of the history line */ 66 char s[GLH_SEG_SIZE]; /* The sub-string. Beware that only the final */ 67 /* substring of a line, as indicated by 'next' */ 68 /* being NULL, is '\0' terminated. */ 69 }; 70 71 /* 72 * History lines are recorded in a hash table, such that repeated 73 * lines are stored just once. 74 * 75 * Start by defining the size of the hash table. This should be a 76 * prime number. 77 */ 78 #define GLH_HASH_SIZE 113 79 80 typedef struct GlhHashBucket GlhHashBucket; 81 82 /* 83 * Each history line will be represented in the hash table by a 84 * structure of the following type. 85 */ 86 typedef struct GlhHashNode GlhHashNode; 87 struct GlhHashNode { 88 GlhHashBucket *bucket; /* The parent hash-table bucket of this node */ 89 GlhHashNode *next; /* The next in the list of nodes within the */ 90 /* parent hash-table bucket. */ 91 GlhLineSeg *head; /* The list of sub-strings which make up a line */ 92 int len; /* The length of the line, excluding any '\0' */ 93 int used; /* The number of times this string is pointed to by */ 94 /* the time-ordered list of history lines. */ 95 int reported; /* A flag that is used when searching to ensure that */ 96 /* a line isn't reported redundantly. */ 97 }; 98 99 /* 100 * How many new GlhHashNode elements should be allocated at a time? 101 */ 102 #define GLH_HASH_INCR 50 103 104 static int _glh_is_line(GlhHashNode *hash, const char *line, size_t n); 105 static int _glh_line_matches_prefix(GlhHashNode *line, GlhHashNode *prefix); 106 static void _glh_return_line(GlhHashNode *hash, char *line, size_t dim); 107 108 /* 109 * All history lines which hash to a given bucket in the hash table, are 110 * recorded in a structure of the following type. 111 */ 112 struct GlhHashBucket { 113 GlhHashNode *lines; /* The list of history lines which fall in this bucket */ 114 }; 115 116 static GlhHashBucket *glh_find_bucket(GlHistory *glh, const char *line, 117 size_t n); 118 static GlhHashNode *glh_find_hash_node(GlhHashBucket *bucket, const char *line, 119 size_t n); 120 121 typedef struct { 122 FreeList *node_mem; /* A free-list of GlhHashNode structures */ 123 GlhHashBucket bucket[GLH_HASH_SIZE]; /* The buckets of the hash table */ 124 } GlhLineHash; 125 126 /* 127 * GlhLineNode's are used to record history lines in time order. 128 */ 129 typedef struct GlhLineNode GlhLineNode; 130 struct GlhLineNode { 131 long id; /* The unique identifier of this history line */ 132 time_t timestamp; /* The time at which the line was archived */ 133 unsigned group; /* The identifier of the history group to which the */ 134 /* the line belongs. */ 135 GlhLineNode *next; /* The next youngest line in the list */ 136 GlhLineNode *prev; /* The next oldest line in the list */ 137 GlhHashNode *line; /* The hash-table entry of the history line */ 138 }; 139 140 /* 141 * The number of GlhLineNode elements per freelist block. 142 */ 143 #define GLH_LINE_INCR 100 144 145 /* 146 * Encapsulate the time-ordered list of historical lines. 147 */ 148 typedef struct { 149 FreeList *node_mem; /* A freelist of GlhLineNode objects */ 150 GlhLineNode *head; /* The oldest line in the list */ 151 GlhLineNode *tail; /* The newest line in the list */ 152 } GlhLineList; 153 154 /* 155 * The _glh_lookup_history() returns copies of history lines in a 156 * dynamically allocated array. This array is initially allocated 157 * GLH_LOOKUP_SIZE bytes. If subsequently this size turns out to be 158 * too small, realloc() is used to increase its size to the required 159 * size plus GLH_LOOKUP_MARGIN. The idea of the later parameter is to 160 * reduce the number of realloc() operations needed. 161 */ 162 #define GLH_LBUF_SIZE 300 163 #define GLH_LBUF_MARGIN 100 164 165 /* 166 * Encapsulate all of the resources needed to store historical input lines. 167 */ 168 struct GlHistory { 169 ErrMsg *err; /* The error-reporting buffer */ 170 GlhLineSeg *buffer; /* An array of sub-line nodes to be partitioned */ 171 /* into lists of sub-strings recording input lines. */ 172 int nbuff; /* The allocated dimension of buffer[] */ 173 GlhLineSeg *unused; /* The list of free nodes in buffer[] */ 174 GlhLineList list; /* A time ordered list of history lines */ 175 GlhLineNode *recall; /* The last line recalled, or NULL if no recall */ 176 /* session is currently active. */ 177 GlhLineNode *id_node;/* The node at which the last ID search terminated */ 178 GlhLineHash hash; /* A hash-table of reference-counted history lines */ 179 GlhHashNode *prefix; /* A pointer to a line containing the prefix that */ 180 /* is being searched for. Note that if prefix==NULL */ 181 /* and prefix_len>0, this means that no line in */ 182 /* the buffer starts with the requested prefix. */ 183 int prefix_len; /* The length of the prefix being searched for. */ 184 char *lbuf; /* The array in which _glh_lookup_history() returns */ 185 /* history lines */ 186 int lbuf_dim; /* The allocated size of lbuf[] */ 187 int nbusy; /* The number of line segments in buffer[] that are */ 188 /* currently being used to record sub-lines */ 189 int nfree; /* The number of line segments in buffer that are */ 190 /* not currently being used to record sub-lines */ 191 unsigned long seq; /* The next ID to assign to a line node */ 192 unsigned group; /* The identifier of the current history group */ 193 int nline; /* The number of lines currently in the history list */ 194 int max_lines; /* Either -1 or a ceiling on the number of lines */ 195 int enable; /* If false, ignore history additions and lookups */ 196 }; 197 198 #ifndef WITHOUT_FILE_SYSTEM 199 static int _glh_cant_load_history(GlHistory *glh, const char *filename, 200 int lineno, const char *message, FILE *fp); 201 static int _glh_cant_save_history(GlHistory *glh, const char *message, 202 const char *filename, FILE *fp); 203 static int _glh_write_timestamp(FILE *fp, time_t timestamp); 204 static int _glh_decode_timestamp(char *string, char **endp, time_t *timestamp); 205 #endif 206 static void _glh_discard_line(GlHistory *glh, GlhLineNode *node); 207 static GlhLineNode *_glh_find_id(GlHistory *glh, GlhLineID id); 208 static GlhHashNode *_glh_acquire_copy(GlHistory *glh, const char *line, 209 size_t n); 210 static GlhHashNode *_glh_discard_copy(GlHistory *glh, GlhHashNode *hnode); 211 static int _glh_prepare_for_recall(GlHistory *glh, char *line); 212 213 /* 214 * The following structure and functions are used to iterate through 215 * the characters of a segmented history line. 216 */ 217 typedef struct { 218 GlhLineSeg *seg; /* The line segment that the next character will */ 219 /* be returned from. */ 220 int posn; /* The index in the above line segment, containing */ 221 /* the next unread character. */ 222 char c; /* The current character in the input line */ 223 } GlhLineStream; 224 static void glh_init_stream(GlhLineStream *str, GlhHashNode *line); 225 static void glh_step_stream(GlhLineStream *str); 226 227 /* 228 * See if search prefix contains any globbing characters. 229 */ 230 static int glh_contains_glob(GlhHashNode *prefix); 231 /* 232 * Match a line against a search pattern. 233 */ 234 static int glh_line_matches_glob(GlhLineStream *lstr, GlhLineStream *pstr); 235 static int glh_matches_range(char c, GlhLineStream *pstr); 236 237 /*....................................................................... 238 * Create a line history maintenance object. 239 * 240 * Input: 241 * buflen size_t The number of bytes to allocate to the 242 * buffer that is used to record all of the 243 * most recent lines of user input that will fit. 244 * If buflen==0, no buffer will be allocated. 245 * Output: 246 * return GlHistory * The new object, or NULL on error. 247 */ 248 GlHistory *_new_GlHistory(size_t buflen) 249 { 250 GlHistory *glh; /* The object to be returned */ 251 int i; 252 /* 253 * Allocate the container. 254 */ 255 glh = (GlHistory *) malloc(sizeof(GlHistory)); 256 if(!glh) { 257 errno = ENOMEM; 258 return NULL; 259 }; 260 /* 261 * Before attempting any operation that might fail, initialize the 262 * container at least up to the point at which it can safely be passed 263 * to _del_GlHistory(). 264 */ 265 glh->err = NULL; 266 glh->buffer = NULL; 267 glh->nbuff = (buflen+GLH_SEG_SIZE-1) / GLH_SEG_SIZE; 268 glh->unused = NULL; 269 glh->list.node_mem = NULL; 270 glh->list.head = glh->list.tail = NULL; 271 glh->recall = NULL; 272 glh->id_node = NULL; 273 glh->hash.node_mem = NULL; 274 for(i=0; i<GLH_HASH_SIZE; i++) 275 glh->hash.bucket[i].lines = NULL; 276 glh->prefix = NULL; 277 glh->lbuf = NULL; 278 glh->lbuf_dim = 0; 279 glh->nbusy = 0; 280 glh->nfree = glh->nbuff; 281 glh->seq = 0; 282 glh->group = 0; 283 glh->nline = 0; 284 glh->max_lines = -1; 285 glh->enable = 1; 286 /* 287 * Allocate a place to record error messages. 288 */ 289 glh->err = _new_ErrMsg(); 290 if(!glh->err) 291 return _del_GlHistory(glh); 292 /* 293 * Allocate the buffer, if required. 294 */ 295 if(glh->nbuff > 0) { 296 glh->nbuff = glh->nfree; 297 glh->buffer = (GlhLineSeg *) malloc(sizeof(GlhLineSeg) * glh->nbuff); 298 if(!glh->buffer) { 299 errno = ENOMEM; 300 return _del_GlHistory(glh); 301 }; 302 /* 303 * All nodes of the buffer are currently unused, so link them all into 304 * a list and make glh->unused point to the head of this list. 305 */ 306 glh->unused = glh->buffer; 307 for(i=0; i<glh->nbuff-1; i++) { 308 GlhLineSeg *seg = glh->unused + i; 309 seg->next = seg + 1; 310 }; 311 glh->unused[i].next = NULL; 312 }; 313 /* 314 * Allocate the GlhLineNode freelist. 315 */ 316 glh->list.node_mem = _new_FreeList(sizeof(GlhLineNode), GLH_LINE_INCR); 317 if(!glh->list.node_mem) 318 return _del_GlHistory(glh); 319 /* 320 * Allocate the GlhHashNode freelist. 321 */ 322 glh->hash.node_mem = _new_FreeList(sizeof(GlhLineNode), GLH_HASH_INCR); 323 if(!glh->hash.node_mem) 324 return _del_GlHistory(glh); 325 /* 326 * Allocate the array that _glh_lookup_history() uses to return a 327 * copy of a given history line. This will be resized when necessary. 328 */ 329 glh->lbuf_dim = GLH_LBUF_SIZE; 330 glh->lbuf = (char *) malloc(glh->lbuf_dim); 331 if(!glh->lbuf) { 332 errno = ENOMEM; 333 return _del_GlHistory(glh); 334 }; 335 return glh; 336 } 337 338 /*....................................................................... 339 * Delete a GlHistory object. 340 * 341 * Input: 342 * glh GlHistory * The object to be deleted. 343 * Output: 344 * return GlHistory * The deleted object (always NULL). 345 */ 346 GlHistory *_del_GlHistory(GlHistory *glh) 347 { 348 if(glh) { 349 /* 350 * Delete the error-message buffer. 351 */ 352 glh->err = _del_ErrMsg(glh->err); 353 /* 354 * Delete the buffer. 355 */ 356 if(glh->buffer) { 357 free(glh->buffer); 358 glh->buffer = NULL; 359 glh->unused = NULL; 360 }; 361 /* 362 * Delete the freelist of GlhLineNode's. 363 */ 364 glh->list.node_mem = _del_FreeList(glh->list.node_mem, 1); 365 /* 366 * The contents of the list were deleted by deleting the freelist. 367 */ 368 glh->list.head = NULL; 369 glh->list.tail = NULL; 370 /* 371 * Delete the freelist of GlhHashNode's. 372 */ 373 glh->hash.node_mem = _del_FreeList(glh->hash.node_mem, 1); 374 /* 375 * Delete the lookup buffer. 376 */ 377 if(glh->lbuf) 378 free(glh->lbuf); 379 /* 380 * Delete the container. 381 */ 382 free(glh); 383 }; 384 return NULL; 385 } 386 387 /*....................................................................... 388 * Append a new line to the history list, deleting old lines to make 389 * room, if needed. 390 * 391 * Input: 392 * glh GlHistory * The input-line history maintenance object. 393 * line char * The line to be archived. 394 * force int Unless this flag is non-zero, empty lines aren't 395 * archived. This flag requests that the line be 396 * archived regardless. 397 * Output: 398 * return int 0 - OK. 399 * 1 - Error. 400 */ 401 int _glh_add_history(GlHistory *glh, const char *line, int force) 402 { 403 int slen; /* The length of the line to be recorded (minus the '\0') */ 404 int empty; /* True if the string is empty */ 405 const char *nlptr; /* A pointer to a newline character in line[] */ 406 GlhHashNode *hnode; /* The hash-table node of the line */ 407 GlhLineNode *lnode; /* A node in the time-ordered list of lines */ 408 int i; 409 /* 410 * Check the arguments. 411 */ 412 if(!glh || !line) { 413 errno = EINVAL; 414 return 1; 415 }; 416 /* 417 * Is history enabled? 418 */ 419 if(!glh->enable || !glh->buffer || glh->max_lines == 0) 420 return 0; 421 /* 422 * Cancel any ongoing search. 423 */ 424 if(_glh_cancel_search(glh)) 425 return 1; 426 /* 427 * How long is the string to be recorded, being careful not to include 428 * any terminating '\n' character. 429 */ 430 nlptr = strchr(line, '\n'); 431 if(nlptr) 432 slen = (nlptr - line); 433 else 434 slen = strlen(line); 435 /* 436 * Is the line empty? 437 */ 438 empty = 1; 439 for(i=0; i<slen && empty; i++) 440 empty = isspace((int)(unsigned char) line[i]); 441 /* 442 * If the line is empty, don't add it to the buffer unless explicitly 443 * told to. 444 */ 445 if(empty && !force) 446 return 0; 447 /* 448 * Has an upper limit to the number of lines in the history list been 449 * specified? 450 */ 451 if(glh->max_lines >= 0) { 452 /* 453 * If necessary, remove old lines until there is room to add one new 454 * line without exceeding the specified line limit. 455 */ 456 while(glh->nline > 0 && glh->nline >= glh->max_lines) 457 _glh_discard_line(glh, glh->list.head); 458 /* 459 * We can't archive the line if the maximum number of lines allowed is 460 * zero. 461 */ 462 if(glh->max_lines == 0) 463 return 0; 464 }; 465 /* 466 * Unless already stored, store a copy of the line in the history buffer, 467 * then return a reference-counted hash-node pointer to this copy. 468 */ 469 hnode = _glh_acquire_copy(glh, line, slen); 470 if(!hnode) { 471 _err_record_msg(glh->err, "No room to store history line", END_ERR_MSG); 472 errno = ENOMEM; 473 return 1; 474 }; 475 /* 476 * Allocate a new node in the time-ordered list of lines. 477 */ 478 lnode = (GlhLineNode *) _new_FreeListNode(glh->list.node_mem); 479 /* 480 * If a new line-node couldn't be allocated, discard our copy of the 481 * stored line before reporting the error. 482 */ 483 if(!lnode) { 484 hnode = _glh_discard_copy(glh, hnode); 485 _err_record_msg(glh->err, "No room to store history line", END_ERR_MSG); 486 errno = ENOMEM; 487 return 1; 488 }; 489 /* 490 * Record a pointer to the hash-table record of the line in the new 491 * list node. 492 */ 493 lnode->id = glh->seq++; 494 lnode->timestamp = time(NULL); 495 lnode->group = glh->group; 496 lnode->line = hnode; 497 /* 498 * Append the new node to the end of the time-ordered list. 499 */ 500 if(glh->list.head) 501 glh->list.tail->next = lnode; 502 else 503 glh->list.head = lnode; 504 lnode->next = NULL; 505 lnode->prev = glh->list.tail; 506 glh->list.tail = lnode; 507 /* 508 * Record the addition of a line to the list. 509 */ 510 glh->nline++; 511 return 0; 512 } 513 514 /*....................................................................... 515 * Recall the next oldest line that has the search prefix last recorded 516 * by _glh_search_prefix(). 517 * 518 * Input: 519 * glh GlHistory * The input-line history maintenance object. 520 * line char * The input line buffer. On input this should contain 521 * the current input line, and on output, if anything 522 * was found, its contents will have been replaced 523 * with the matching line. 524 * dim size_t The allocated dimension of the line buffer. 525 * Output: 526 * return char * A pointer to line[0], or NULL if not found. 527 */ 528 char *_glh_find_backwards(GlHistory *glh, char *line, size_t dim) 529 { 530 GlhLineNode *node; /* The line location node being checked */ 531 GlhHashNode *old_line; /* The previous recalled line */ 532 /* 533 * Check the arguments. 534 */ 535 if(!glh || !line) { 536 if(glh) 537 _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG); 538 errno = EINVAL; 539 return NULL; 540 }; 541 /* 542 * Is history enabled? 543 */ 544 if(!glh->enable || !glh->buffer || glh->max_lines == 0) 545 return NULL; 546 /* 547 * Check the line dimensions. 548 */ 549 if(dim < strlen(line) + 1) { 550 _err_record_msg(glh->err, "'dim' argument inconsistent with strlen(line)", 551 END_ERR_MSG); 552 errno = EINVAL; 553 return NULL; 554 }; 555 /* 556 * Preserve the input line if needed. 557 */ 558 if(_glh_prepare_for_recall(glh, line)) 559 return NULL; 560 /* 561 * From where should we start the search? 562 */ 563 if(glh->recall) { 564 node = glh->recall->prev; 565 old_line = glh->recall->line; 566 } else { 567 node = glh->list.tail; 568 old_line = NULL; 569 }; 570 /* 571 * Search backwards through the list for the first match with the 572 * prefix string that differs from the last line that was recalled. 573 */ 574 while(node && (node->group != glh->group || node->line == old_line || 575 !_glh_line_matches_prefix(node->line, glh->prefix))) 576 node = node->prev; 577 /* 578 * Was a matching line found? 579 */ 580 if(node) { 581 /* 582 * Recall the found node as the starting point for subsequent 583 * searches. 584 */ 585 glh->recall = node; 586 /* 587 * Copy the matching line into the provided line buffer. 588 */ 589 _glh_return_line(node->line, line, dim); 590 /* 591 * Return it. 592 */ 593 return line; 594 }; 595 /* 596 * No match was found. 597 */ 598 return NULL; 599 } 600 601 /*....................................................................... 602 * Recall the next newest line that has the search prefix last recorded 603 * by _glh_search_prefix(). 604 * 605 * Input: 606 * glh GlHistory * The input-line history maintenance object. 607 * line char * The input line buffer. On input this should contain 608 * the current input line, and on output, if anything 609 * was found, its contents will have been replaced 610 * with the matching line. 611 * dim size_t The allocated dimensions of the line buffer. 612 * Output: 613 * return char * The line requested, or NULL if no matching line 614 * was found. 615 */ 616 char *_glh_find_forwards(GlHistory *glh, char *line, size_t dim) 617 { 618 GlhLineNode *node; /* The line location node being checked */ 619 GlhHashNode *old_line; /* The previous recalled line */ 620 /* 621 * Check the arguments. 622 */ 623 if(!glh || !line) { 624 if(glh) 625 _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG); 626 errno = EINVAL; 627 return NULL; 628 }; 629 /* 630 * Is history enabled? 631 */ 632 if(!glh->enable || !glh->buffer || glh->max_lines == 0) 633 return NULL; 634 /* 635 * Check the line dimensions. 636 */ 637 if(dim < strlen(line) + 1) { 638 _err_record_msg(glh->err, "'dim' argument inconsistent with strlen(line)", 639 END_ERR_MSG); 640 errno = EINVAL; 641 return NULL; 642 }; 643 /* 644 * From where should we start the search? 645 */ 646 if(glh->recall) { 647 node = glh->recall->next; 648 old_line = glh->recall->line; 649 } else { 650 return NULL; 651 }; 652 /* 653 * Search forwards through the list for the first match with the 654 * prefix string. 655 */ 656 while(node && (node->group != glh->group || node->line == old_line || 657 !_glh_line_matches_prefix(node->line, glh->prefix))) 658 node = node->next; 659 /* 660 * Was a matching line found? 661 */ 662 if(node) { 663 /* 664 * Copy the matching line into the provided line buffer. 665 */ 666 _glh_return_line(node->line, line, dim); 667 /* 668 * Record the starting point of the next search. 669 */ 670 glh->recall = node; 671 /* 672 * If we just returned the line that was being entered when the search 673 * session first started, cancel the search. 674 */ 675 if(node == glh->list.tail) 676 _glh_cancel_search(glh); 677 /* 678 * Return the matching line to the user. 679 */ 680 return line; 681 }; 682 /* 683 * No match was found. 684 */ 685 return NULL; 686 } 687 688 /*....................................................................... 689 * If a search is in progress, cancel it. 690 * 691 * This involves discarding the line that was temporarily saved by 692 * _glh_find_backwards() when the search was originally started, 693 * and reseting the search iteration pointer to NULL. 694 * 695 * Input: 696 * glh GlHistory * The input-line history maintenance object. 697 * Output: 698 * return int 0 - OK. 699 * 1 - Error. 700 */ 701 int _glh_cancel_search(GlHistory *glh) 702 { 703 /* 704 * Check the arguments. 705 */ 706 if(!glh) { 707 errno = EINVAL; 708 return 1; 709 }; 710 /* 711 * If there wasn't a search in progress, do nothing. 712 */ 713 if(!glh->recall) 714 return 0; 715 /* 716 * Reset the search pointers. Note that it is essential to set 717 * glh->recall to NULL before calling _glh_discard_line(), to avoid an 718 * infinite recursion. 719 */ 720 glh->recall = NULL; 721 /* 722 * Delete the node of the preserved line. 723 */ 724 _glh_discard_line(glh, glh->list.tail); 725 return 0; 726 } 727 728 /*....................................................................... 729 * Set the prefix of subsequent history searches. 730 * 731 * Input: 732 * glh GlHistory * The input-line history maintenance object. 733 * line const char * The command line who's prefix is to be used. 734 * prefix_len int The length of the prefix. 735 * Output: 736 * return int 0 - OK. 737 * 1 - Error. 738 */ 739 int _glh_search_prefix(GlHistory *glh, const char *line, int prefix_len) 740 { 741 /* 742 * Check the arguments. 743 */ 744 if(!glh) { 745 errno = EINVAL; 746 return 1; 747 }; 748 /* 749 * Is history enabled? 750 */ 751 if(!glh->enable || !glh->buffer || glh->max_lines == 0) 752 return 0; 753 /* 754 * Discard any existing prefix. 755 */ 756 glh->prefix = _glh_discard_copy(glh, glh->prefix); 757 /* 758 * Only store a copy of the prefix string if it isn't a zero-length string. 759 */ 760 if(prefix_len > 0) { 761 /* 762 * Get a reference-counted copy of the prefix from the history cache buffer. 763 */ 764 glh->prefix = _glh_acquire_copy(glh, line, prefix_len); 765 /* 766 * Was there insufficient buffer space? 767 */ 768 if(!glh->prefix) { 769 _err_record_msg(glh->err, "The search prefix is too long to store", 770 END_ERR_MSG); 771 errno = ENOMEM; 772 return 1; 773 }; 774 }; 775 return 0; 776 } 777 778 /*....................................................................... 779 * Recall the oldest recorded line. 780 * 781 * Input: 782 * glh GlHistory * The input-line history maintenance object. 783 * line char * The input line buffer. On input this should contain 784 * the current input line, and on output, its contents 785 * will have been replaced with the oldest line. 786 * dim size_t The allocated dimensions of the line buffer. 787 * Output: 788 * return char * A pointer to line[0], or NULL if not found. 789 */ 790 char *_glh_oldest_line(GlHistory *glh, char *line, size_t dim) 791 { 792 GlhLineNode *node; /* The line location node being checked */ 793 /* 794 * Check the arguments. 795 */ 796 if(!glh || !line) { 797 if(glh) 798 _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG); 799 errno = EINVAL; 800 return NULL; 801 }; 802 /* 803 * Is history enabled? 804 */ 805 if(!glh->enable || !glh->buffer || glh->max_lines == 0) 806 return NULL; 807 /* 808 * Check the line dimensions. 809 */ 810 if(dim < strlen(line) + 1) { 811 _err_record_msg(glh->err, "'dim' argument inconsistent with strlen(line)", 812 END_ERR_MSG); 813 errno = EINVAL; 814 return NULL; 815 }; 816 /* 817 * Preserve the input line if needed. 818 */ 819 if(_glh_prepare_for_recall(glh, line)) 820 return NULL; 821 /* 822 * Locate the oldest line that belongs to the current group. 823 */ 824 for(node=glh->list.head; node && node->group != glh->group; 825 node = node->next) 826 ; 827 /* 828 * No line found? 829 */ 830 if(!node) 831 return NULL; 832 /* 833 * Record the above node as the starting point for subsequent 834 * searches. 835 */ 836 glh->recall = node; 837 /* 838 * Copy the recalled line into the provided line buffer. 839 */ 840 _glh_return_line(node->line, line, dim); 841 /* 842 * If we just returned the line that was being entered when the search 843 * session first started, cancel the search. 844 */ 845 if(node == glh->list.tail) 846 _glh_cancel_search(glh); 847 return line; 848 } 849 850 /*....................................................................... 851 * Recall the line that was being entered when the search started. 852 * 853 * Input: 854 * glh GlHistory * The input-line history maintenance object. 855 * line char * The input line buffer. On input this should contain 856 * the current input line, and on output, its contents 857 * will have been replaced with the line that was 858 * being entered when the search was started. 859 * dim size_t The allocated dimensions of the line buffer. 860 * Output: 861 * return char * A pointer to line[0], or NULL if not found. 862 */ 863 char *_glh_current_line(GlHistory *glh, char *line, size_t dim) 864 { 865 /* 866 * Check the arguments. 867 */ 868 if(!glh || !line) { 869 if(glh) 870 _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG); 871 errno = EINVAL; 872 return NULL; 873 }; 874 /* 875 * If history isn't enabled, or no history search has yet been started, 876 * ignore the call. 877 */ 878 if(!glh->enable || !glh->buffer || glh->max_lines == 0 || !glh->recall) 879 return NULL; 880 /* 881 * Check the line dimensions. 882 */ 883 if(dim < strlen(line) + 1) { 884 _err_record_msg(glh->err, "'dim' argument inconsistent with strlen(line)", 885 END_ERR_MSG); 886 errno = EINVAL; 887 return NULL; 888 }; 889 /* 890 * Copy the recalled line into the provided line buffer. 891 */ 892 _glh_return_line(glh->list.tail->line, line, dim); 893 /* 894 * Since we have returned to the starting point of the search, cancel it. 895 */ 896 _glh_cancel_search(glh); 897 return line; 898 } 899 900 /*....................................................................... 901 * Query the id of a history line offset by a given number of lines from 902 * the one that is currently being recalled. If a recall session isn't 903 * in progress, or the offset points outside the history list, 0 is 904 * returned. 905 * 906 * Input: 907 * glh GlHistory * The input-line history maintenance object. 908 * offset int The line offset (0 for the current line, < 0 909 * for an older line, > 0 for a newer line. 910 * Output: 911 * return GlhLineID The identifier of the line that is currently 912 * being recalled, or 0 if no recall session is 913 * currently in progress. 914 */ 915 GlhLineID _glh_line_id(GlHistory *glh, int offset) 916 { 917 GlhLineNode *node; /* The line location node being checked */ 918 /* 919 * Is history enabled? 920 */ 921 if(!glh->enable || !glh->buffer || glh->max_lines == 0) 922 return 0; 923 /* 924 * Search forward 'offset' lines to find the required line. 925 */ 926 if(offset >= 0) { 927 for(node=glh->recall; node && offset != 0; node=node->next) { 928 if(node->group == glh->group) 929 offset--; 930 }; 931 } else { 932 for(node=glh->recall; node && offset != 0; node=node->prev) { 933 if(node->group == glh->group) 934 offset++; 935 }; 936 }; 937 return node ? node->id : 0; 938 } 939 940 /*....................................................................... 941 * Recall a line by its history buffer ID. If the line is no longer 942 * in the buffer, or the id is zero, NULL is returned. 943 * 944 * Input: 945 * glh GlHistory * The input-line history maintenance object. 946 * id GlhLineID The ID of the line to be returned. 947 * line char * The input line buffer. On input this should contain 948 * the current input line, and on output, its contents 949 * will have been replaced with the saved line. 950 * dim size_t The allocated dimensions of the line buffer. 951 * Output: 952 * return char * A pointer to line[0], or NULL if not found. 953 */ 954 char *_glh_recall_line(GlHistory *glh, GlhLineID id, char *line, size_t dim) 955 { 956 GlhLineNode *node; /* The line location node being checked */ 957 /* 958 * Is history enabled? 959 */ 960 if(!glh->enable || !glh->buffer || glh->max_lines == 0) 961 return NULL; 962 /* 963 * Preserve the input line if needed. 964 */ 965 if(_glh_prepare_for_recall(glh, line)) 966 return NULL; 967 /* 968 * Search for the specified line. 969 */ 970 node = _glh_find_id(glh, id); 971 /* 972 * Not found? 973 */ 974 if(!node || node->group != glh->group) 975 return NULL; 976 /* 977 * Record the node of the matching line as the starting point 978 * for subsequent searches. 979 */ 980 glh->recall = node; 981 /* 982 * Copy the recalled line into the provided line buffer. 983 */ 984 _glh_return_line(node->line, line, dim); 985 return line; 986 } 987 988 /*....................................................................... 989 * Save the current history in a specified file. 990 * 991 * Input: 992 * glh GlHistory * The input-line history maintenance object. 993 * filename const char * The name of the new file to record the 994 * history in. 995 * comment const char * Extra information such as timestamps will 996 * be recorded on a line started with this 997 * string, the idea being that the file can 998 * double as a command file. Specify "" if 999 * you don't care. 1000 * max_lines int The maximum number of lines to save, or -1 1001 * to save all of the lines in the history 1002 * list. 1003 * Output: 1004 * return int 0 - OK. 1005 * 1 - Error. 1006 */ 1007 int _glh_save_history(GlHistory *glh, const char *filename, const char *comment, 1008 int max_lines) 1009 { 1010 #ifdef WITHOUT_FILE_SYSTEM 1011 _err_record_msg(glh->err, "Can't save history without filesystem access", 1012 END_ERR_MSG); 1013 errno = EINVAL; 1014 return 1; 1015 #else 1016 FILE *fp; /* The output file */ 1017 GlhLineNode *node; /* The line being saved */ 1018 GlhLineNode *head; /* The head of the list of lines to be saved */ 1019 GlhLineSeg *seg; /* One segment of a line being saved */ 1020 /* 1021 * Check the arguments. 1022 */ 1023 if(!glh || !filename || !comment) { 1024 if(glh) 1025 _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG); 1026 errno = EINVAL; 1027 return 1; 1028 }; 1029 /* 1030 * Attempt to open the specified file. 1031 */ 1032 fp = fopen(filename, "w"); 1033 if(!fp) 1034 return _glh_cant_save_history(glh, "Can't open", filename, NULL); 1035 /* 1036 * If a ceiling on the number of lines to save was specified, count 1037 * that number of lines backwards, to find the first line to be saved. 1038 */ 1039 head = NULL; 1040 if(max_lines >= 0) { 1041 for(head=glh->list.tail; head && --max_lines > 0; head=head->prev) 1042 ; 1043 }; 1044 if(!head) 1045 head = glh->list.head; 1046 /* 1047 * Write the contents of the history buffer to the history file, writing 1048 * associated data such as timestamps, to a line starting with the 1049 * specified comment string. 1050 */ 1051 for(node=head; node; node=node->next) { 1052 /* 1053 * Write peripheral information associated with the line, as a comment. 1054 */ 1055 if(fprintf(fp, "%s ", comment) < 0 || 1056 _glh_write_timestamp(fp, node->timestamp) || 1057 fprintf(fp, " %u\n", node->group) < 0) { 1058 return _glh_cant_save_history(glh, "Error writing", filename, fp); 1059 }; 1060 /* 1061 * Write the history line. 1062 */ 1063 for(seg=node->line->head; seg; seg=seg->next) { 1064 size_t slen = seg->next ? GLH_SEG_SIZE : strlen(seg->s); 1065 if(fwrite(seg->s, sizeof(char), slen, fp) != slen) 1066 return _glh_cant_save_history(glh, "Error writing", filename, fp); 1067 }; 1068 fputc('\n', fp); 1069 }; 1070 /* 1071 * Close the history file. 1072 */ 1073 if(fclose(fp) == EOF) 1074 return _glh_cant_save_history(glh, "Error writing", filename, NULL); 1075 return 0; 1076 #endif 1077 } 1078 1079 #ifndef WITHOUT_FILE_SYSTEM 1080 /*....................................................................... 1081 * This is a private error return function of _glh_save_history(). It 1082 * composes an error report in the error buffer, composed using 1083 * sprintf("%s %s (%s)", message, filename, strerror(errno)). It then 1084 * closes fp and returns the error return code of _glh_save_history(). 1085 * 1086 * Input: 1087 * glh GlHistory * The input-line history maintenance object. 1088 * message const char * A message to be followed by the filename. 1089 * filename const char * The name of the offending output file. 1090 * fp FILE * The stream to be closed (send NULL if not 1091 * open). 1092 * Output: 1093 * return int Always 1. 1094 */ 1095 static int _glh_cant_save_history(GlHistory *glh, const char *message, 1096 const char *filename, FILE *fp) 1097 { 1098 _err_record_msg(glh->err, message, filename, " (", 1099 strerror(errno), ")", END_ERR_MSG); 1100 if(fp) 1101 (void) fclose(fp); 1102 return 1; 1103 } 1104 1105 /*....................................................................... 1106 * Write a timestamp to a given stdio stream, in the format 1107 * yyyymmddhhmmss 1108 * 1109 * Input: 1110 * fp FILE * The stream to write to. 1111 * timestamp time_t The timestamp to be written. 1112 * Output: 1113 * return int 0 - OK. 1114 * 1 - Error. 1115 */ 1116 static int _glh_write_timestamp(FILE *fp, time_t timestamp) 1117 { 1118 struct tm *t; /* THe broken-down calendar time */ 1119 /* 1120 * Get the calendar components corresponding to the given timestamp. 1121 */ 1122 if(timestamp < 0 || (t = localtime(×tamp)) == NULL) { 1123 if(fprintf(fp, "?") < 0) 1124 return 1; 1125 return 0; 1126 }; 1127 /* 1128 * Write the calendar time as yyyymmddhhmmss. 1129 */ 1130 if(fprintf(fp, "%04d%02d%02d%02d%02d%02d", t->tm_year + 1900, t->tm_mon + 1, 1131 t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec) < 0) 1132 return 1; 1133 return 0; 1134 } 1135 1136 #endif 1137 1138 /*....................................................................... 1139 * Restore previous history lines from a given file. 1140 * 1141 * Input: 1142 * glh GlHistory * The input-line history maintenance object. 1143 * filename const char * The name of the file to read from. 1144 * comment const char * The same comment string that was passed to 1145 * _glh_save_history() when this file was 1146 * written. 1147 * line char * A buffer into which lines can be read. 1148 * dim size_t The allocated dimension of line[]. 1149 * Output: 1150 * return int 0 - OK. 1151 * 1 - Error. 1152 */ 1153 int _glh_load_history(GlHistory *glh, const char *filename, const char *comment, 1154 char *line, size_t dim) 1155 { 1156 #ifdef WITHOUT_FILE_SYSTEM 1157 _err_record_msg(glh->err, "Can't load history without filesystem access", 1158 END_ERR_MSG); 1159 errno = EINVAL; 1160 return 1; 1161 #else 1162 FILE *fp; /* The output file */ 1163 size_t comment_len; /* The length of the comment string */ 1164 time_t timestamp; /* The timestamp of the history line */ 1165 unsigned group; /* The identifier of the history group to which */ 1166 /* the line belongs. */ 1167 int lineno; /* The line number being read */ 1168 /* 1169 * Check the arguments. 1170 */ 1171 if(!glh || !filename || !comment || !line) { 1172 if(glh) 1173 _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG); 1174 errno = EINVAL; 1175 return 1; 1176 }; 1177 /* 1178 * Measure the length of the comment string. 1179 */ 1180 comment_len = strlen(comment); 1181 /* 1182 * Clear the history list. 1183 */ 1184 _glh_clear_history(glh, 1); 1185 /* 1186 * Attempt to open the specified file. Don't treat it as an error 1187 * if the file doesn't exist. 1188 */ 1189 fp = fopen(filename, "r"); 1190 if(!fp) 1191 return 0; 1192 /* 1193 * Attempt to read each line and preceding peripheral info, and add these 1194 * to the history list. 1195 */ 1196 for(lineno=1; fgets(line, dim, fp) != NULL; lineno++) { 1197 char *lptr; /* A pointer into the input line */ 1198 /* 1199 * Check that the line starts with the comment string. 1200 */ 1201 if(strncmp(line, comment, comment_len) != 0) { 1202 return _glh_cant_load_history(glh, filename, lineno, 1203 "Corrupt history parameter line", fp); 1204 }; 1205 /* 1206 * Skip spaces and tabs after the comment. 1207 */ 1208 for(lptr=line+comment_len; *lptr && (*lptr==' ' || *lptr=='\t'); lptr++) 1209 ; 1210 /* 1211 * The next word must be a timestamp. 1212 */ 1213 if(_glh_decode_timestamp(lptr, &lptr, ×tamp)) { 1214 return _glh_cant_load_history(glh, filename, lineno, 1215 "Corrupt timestamp", fp); 1216 }; 1217 /* 1218 * Skip spaces and tabs. 1219 */ 1220 while(*lptr==' ' || *lptr=='\t') 1221 lptr++; 1222 /* 1223 * The next word must be an unsigned integer group number. 1224 */ 1225 group = (int) strtoul(lptr, &lptr, 10); 1226 if(*lptr != ' ' && *lptr != '\n') { 1227 return _glh_cant_load_history(glh, filename, lineno, 1228 "Corrupt group id", fp); 1229 }; 1230 /* 1231 * Skip spaces and tabs. 1232 */ 1233 while(*lptr==' ' || *lptr=='\t') 1234 lptr++; 1235 /* 1236 * There shouldn't be anything left on the line. 1237 */ 1238 if(*lptr != '\n') { 1239 return _glh_cant_load_history(glh, filename, lineno, 1240 "Corrupt parameter line", fp); 1241 }; 1242 /* 1243 * Now read the history line itself. 1244 */ 1245 lineno++; 1246 if(fgets(line, dim, fp) == NULL) 1247 return _glh_cant_load_history(glh, filename, lineno, "Read error", fp); 1248 /* 1249 * Append the line to the history buffer. 1250 */ 1251 if(_glh_add_history(glh, line, 1)) { 1252 return _glh_cant_load_history(glh, filename, lineno, 1253 "Insufficient memory to record line", fp); 1254 }; 1255 /* 1256 * Record the group and timestamp information along with the line. 1257 */ 1258 if(glh->list.tail) { 1259 glh->list.tail->timestamp = timestamp; 1260 glh->list.tail->group = group; 1261 }; 1262 }; 1263 /* 1264 * Close the file. 1265 */ 1266 (void) fclose(fp); 1267 return 0; 1268 #endif 1269 } 1270 1271 #ifndef WITHOUT_FILE_SYSTEM 1272 /*....................................................................... 1273 * This is a private error return function of _glh_load_history(). 1274 */ 1275 static int _glh_cant_load_history(GlHistory *glh, const char *filename, 1276 int lineno, const char *message, FILE *fp) 1277 { 1278 char lnum[20]; 1279 /* 1280 * Convert the line number to a string. 1281 */ 1282 snprintf(lnum, sizeof(lnum), "%d", lineno); 1283 /* 1284 * Render an error message. 1285 */ 1286 _err_record_msg(glh->err, filename, ":", lnum, ":", message, END_ERR_MSG); 1287 /* 1288 * Close the file. 1289 */ 1290 if(fp) 1291 (void) fclose(fp); 1292 return 1; 1293 } 1294 1295 /*....................................................................... 1296 * Read a timestamp from a string. 1297 * 1298 * Input: 1299 * string char * The string to read from. 1300 * Input/Output: 1301 * endp char ** On output *endp will point to the next unprocessed 1302 * character in string[]. 1303 * timestamp time_t * The timestamp will be assigned to *t. 1304 * Output: 1305 * return int 0 - OK. 1306 * 1 - Error. 1307 */ 1308 static int _glh_decode_timestamp(char *string, char **endp, time_t *timestamp) 1309 { 1310 unsigned year,month,day,hour,min,sec; /* Calendar time components */ 1311 struct tm t; 1312 /* 1313 * There are 14 characters in the date format yyyymmddhhmmss. 1314 */ 1315 enum {TSLEN=14}; 1316 char timestr[TSLEN+1]; /* The timestamp part of the string */ 1317 /* 1318 * If the time wasn't available at the time that the line was recorded 1319 * it will have been written as "?". Check for this before trying 1320 * to read the timestamp. 1321 */ 1322 if(string[0] == '\?') { 1323 *endp = string+1; 1324 *timestamp = -1; 1325 return 0; 1326 }; 1327 /* 1328 * The timestamp is expected to be written in the form yyyymmddhhmmss. 1329 */ 1330 if(strlen(string) < TSLEN) { 1331 *endp = string; 1332 return 1; 1333 }; 1334 /* 1335 * Copy the timestamp out of the string. 1336 */ 1337 strncpy(timestr, string, TSLEN); 1338 timestr[TSLEN] = '\0'; 1339 /* 1340 * Decode the timestamp. 1341 */ 1342 if(sscanf(timestr, "%4u%2u%2u%2u%2u%2u", &year, &month, &day, &hour, &min, 1343 &sec) != 6) { 1344 *endp = string; 1345 return 1; 1346 }; 1347 /* 1348 * Advance the string pointer over the successfully read timestamp. 1349 */ 1350 *endp = string + TSLEN; 1351 /* 1352 * Copy the read values into a struct tm. 1353 */ 1354 t.tm_sec = sec; 1355 t.tm_min = min; 1356 t.tm_hour = hour; 1357 t.tm_mday = day; 1358 t.tm_wday = 0; 1359 t.tm_yday = 0; 1360 t.tm_mon = month - 1; 1361 t.tm_year = year - 1900; 1362 t.tm_isdst = -1; 1363 /* 1364 * Convert the contents of the struct tm to a time_t. 1365 */ 1366 *timestamp = mktime(&t); 1367 return 0; 1368 } 1369 #endif 1370 1371 /*....................................................................... 1372 * Switch history groups. 1373 * 1374 * Input: 1375 * glh GlHistory * The input-line history maintenance object. 1376 * group unsigned The new group identifier. This will be recorded 1377 * with subsequent history lines, and subsequent 1378 * history searches will only return lines with 1379 * this group identifier. This allows multiple 1380 * separate history lists to exist within 1381 * a single GlHistory object. Note that the 1382 * default group identifier is 0. 1383 * Output: 1384 * return int 0 - OK. 1385 * 1 - Error. 1386 */ 1387 int _glh_set_group(GlHistory *glh, unsigned group) 1388 { 1389 /* 1390 * Check the arguments. 1391 */ 1392 if(!glh) { 1393 if(glh) 1394 _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG); 1395 errno = EINVAL; 1396 return 1; 1397 }; 1398 /* 1399 * Is the group being changed? 1400 */ 1401 if(group != glh->group) { 1402 /* 1403 * Cancel any ongoing search. 1404 */ 1405 if(_glh_cancel_search(glh)) 1406 return 1; 1407 /* 1408 * Record the new group. 1409 */ 1410 glh->group = group; 1411 }; 1412 return 0; 1413 } 1414 1415 /*....................................................................... 1416 * Query the current history group. 1417 * 1418 * Input: 1419 * glh GlHistory * The input-line history maintenance object. 1420 * Output: 1421 * return unsigned The group identifier. 1422 */ 1423 int _glh_get_group(GlHistory *glh) 1424 { 1425 return glh ? glh->group : 0; 1426 } 1427 1428 /*....................................................................... 1429 * Display the contents of the history list. 1430 * 1431 * Input: 1432 * glh GlHistory * The input-line history maintenance object. 1433 * write_fn GlWriteFn * The function to call to write the line, or 1434 * 0 to discard the output. 1435 * data void * Anonymous data to pass to write_fn(). 1436 * fmt const char * A format string. This can contain arbitrary 1437 * characters, which are written verbatim, plus 1438 * any of the following format directives: 1439 * %D - The date, like 2001-11-20 1440 * %T - The time of day, like 23:59:59 1441 * %N - The sequential entry number of the 1442 * line in the history buffer. 1443 * %G - The history group number of the line. 1444 * %% - A literal % character. 1445 * %H - The history line. 1446 * all_groups int If true, display history lines from all 1447 * history groups. Otherwise only display 1448 * those of the current history group. 1449 * max_lines int If max_lines is < 0, all available lines 1450 * are displayed. Otherwise only the most 1451 * recent max_lines lines will be displayed. 1452 * Output: 1453 * return int 0 - OK. 1454 * 1 - Error. 1455 */ 1456 int _glh_show_history(GlHistory *glh, GlWriteFn *write_fn, void *data, 1457 const char *fmt, int all_groups, int max_lines) 1458 { 1459 GlhLineNode *node; /* The line being displayed */ 1460 GlhLineNode *oldest; /* The oldest line to display */ 1461 GlhLineSeg *seg; /* One segment of a line being displayed */ 1462 enum {TSMAX=32}; /* The maximum length of the date and time string */ 1463 char buffer[TSMAX+1]; /* The buffer in which to write the date and time */ 1464 int idlen; /* The length of displayed ID strings */ 1465 unsigned grpmax; /* The maximum group number in the buffer */ 1466 int grplen; /* The number of characters needed to print grpmax */ 1467 int len; /* The length of a string to be written */ 1468 /* 1469 * Check the arguments. 1470 */ 1471 if(!glh || !write_fn || !fmt) { 1472 if(glh) 1473 _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG); 1474 errno = EINVAL; 1475 return 1; 1476 }; 1477 /* 1478 * Is history enabled? 1479 */ 1480 if(!glh->enable || !glh->list.head) 1481 return 0; 1482 /* 1483 * Work out the length to display ID numbers, choosing the length of 1484 * the biggest number in the buffer. Smaller numbers will be padded 1485 * with leading zeroes if needed. 1486 */ 1487 snprintf(buffer, sizeof(buffer), "%lu", (unsigned long) glh->list.tail->id); 1488 idlen = strlen(buffer); 1489 /* 1490 * Find the largest group number. 1491 */ 1492 grpmax = 0; 1493 for(node=glh->list.head; node; node=node->next) { 1494 if(node->group > grpmax) 1495 grpmax = node->group; 1496 }; 1497 /* 1498 * Find out how many characters are needed to display the group number. 1499 */ 1500 snprintf(buffer, sizeof(buffer), "%u", (unsigned) grpmax); 1501 grplen = strlen(buffer); 1502 /* 1503 * Find the node that follows the oldest line to be displayed. 1504 */ 1505 if(max_lines < 0) { 1506 oldest = glh->list.head; 1507 } else if(max_lines==0) { 1508 return 0; 1509 } else { 1510 for(oldest=glh->list.tail; oldest; oldest=oldest->prev) { 1511 if((all_groups || oldest->group == glh->group) && --max_lines <= 0) 1512 break; 1513 }; 1514 /* 1515 * If the number of lines in the buffer doesn't exceed the specified 1516 * maximum, start from the oldest line in the buffer. 1517 */ 1518 if(!oldest) 1519 oldest = glh->list.head; 1520 }; 1521 /* 1522 * List the history lines in increasing time order. 1523 */ 1524 for(node=oldest; node; node=node->next) { 1525 /* 1526 * Only display lines from the current history group, unless 1527 * told otherwise. 1528 */ 1529 if(all_groups || node->group == glh->group) { 1530 const char *fptr; /* A pointer into the format string */ 1531 struct tm *t = NULL; /* The broken time version of the timestamp */ 1532 /* 1533 * Work out the calendar representation of the node timestamp. 1534 */ 1535 if(node->timestamp != (time_t) -1) 1536 t = localtime(&node->timestamp); 1537 /* 1538 * Parse the format string. 1539 */ 1540 fptr = fmt; 1541 while(*fptr) { 1542 /* 1543 * Search for the start of the next format directive or the end of the string. 1544 */ 1545 const char *start = fptr; 1546 while(*fptr && *fptr != '%') 1547 fptr++; 1548 /* 1549 * Display any literal characters that precede the located directive. 1550 */ 1551 if(fptr > start) { 1552 len = (int) (fptr - start); 1553 if(write_fn(data, start, len) != len) 1554 return 1; 1555 }; 1556 /* 1557 * Did we hit a new directive before the end of the line? 1558 */ 1559 if(*fptr) { 1560 /* 1561 * Obey the directive. Ignore unknown directives. 1562 */ 1563 switch(*++fptr) { 1564 case 'D': /* Display the date */ 1565 if(t && strftime(buffer, TSMAX, "%Y-%m-%d", t) != 0) { 1566 len = strlen(buffer); 1567 if(write_fn(data, buffer, len) != len) 1568 return 1; 1569 }; 1570 break; 1571 case 'T': /* Display the time of day */ 1572 if(t && strftime(buffer, TSMAX, "%H:%M:%S", t) != 0) { 1573 len = strlen(buffer); 1574 if(write_fn(data, buffer, len) != len) 1575 return 1; 1576 }; 1577 break; 1578 case 'N': /* Display the sequential entry number */ 1579 snprintf(buffer, sizeof(buffer), "%*lu", idlen, (unsigned long) node->id); 1580 len = strlen(buffer); 1581 if(write_fn(data, buffer, len) != len) 1582 return 1; 1583 break; 1584 case 'G': 1585 snprintf(buffer, sizeof(buffer), "%*u", grplen, (unsigned) node->group); 1586 len = strlen(buffer); 1587 if(write_fn(data, buffer, len) != len) 1588 return 1; 1589 break; 1590 case 'H': /* Display the history line */ 1591 for(seg=node->line->head; seg; seg=seg->next) { 1592 len = seg->next ? GLH_SEG_SIZE : strlen(seg->s); 1593 if(write_fn(data, seg->s, len) != len) 1594 return 1; 1595 }; 1596 break; 1597 case '%': /* A literal % symbol */ 1598 if(write_fn(data, "%", 1) != 1) 1599 return 1; 1600 break; 1601 }; 1602 /* 1603 * Skip the directive. 1604 */ 1605 if(*fptr) 1606 fptr++; 1607 }; 1608 }; 1609 }; 1610 }; 1611 return 0; 1612 } 1613 1614 /*....................................................................... 1615 * Change the size of the history buffer. 1616 * 1617 * Input: 1618 * glh GlHistory * The input-line history maintenance object. 1619 * bufsize size_t The number of bytes in the history buffer, or 0 1620 * to delete the buffer completely. 1621 * Output: 1622 * return int 0 - OK. 1623 * 1 - Insufficient memory (the previous buffer 1624 * will have been retained). No error message 1625 * will be displayed. 1626 */ 1627 int _glh_resize_history(GlHistory *glh, size_t bufsize) 1628 { 1629 int nbuff; /* The number of segments in the new buffer */ 1630 int i; 1631 /* 1632 * Check the arguments. 1633 */ 1634 if(!glh) { 1635 errno = EINVAL; 1636 return 1; 1637 }; 1638 /* 1639 * How many buffer segments does the requested buffer size correspond 1640 * to? 1641 */ 1642 nbuff = (bufsize+GLH_SEG_SIZE-1) / GLH_SEG_SIZE; 1643 /* 1644 * Has a different size than the current size been requested? 1645 */ 1646 if(glh->nbuff != nbuff) { 1647 /* 1648 * Cancel any ongoing search. 1649 */ 1650 (void) _glh_cancel_search(glh); 1651 /* 1652 * Create a wholly new buffer? 1653 */ 1654 if(glh->nbuff == 0 && nbuff>0) { 1655 glh->buffer = (GlhLineSeg *) malloc(sizeof(GlhLineSeg) * nbuff); 1656 if(!glh->buffer) 1657 return 1; 1658 glh->nbuff = nbuff; 1659 glh->nfree = glh->nbuff; 1660 glh->nbusy = 0; 1661 glh->nline = 0; 1662 /* 1663 * Link the currently unused nodes of the buffer into a list. 1664 */ 1665 glh->unused = glh->buffer; 1666 for(i=0; i<glh->nbuff-1; i++) { 1667 GlhLineSeg *seg = glh->unused + i; 1668 seg->next = seg + 1; 1669 }; 1670 glh->unused[i].next = NULL; 1671 /* 1672 * Delete an existing buffer? 1673 */ 1674 } else if(nbuff == 0) { 1675 _glh_clear_history(glh, 1); 1676 free(glh->buffer); 1677 glh->buffer = NULL; 1678 glh->unused = NULL; 1679 glh->nbuff = 0; 1680 glh->nfree = 0; 1681 glh->nbusy = 0; 1682 glh->nline = 0; 1683 /* 1684 * Change from one finite buffer size to another? 1685 */ 1686 } else { 1687 GlhLineSeg *buffer; /* The resized buffer */ 1688 int nbusy; /* The number of used line segments in the new buffer */ 1689 /* 1690 * Starting from the oldest line in the buffer, discard lines until 1691 * the buffer contains at most 'nbuff' used line segments. 1692 */ 1693 while(glh->list.head && glh->nbusy > nbuff) 1694 _glh_discard_line(glh, glh->list.head); 1695 /* 1696 * Attempt to allocate a new buffer. 1697 */ 1698 buffer = (GlhLineSeg *) malloc(nbuff * sizeof(GlhLineSeg)); 1699 if(!buffer) { 1700 errno = ENOMEM; 1701 return 1; 1702 }; 1703 /* 1704 * Copy the used segments of the old buffer to the start of the new buffer. 1705 */ 1706 nbusy = 0; 1707 for(i=0; i<GLH_HASH_SIZE; i++) { 1708 GlhHashBucket *b = glh->hash.bucket + i; 1709 GlhHashNode *hnode; 1710 for(hnode=b->lines; hnode; hnode=hnode->next) { 1711 GlhLineSeg *seg = hnode->head; 1712 hnode->head = buffer + nbusy; 1713 for( ; seg; seg=seg->next) { 1714 buffer[nbusy] = *seg; 1715 buffer[nbusy].next = seg->next ? &buffer[nbusy+1] : NULL; 1716 nbusy++; 1717 }; 1718 }; 1719 }; 1720 /* 1721 * Make a list of the new buffer's unused segments. 1722 */ 1723 for(i=nbusy; i<nbuff-1; i++) 1724 buffer[i].next = &buffer[i+1]; 1725 if(i < nbuff) 1726 buffer[i].next = NULL; 1727 /* 1728 * Discard the old buffer. 1729 */ 1730 free(glh->buffer); 1731 /* 1732 * Install the new buffer. 1733 */ 1734 glh->buffer = buffer; 1735 glh->nbuff = nbuff; 1736 glh->nbusy = nbusy; 1737 glh->nfree = nbuff - nbusy; 1738 glh->unused = glh->nfree > 0 ? (buffer + nbusy) : NULL; 1739 }; 1740 }; 1741 return 0; 1742 } 1743 1744 /*....................................................................... 1745 * Set an upper limit to the number of lines that can be recorded in the 1746 * history list, or remove a previously specified limit. 1747 * 1748 * Input: 1749 * glh GlHistory * The input-line history maintenance object. 1750 * max_lines int The maximum number of lines to allow, or -1 to 1751 * cancel a previous limit and allow as many lines 1752 * as will fit in the current history buffer size. 1753 */ 1754 void _glh_limit_history(GlHistory *glh, int max_lines) 1755 { 1756 if(!glh) 1757 return; 1758 /* 1759 * Apply a new limit? 1760 */ 1761 if(max_lines >= 0 && max_lines != glh->max_lines) { 1762 /* 1763 * Count successively older lines until we reach the start of the 1764 * list, or until we have seen max_lines lines (at which point 'node' 1765 * will be line number max_lines+1). 1766 */ 1767 int nline = 0; 1768 GlhLineNode *node; 1769 for(node=glh->list.tail; node && ++nline <= max_lines; node=node->prev) 1770 ; 1771 /* 1772 * Discard any lines that exceed the limit. 1773 */ 1774 if(node) { 1775 GlhLineNode *oldest = node->next; /* The oldest line to be kept */ 1776 /* 1777 * Delete nodes from the head of the list until we reach the node that 1778 * is to be kept. 1779 */ 1780 while(glh->list.head && glh->list.head != oldest) 1781 _glh_discard_line(glh, glh->list.head); 1782 }; 1783 }; 1784 /* 1785 * Record the new limit. 1786 */ 1787 glh->max_lines = max_lines; 1788 return; 1789 } 1790 1791 /*....................................................................... 1792 * Discard either all history, or the history associated with the current 1793 * history group. 1794 * 1795 * Input: 1796 * glh GlHistory * The input-line history maintenance object. 1797 * all_groups int If true, clear all of the history. If false, 1798 * clear only the stored lines associated with the 1799 * currently selected history group. 1800 */ 1801 void _glh_clear_history(GlHistory *glh, int all_groups) 1802 { 1803 int i; 1804 /* 1805 * Check the arguments. 1806 */ 1807 if(!glh) 1808 return; 1809 /* 1810 * Cancel any ongoing search. 1811 */ 1812 (void) _glh_cancel_search(glh); 1813 /* 1814 * Delete all history lines regardless of group? 1815 */ 1816 if(all_groups) { 1817 /* 1818 * Claer the time-ordered list of lines. 1819 */ 1820 _rst_FreeList(glh->list.node_mem); 1821 glh->list.head = glh->list.tail = NULL; 1822 glh->nline = 0; 1823 glh->id_node = NULL; 1824 /* 1825 * Clear the hash table. 1826 */ 1827 for(i=0; i<GLH_HASH_SIZE; i++) 1828 glh->hash.bucket[i].lines = NULL; 1829 _rst_FreeList(glh->hash.node_mem); 1830 /* 1831 * Move all line segment nodes back onto the list of unused segments. 1832 */ 1833 if(glh->buffer) { 1834 glh->unused = glh->buffer; 1835 for(i=0; i<glh->nbuff-1; i++) { 1836 GlhLineSeg *seg = glh->unused + i; 1837 seg->next = seg + 1; 1838 }; 1839 glh->unused[i].next = NULL; 1840 glh->nfree = glh->nbuff; 1841 glh->nbusy = 0; 1842 } else { 1843 glh->unused = NULL; 1844 glh->nbusy = glh->nfree = 0; 1845 }; 1846 /* 1847 * Just delete lines of the current group? 1848 */ 1849 } else { 1850 GlhLineNode *node; /* The line node being checked */ 1851 GlhLineNode *next; /* The line node that follows 'node' */ 1852 /* 1853 * Search out and delete the line nodes of the current group. 1854 */ 1855 for(node=glh->list.head; node; node=next) { 1856 /* 1857 * Keep a record of the following node before we delete the current 1858 * node. 1859 */ 1860 next = node->next; 1861 /* 1862 * Discard this node? 1863 */ 1864 if(node->group == glh->group) 1865 _glh_discard_line(glh, node); 1866 }; 1867 }; 1868 return; 1869 } 1870 1871 /*....................................................................... 1872 * Temporarily enable or disable the history list. 1873 * 1874 * Input: 1875 * glh GlHistory * The input-line history maintenance object. 1876 * enable int If true, turn on the history mechanism. If 1877 * false, disable it. 1878 */ 1879 void _glh_toggle_history(GlHistory *glh, int enable) 1880 { 1881 if(glh) 1882 glh->enable = enable; 1883 } 1884 1885 /*....................................................................... 1886 * Discard a given archived input line. 1887 * 1888 * Input: 1889 * glh GlHistory * The history container object. 1890 * node GlhLineNode * The line to be discarded, specified via its 1891 * entry in the time-ordered list of historical 1892 * input lines. 1893 */ 1894 static void _glh_discard_line(GlHistory *glh, GlhLineNode *node) 1895 { 1896 /* 1897 * Remove the node from the linked list. 1898 */ 1899 if(node->prev) 1900 node->prev->next = node->next; 1901 else 1902 glh->list.head = node->next; 1903 if(node->next) 1904 node->next->prev = node->prev; 1905 else 1906 glh->list.tail = node->prev; 1907 /* 1908 * If we are deleting the node that is marked as the start point of the 1909 * last ID search, remove the cached starting point. 1910 */ 1911 if(node == glh->id_node) 1912 glh->id_node = NULL; 1913 /* 1914 * If we are deleting the node that is marked as the start point of the 1915 * next prefix search, cancel the search. 1916 */ 1917 if(node == glh->recall) 1918 _glh_cancel_search(glh); 1919 /* 1920 * Delete our copy of the line. 1921 */ 1922 node->line = _glh_discard_copy(glh, node->line); 1923 /* 1924 * Return the node to the freelist. 1925 */ 1926 (void) _del_FreeListNode(glh->list.node_mem, node); 1927 /* 1928 * Record the removal of a line from the list. 1929 */ 1930 glh->nline--; 1931 return; 1932 } 1933 1934 /*....................................................................... 1935 * Lookup the details of a given history line, given its id. 1936 * 1937 * Input: 1938 * glh GlHistory * The input-line history maintenance object. 1939 * id GlLineID The sequential number of the line. 1940 * Input/Output: 1941 * line const char ** A pointer to a copy of the history line will be 1942 * assigned to *line. Beware that this pointer may 1943 * be invalidated by the next call to any public 1944 * history function. 1945 * group unsigned * The group membership of the line will be assigned 1946 * to *group. 1947 * timestamp time_t * The timestamp of the line will be assigned to 1948 * *timestamp. 1949 * Output: 1950 * return int 0 - The requested line wasn't found. 1951 * 1 - The line was found. 1952 */ 1953 int _glh_lookup_history(GlHistory *glh, GlhLineID id, const char **line, 1954 unsigned *group, time_t *timestamp) 1955 { 1956 GlhLineNode *node; /* The located line location node */ 1957 /* 1958 * Check the arguments. 1959 */ 1960 if(!glh) 1961 return 0; 1962 /* 1963 * Search for the line that has the specified ID. 1964 */ 1965 node = _glh_find_id(glh, id); 1966 /* 1967 * Not found? 1968 */ 1969 if(!node) 1970 return 0; 1971 /* 1972 * Has the history line been requested? 1973 */ 1974 if(line) { 1975 /* 1976 * If necessary, reallocate the lookup buffer to accomodate the size of 1977 * a copy of the located line. 1978 */ 1979 if(node->line->len + 1 > glh->lbuf_dim) { 1980 int lbuf_dim = node->line->len + 1; 1981 char *lbuf = realloc(glh->lbuf, lbuf_dim); 1982 if(!lbuf) { 1983 errno = ENOMEM; 1984 return 0; 1985 }; 1986 glh->lbuf_dim = lbuf_dim; 1987 glh->lbuf = lbuf; 1988 }; 1989 /* 1990 * Copy the history line into the lookup buffer. 1991 */ 1992 _glh_return_line(node->line, glh->lbuf, glh->lbuf_dim); 1993 /* 1994 * Assign the lookup buffer as the returned line pointer. 1995 */ 1996 *line = glh->lbuf; 1997 }; 1998 /* 1999 * Does the caller want to know the group of the line? 2000 */ 2001 if(group) 2002 *group = node->group; 2003 /* 2004 * Does the caller want to know the timestamp of the line? 2005 */ 2006 if(timestamp) 2007 *timestamp = node->timestamp; 2008 return 1; 2009 } 2010 2011 /*....................................................................... 2012 * Lookup a node in the history list by its ID. 2013 * 2014 * Input: 2015 * glh GlHistory * The input-line history maintenance object. 2016 * id GlhLineID The ID of the line to be returned. 2017 * Output: 2018 * return GlhLIneNode * The located node, or NULL if not found. 2019 */ 2020 static GlhLineNode *_glh_find_id(GlHistory *glh, GlhLineID id) 2021 { 2022 GlhLineNode *node; /* The node being checked */ 2023 /* 2024 * Is history enabled? 2025 */ 2026 if(!glh->enable || !glh->list.head) 2027 return NULL; 2028 /* 2029 * If possible, start at the end point of the last ID search. 2030 * Otherwise start from the head of the list. 2031 */ 2032 node = glh->id_node; 2033 if(!node) 2034 node = glh->list.head; 2035 /* 2036 * Search forwards from 'node'? 2037 */ 2038 if(node->id < id) { 2039 while(node && node->id != id) 2040 node = node->next; 2041 glh->id_node = node ? node : glh->list.tail; 2042 /* 2043 * Search backwards from 'node'? 2044 */ 2045 } else { 2046 while(node && node->id != id) 2047 node = node->prev; 2048 glh->id_node = node ? node : glh->list.head; 2049 }; 2050 /* 2051 * Return the located node (this will be NULL if the ID wasn't found). 2052 */ 2053 return node; 2054 } 2055 2056 /*....................................................................... 2057 * Query the state of the history list. Note that any of the input/output 2058 * pointers can be specified as NULL. 2059 * 2060 * Input: 2061 * glh GlHistory * The input-line history maintenance object. 2062 * Input/Output: 2063 * enabled int * If history is enabled, *enabled will be 2064 * set to 1. Otherwise it will be assigned 0. 2065 * group unsigned * The current history group ID will be assigned 2066 * to *group. 2067 * max_lines int * The currently requested limit on the number 2068 * of history lines in the list, or -1 if 2069 * unlimited. 2070 */ 2071 void _glh_state_of_history(GlHistory *glh, int *enabled, unsigned *group, 2072 int *max_lines) 2073 { 2074 if(glh) { 2075 if(enabled) 2076 *enabled = glh->enable; 2077 if(group) 2078 *group = glh->group; 2079 if(max_lines) 2080 *max_lines = glh->max_lines; 2081 }; 2082 } 2083 2084 /*....................................................................... 2085 * Get the range of lines in the history buffer. 2086 * 2087 * Input: 2088 * glh GlHistory * The input-line history maintenance object. 2089 * Input/Output: 2090 * oldest unsigned long * The sequential entry number of the oldest 2091 * line in the history list will be assigned 2092 * to *oldest, unless there are no lines, in 2093 * which case 0 will be assigned. 2094 * newest unsigned long * The sequential entry number of the newest 2095 * line in the history list will be assigned 2096 * to *newest, unless there are no lines, in 2097 * which case 0 will be assigned. 2098 * nlines int * The number of lines currently in the history 2099 * list. 2100 */ 2101 void _glh_range_of_history(GlHistory *glh, unsigned long *oldest, 2102 unsigned long *newest, int *nlines) 2103 { 2104 if(glh) { 2105 if(oldest) 2106 *oldest = glh->list.head ? glh->list.head->id : 0; 2107 if(newest) 2108 *newest = glh->list.tail ? glh->list.tail->id : 0; 2109 if(nlines) 2110 *nlines = glh->nline; 2111 }; 2112 } 2113 2114 /*....................................................................... 2115 * Return the size of the history buffer and the amount of the 2116 * buffer that is currently in use. 2117 * 2118 * Input: 2119 * glh GlHistory * The input-line history maintenance object. 2120 * Input/Output: 2121 * buff_size size_t * The size of the history buffer (bytes). 2122 * buff_used size_t * The amount of the history buffer that 2123 * is currently occupied (bytes). 2124 */ 2125 void _glh_size_of_history(GlHistory *glh, size_t *buff_size, size_t *buff_used) 2126 { 2127 if(glh) { 2128 if(buff_size) 2129 *buff_size = (glh->nbusy + glh->nfree) * GLH_SEG_SIZE; 2130 /* 2131 * Determine the amount of buffer space that is currently occupied. 2132 */ 2133 if(buff_used) 2134 *buff_used = glh->nbusy * GLH_SEG_SIZE; 2135 }; 2136 } 2137 2138 /*....................................................................... 2139 * Return extra information (ie. in addition to that provided by errno) 2140 * about the last error to occur in any of the public functions of this 2141 * module. 2142 * 2143 * Input: 2144 * glh GlHistory * The container of the history list. 2145 * Output: 2146 * return const char * A pointer to the internal buffer in which 2147 * the error message is temporarily stored. 2148 */ 2149 const char *_glh_last_error(GlHistory *glh) 2150 { 2151 return glh ? _err_get_msg(glh->err) : "NULL GlHistory argument"; 2152 } 2153 2154 /*....................................................................... 2155 * Unless already stored, store a copy of the line in the history buffer, 2156 * then return a reference-counted hash-node pointer to this copy. 2157 * 2158 * Input: 2159 * glh GlHistory * The history maintenance buffer. 2160 * line const char * The history line to be recorded. 2161 * n size_t The length of the string, excluding any '\0' 2162 * terminator. 2163 * Output: 2164 * return GlhHashNode * The hash-node containing the stored line, or 2165 * NULL on error. 2166 */ 2167 static GlhHashNode *_glh_acquire_copy(GlHistory *glh, const char *line, 2168 size_t n) 2169 { 2170 GlhHashBucket *bucket; /* The hash-table bucket of the line */ 2171 GlhHashNode *hnode; /* The hash-table node of the line */ 2172 int i; 2173 /* 2174 * In which bucket should the line be recorded? 2175 */ 2176 bucket = glh_find_bucket(glh, line, n); 2177 /* 2178 * Is the line already recorded there? 2179 */ 2180 hnode = glh_find_hash_node(bucket, line, n); 2181 /* 2182 * If the line isn't recorded in the buffer yet, make room for it. 2183 */ 2184 if(!hnode) { 2185 GlhLineSeg *seg; /* A line segment */ 2186 int offset; /* An offset into line[] */ 2187 /* 2188 * How many string segments will be needed to record the new line, 2189 * including space for a '\0' terminator? 2190 */ 2191 int nseg = ((n+1) + GLH_SEG_SIZE-1) / GLH_SEG_SIZE; 2192 /* 2193 * Discard the oldest history lines in the buffer until at least 2194 * 'nseg' segments have been freed up, or until we run out of buffer 2195 * space. 2196 */ 2197 while(glh->nfree < nseg && glh->nbusy > 0) 2198 _glh_discard_line(glh, glh->list.head); 2199 /* 2200 * If the buffer is smaller than the new line, don't attempt to truncate 2201 * it to fit. Simply don't archive it. 2202 */ 2203 if(glh->nfree < nseg) 2204 return NULL; 2205 /* 2206 * Record the line in the first 'nseg' segments of the list of unused segments. 2207 */ 2208 offset = 0; 2209 for(i=0,seg=glh->unused; i<nseg-1; i++,seg=seg->next, offset+=GLH_SEG_SIZE) 2210 memcpy(seg->s, line + offset, GLH_SEG_SIZE); 2211 memcpy(seg->s, line + offset, n-offset); 2212 seg->s[n-offset] = '\0'; 2213 /* 2214 * Create a new hash-node for the line. 2215 */ 2216 hnode = (GlhHashNode *) _new_FreeListNode(glh->hash.node_mem); 2217 if(!hnode) 2218 return NULL; 2219 /* 2220 * Move the copy of the line from the list of unused segments to 2221 * the hash node. 2222 */ 2223 hnode->head = glh->unused; 2224 glh->unused = seg->next; 2225 seg->next = NULL; 2226 glh->nbusy += nseg; 2227 glh->nfree -= nseg; 2228 /* 2229 * Prepend the new hash node to the list within the associated bucket. 2230 */ 2231 hnode->next = bucket->lines; 2232 bucket->lines = hnode; 2233 /* 2234 * Initialize the rest of the members of the hash node. 2235 */ 2236 hnode->len = n; 2237 hnode->reported = 0; 2238 hnode->used = 0; 2239 hnode->bucket = bucket; 2240 }; 2241 /* 2242 * Increment the reference count of the line. 2243 */ 2244 hnode->used++; 2245 return hnode; 2246 } 2247 2248 /*....................................................................... 2249 * Decrement the reference count of the history line of a given hash-node, 2250 * and if the count reaches zero, delete both the hash-node and the 2251 * buffered copy of the line. 2252 * 2253 * Input: 2254 * glh GlHistory * The history container object. 2255 * hnode GlhHashNode * The node to be removed. 2256 * Output: 2257 * return GlhHashNode * The deleted hash-node (ie. NULL). 2258 */ 2259 static GlhHashNode *_glh_discard_copy(GlHistory *glh, GlhHashNode *hnode) 2260 { 2261 if(hnode) { 2262 GlhHashBucket *bucket = hnode->bucket; 2263 /* 2264 * If decrementing the reference count of the hash-node doesn't reduce 2265 * the reference count to zero, then the line is still in use in another 2266 * object, so don't delete it yet. Return NULL to indicate that the caller's 2267 * access to the hash-node copy has been deleted. 2268 */ 2269 if(--hnode->used >= 1) 2270 return NULL; 2271 /* 2272 * Remove the hash-node from the list in its parent bucket. 2273 */ 2274 if(bucket->lines == hnode) { 2275 bucket->lines = hnode->next; 2276 } else { 2277 GlhHashNode *prev; /* The node which precedes hnode in the bucket */ 2278 for(prev=bucket->lines; prev && prev->next != hnode; prev=prev->next) 2279 ; 2280 if(prev) 2281 prev->next = hnode->next; 2282 }; 2283 hnode->next = NULL; 2284 /* 2285 * Return the line segments of the hash-node to the list of unused segments. 2286 */ 2287 if(hnode->head) { 2288 GlhLineSeg *tail; /* The last node in the list of line segments */ 2289 int nseg; /* The number of segments being discarded */ 2290 /* 2291 * Get the last node of the list of line segments referenced in the hash-node, 2292 * while counting the number of line segments used. 2293 */ 2294 for(nseg=1,tail=hnode->head; tail->next; nseg++,tail=tail->next) 2295 ; 2296 /* 2297 * Prepend the list of line segments used by the hash node to the 2298 * list of unused line segments. 2299 */ 2300 tail->next = glh->unused; 2301 glh->unused = hnode->head; 2302 glh->nbusy -= nseg; 2303 glh->nfree += nseg; 2304 }; 2305 /* 2306 * Return the container of the hash-node to the freelist. 2307 */ 2308 hnode = (GlhHashNode *) _del_FreeListNode(glh->hash.node_mem, hnode); 2309 }; 2310 return NULL; 2311 } 2312 2313 /*....................................................................... 2314 * Private function to locate the hash bucket associated with a given 2315 * history line. 2316 * 2317 * This uses a hash-function described in the dragon-book 2318 * ("Compilers - Principles, Techniques and Tools", by Aho, Sethi and 2319 * Ullman; pub. Adison Wesley) page 435. 2320 * 2321 * Input: 2322 * glh GlHistory * The history container object. 2323 * line const char * The historical line to look up. 2324 * n size_t The length of the line in line[], excluding 2325 * any '\0' terminator. 2326 * Output: 2327 * return GlhHashBucket * The located hash-bucket. 2328 */ 2329 static GlhHashBucket *glh_find_bucket(GlHistory *glh, const char *line, 2330 size_t n) 2331 { 2332 unsigned long h = 0L; 2333 int i; 2334 for(i=0; i<n; i++) { 2335 unsigned char c = line[i]; 2336 h = 65599UL * h + c; /* 65599 is a prime close to 2^16 */ 2337 }; 2338 return glh->hash.bucket + (h % GLH_HASH_SIZE); 2339 } 2340 2341 /*....................................................................... 2342 * Find a given history line within a given hash-table bucket. 2343 * 2344 * Input: 2345 * bucket GlhHashBucket * The hash-table bucket in which to search. 2346 * line const char * The historical line to lookup. 2347 * n size_t The length of the line in line[], excluding 2348 * any '\0' terminator. 2349 * Output: 2350 * return GlhHashNode * The hash-table entry of the line, or NULL 2351 * if not found. 2352 */ 2353 static GlhHashNode *glh_find_hash_node(GlhHashBucket *bucket, const char *line, 2354 size_t n) 2355 { 2356 GlhHashNode *node; /* A node in the list of lines in the bucket */ 2357 /* 2358 * Compare each of the lines in the list of lines, against 'line'. 2359 */ 2360 for(node=bucket->lines; node; node=node->next) { 2361 if(_glh_is_line(node, line, n)) 2362 return node; 2363 }; 2364 return NULL; 2365 } 2366 2367 /*....................................................................... 2368 * Return non-zero if a given string is equal to a given segmented line 2369 * node. 2370 * 2371 * Input: 2372 * hash GlhHashNode * The hash-table entry of the line. 2373 * line const char * The string to be compared to the segmented 2374 * line. 2375 * n size_t The length of the line in line[], excluding 2376 * any '\0' terminator. 2377 * Output: 2378 * return int 0 - The lines differ. 2379 * 1 - The lines are the same. 2380 */ 2381 static int _glh_is_line(GlhHashNode *hash, const char *line, size_t n) 2382 { 2383 GlhLineSeg *seg; /* A node in the list of line segments */ 2384 int i; 2385 /* 2386 * Do the two lines have the same length? 2387 */ 2388 if(n != hash->len) 2389 return 0; 2390 /* 2391 * Compare the characters of the segmented and unsegmented versions 2392 * of the line. 2393 */ 2394 for(seg=hash->head; n>0 && seg; seg=seg->next) { 2395 const char *s = seg->s; 2396 for(i=0; n>0 && i<GLH_SEG_SIZE; i++,n--) { 2397 if(*line++ != *s++) 2398 return 0; 2399 }; 2400 }; 2401 return 1; 2402 } 2403 2404 /*....................................................................... 2405 * Return non-zero if a given line has the specified segmented search 2406 * prefix. 2407 * 2408 * Input: 2409 * line GlhHashNode * The line to be compared against the prefix. 2410 * prefix GlhHashNode * The search prefix, or NULL to match any string. 2411 * Output: 2412 * return int 0 - The line doesn't have the specified prefix. 2413 * 1 - The line has the specified prefix. 2414 */ 2415 static int _glh_line_matches_prefix(GlhHashNode *line, GlhHashNode *prefix) 2416 { 2417 GlhLineStream lstr; /* The stream that is used to traverse 'line' */ 2418 GlhLineStream pstr; /* The stream that is used to traverse 'prefix' */ 2419 /* 2420 * When prefix==NULL, this means that the nul string 2421 * is to be matched, and this matches all lines. 2422 */ 2423 if(!prefix) 2424 return 1; 2425 /* 2426 * Wrap the two history lines that are to be compared in iterator 2427 * stream objects. 2428 */ 2429 glh_init_stream(&lstr, line); 2430 glh_init_stream(&pstr, prefix); 2431 /* 2432 * If the prefix contains a glob pattern, match the prefix as a glob 2433 * pattern. 2434 */ 2435 if(glh_contains_glob(prefix)) 2436 return glh_line_matches_glob(&lstr, &pstr); 2437 /* 2438 * Is the prefix longer than the line being compared against it? 2439 */ 2440 if(prefix->len > line->len) 2441 return 0; 2442 /* 2443 * Compare the line to the prefix. 2444 */ 2445 while(pstr.c != '\0' && pstr.c == lstr.c) { 2446 glh_step_stream(&lstr); 2447 glh_step_stream(&pstr); 2448 }; 2449 /* 2450 * Did we reach the end of the prefix string before finding 2451 * any differences? 2452 */ 2453 return pstr.c == '\0'; 2454 } 2455 2456 /*....................................................................... 2457 * Copy a given history line into a specified output string. 2458 * 2459 * Input: 2460 * hash GlhHashNode The hash-table entry of the history line to 2461 * be copied. 2462 * line char * A copy of the history line. 2463 * dim size_t The allocated dimension of the line buffer. 2464 */ 2465 static void _glh_return_line(GlhHashNode *hash, char *line, size_t dim) 2466 { 2467 GlhLineSeg *seg; /* A node in the list of line segments */ 2468 int i; 2469 for(seg=hash->head; dim>0 && seg; seg=seg->next) { 2470 const char *s = seg->s; 2471 for(i=0; dim>0 && i<GLH_SEG_SIZE; i++,dim--) 2472 *line++ = *s++; 2473 }; 2474 /* 2475 * If the line wouldn't fit in the output buffer, replace the last character 2476 * with a '\0' terminator. 2477 */ 2478 if(dim==0) 2479 line[-1] = '\0'; 2480 } 2481 2482 /*....................................................................... 2483 * This function should be called whenever a new line recall is 2484 * attempted. It preserves a copy of the current input line in the 2485 * history list while other lines in the history list are being 2486 * returned. 2487 * 2488 * Input: 2489 * glh GlHistory * The input-line history maintenance object. 2490 * line char * The current contents of the input line buffer. 2491 * Output: 2492 * return int 0 - OK. 2493 * 1 - Error. 2494 */ 2495 static int _glh_prepare_for_recall(GlHistory *glh, char *line) 2496 { 2497 /* 2498 * If a recall session has already been started, but we have returned 2499 * to the preserved copy of the input line, if the user has changed 2500 * this line, we should replace the preserved copy of the original 2501 * input line with the new one. To do this simply cancel the session, 2502 * so that a new session is started below. 2503 */ 2504 if(glh->recall && glh->recall == glh->list.tail && 2505 !_glh_is_line(glh->recall->line, line, strlen(line))) { 2506 _glh_cancel_search(glh); 2507 }; 2508 /* 2509 * If this is the first line recall of a new recall session, save the 2510 * current line for potential recall later, and mark it as the last 2511 * line recalled. 2512 */ 2513 if(!glh->recall) { 2514 if(_glh_add_history(glh, line, 1)) 2515 return 1; 2516 glh->recall = glh->list.tail; 2517 /* 2518 * The above call to _glh_add_history() will have incremented the line 2519 * sequence number, after adding the line. Since we only want this to 2520 * to be incremented for permanently entered lines, decrement it again. 2521 */ 2522 glh->seq--; 2523 }; 2524 return 0; 2525 } 2526 2527 /*....................................................................... 2528 * Return non-zero if a history search session is currently in progress. 2529 * 2530 * Input: 2531 * glh GlHistory * The input-line history maintenance object. 2532 * Output: 2533 * return int 0 - No search is currently in progress. 2534 * 1 - A search is in progress. 2535 */ 2536 int _glh_search_active(GlHistory *glh) 2537 { 2538 return glh && glh->recall; 2539 } 2540 2541 /*....................................................................... 2542 * Initialize a character iterator object to point to the start of a 2543 * given history line. The first character of the line will be placed 2544 * in str->c, and subsequent characters can be placed there by calling 2545 * glh_strep_stream(). 2546 * 2547 * Input: 2548 * str GlhLineStream * The iterator object to be initialized. 2549 * line GlhHashNode * The history line to be iterated over (a 2550 * NULL value here, is interpretted as an 2551 * empty string by glh_step_stream()). 2552 */ 2553 static void glh_init_stream(GlhLineStream *str, GlhHashNode *line) 2554 { 2555 str->seg = line ? line->head : NULL; 2556 str->posn = 0; 2557 str->c = str->seg ? str->seg->s[0] : '\0'; 2558 } 2559 2560 /*....................................................................... 2561 * Copy the next unread character in the line being iterated, in str->c. 2562 * Once the end of the history line has been reached, all futher calls 2563 * set str->c to '\0'. 2564 * 2565 * Input: 2566 * str GlhLineStream * The history-line iterator to read from. 2567 */ 2568 static void glh_step_stream(GlhLineStream *str) 2569 { 2570 /* 2571 * Get the character from the current iterator position within the line. 2572 */ 2573 str->c = str->seg ? str->seg->s[str->posn] : '\0'; 2574 /* 2575 * Unless we have reached the end of the string, move the iterator 2576 * to the position of the next character in the line. 2577 */ 2578 if(str->c != '\0' && ++str->posn >= GLH_SEG_SIZE) { 2579 str->posn = 0; 2580 str->seg = str->seg->next; 2581 }; 2582 } 2583 2584 /*....................................................................... 2585 * Return non-zero if the specified search prefix contains any glob 2586 * wildcard characters. 2587 * 2588 * Input: 2589 * prefix GlhHashNode * The search prefix. 2590 * Output: 2591 * return int 0 - The prefix doesn't contain any globbing 2592 * characters. 2593 * 1 - The prefix contains at least one 2594 * globbing character. 2595 */ 2596 static int glh_contains_glob(GlhHashNode *prefix) 2597 { 2598 GlhLineStream pstr; /* The stream that is used to traverse 'prefix' */ 2599 /* 2600 * Wrap a stream iterator around the prefix, so that we can traverse it 2601 * without worrying about line-segmentation. 2602 */ 2603 glh_init_stream(&pstr, prefix); 2604 /* 2605 * Search for unescaped wildcard characters. 2606 */ 2607 while(pstr.c != '\0') { 2608 switch(pstr.c) { 2609 case '\\': /* Skip escaped characters */ 2610 glh_step_stream(&pstr); 2611 break; 2612 case '*': case '?': case '[': /* A wildcard character? */ 2613 return 1; 2614 break; 2615 }; 2616 glh_step_stream(&pstr); 2617 }; 2618 /* 2619 * No wildcard characters were found. 2620 */ 2621 return 0; 2622 } 2623 2624 /*....................................................................... 2625 * Return non-zero if the history line matches a search prefix containing 2626 * a glob pattern. 2627 * 2628 * Input: 2629 * lstr GlhLineStream * The iterator stream being used to traverse 2630 * the history line that is being matched. 2631 * pstr GlhLineStream * The iterator stream being used to traverse 2632 * the pattern. 2633 * Output: 2634 * return int 0 - Doesn't match. 2635 * 1 - The line matches the pattern. 2636 */ 2637 static int glh_line_matches_glob(GlhLineStream *lstr, GlhLineStream *pstr) 2638 { 2639 /* 2640 * Match each character of the pattern until we reach the end of the 2641 * pattern. 2642 */ 2643 while(pstr->c != '\0') { 2644 /* 2645 * Handle the next character of the pattern. 2646 */ 2647 switch(pstr->c) { 2648 /* 2649 * A match zero-or-more characters wildcard operator. 2650 */ 2651 case '*': 2652 /* 2653 * Skip the '*' character in the pattern. 2654 */ 2655 glh_step_stream(pstr); 2656 /* 2657 * If the pattern ends with the '*' wildcard, then the 2658 * rest of the line matches this. 2659 */ 2660 if(pstr->c == '\0') 2661 return 1; 2662 /* 2663 * Using the wildcard to match successively longer sections of 2664 * the remaining characters of the line, attempt to match 2665 * the tail of the line against the tail of the pattern. 2666 */ 2667 while(lstr->c) { 2668 GlhLineStream old_lstr = *lstr; 2669 GlhLineStream old_pstr = *pstr; 2670 if(glh_line_matches_glob(lstr, pstr)) 2671 return 1; 2672 /* 2673 * Restore the line and pattern iterators for a new try. 2674 */ 2675 *lstr = old_lstr; 2676 *pstr = old_pstr; 2677 /* 2678 * Prepare to try again, one character further into the line. 2679 */ 2680 glh_step_stream(lstr); 2681 }; 2682 return 0; /* The pattern following the '*' didn't match */ 2683 break; 2684 /* 2685 * A match-one-character wildcard operator. 2686 */ 2687 case '?': 2688 /* 2689 * If there is a character to be matched, skip it and advance the 2690 * pattern pointer. 2691 */ 2692 if(lstr->c) { 2693 glh_step_stream(lstr); 2694 glh_step_stream(pstr); 2695 /* 2696 * If we hit the end of the line, there is no character 2697 * matching the operator, so the pattern doesn't match. 2698 */ 2699 } else { 2700 return 0; 2701 }; 2702 break; 2703 /* 2704 * A character range operator, with the character ranges enclosed 2705 * in matching square brackets. 2706 */ 2707 case '[': 2708 glh_step_stream(pstr); /* Skip the '[' character */ 2709 if(!lstr->c || !glh_matches_range(lstr->c, pstr)) 2710 return 0; 2711 glh_step_stream(lstr); /* Skip the character that matched */ 2712 break; 2713 /* 2714 * A backslash in the pattern prevents the following character as 2715 * being seen as a special character. 2716 */ 2717 case '\\': 2718 glh_step_stream(pstr); /* Skip the backslash */ 2719 /* Note fallthrough to default */ 2720 /* 2721 * A normal character to be matched explicitly. 2722 */ 2723 /* FALLTHROUGH */ 2724 default: 2725 if(lstr->c == pstr->c) { 2726 glh_step_stream(lstr); 2727 glh_step_stream(pstr); 2728 } else { 2729 return 0; 2730 }; 2731 break; 2732 }; 2733 }; 2734 /* 2735 * To get here, pattern must have been exhausted. The line only 2736 * matches the pattern if the line as also been exhausted. 2737 */ 2738 return pstr->c == '\0' && lstr->c == '\0'; 2739 } 2740 2741 /*....................................................................... 2742 * Match a character range expression terminated by an unescaped close 2743 * square bracket. 2744 * 2745 * Input: 2746 * c char The character to be matched with the range 2747 * pattern. 2748 * pstr GlhLineStream * The iterator stream being used to traverse 2749 * the pattern. 2750 * Output: 2751 * return int 0 - Doesn't match. 2752 * 1 - The character matched. 2753 */ 2754 static int glh_matches_range(char c, GlhLineStream *pstr) 2755 { 2756 int invert = 0; /* True to invert the sense of the match */ 2757 int matched = 0; /* True if the character matched the pattern */ 2758 char lastc = '\0'; /* The previous character in the pattern */ 2759 /* 2760 * If the first character is a caret, the sense of the match is 2761 * inverted and only if the character isn't one of those in the 2762 * range, do we say that it matches. 2763 */ 2764 if(pstr->c == '^') { 2765 glh_step_stream(pstr); 2766 invert = 1; 2767 }; 2768 /* 2769 * The hyphen is only a special character when it follows the first 2770 * character of the range (not including the caret). 2771 */ 2772 if(pstr->c == '-') { 2773 glh_step_stream(pstr); 2774 if(c == '-') 2775 matched = 1; 2776 /* 2777 * Skip other leading '-' characters since they make no sense. 2778 */ 2779 while(pstr->c == '-') 2780 glh_step_stream(pstr); 2781 }; 2782 /* 2783 * The hyphen is only a special character when it follows the first 2784 * character of the range (not including the caret or a hyphen). 2785 */ 2786 if(pstr->c == ']') { 2787 glh_step_stream(pstr); 2788 if(c == ']') 2789 matched = 1; 2790 }; 2791 /* 2792 * Having dealt with the characters that have special meanings at 2793 * the beginning of a character range expression, see if the 2794 * character matches any of the remaining characters of the range, 2795 * up until a terminating ']' character is seen. 2796 */ 2797 while(!matched && pstr->c && pstr->c != ']') { 2798 /* 2799 * Is this a range of characters signaled by the two end characters 2800 * separated by a hyphen? 2801 */ 2802 if(pstr->c == '-') { 2803 glh_step_stream(pstr); /* Skip the hyphen */ 2804 if(pstr->c != ']') { 2805 if(c >= lastc && c <= pstr->c) 2806 matched = 1; 2807 }; 2808 /* 2809 * A normal character to be compared directly. 2810 */ 2811 } else if(pstr->c == c) { 2812 matched = 1; 2813 }; 2814 /* 2815 * Record and skip the character that we just processed. 2816 */ 2817 lastc = pstr->c; 2818 if(pstr->c != ']') 2819 glh_step_stream(pstr); 2820 }; 2821 /* 2822 * Find the terminating ']'. 2823 */ 2824 while(pstr->c && pstr->c != ']') 2825 glh_step_stream(pstr); 2826 /* 2827 * Did we find a terminating ']'? 2828 */ 2829 if(pstr->c == ']') { 2830 /* 2831 * Skip the terminating ']'. 2832 */ 2833 glh_step_stream(pstr); 2834 /* 2835 * If the pattern started with a caret, invert the sense of the match. 2836 */ 2837 if(invert) 2838 matched = !matched; 2839 /* 2840 * If the pattern didn't end with a ']', then it doesn't match, 2841 * regardless of the value of the required sense of the match. 2842 */ 2843 } else { 2844 matched = 0; 2845 }; 2846 return matched; 2847 } 2848 2849