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