death to trailing whitespace
[openafs.git] / src / gtx / textcb.c
1 /*
2  * Copyright 2000, International Business Machines Corporation and others.
3  * All Rights Reserved.
4  *
5  * This software has been released under the terms of the IBM Public
6  * License.  For details, see the LICENSE file in the top-level source
7  * directory or online at http://www.openafs.org/dl/license10.html
8  */
9
10 /*
11  * Implementation of the gator circular buffer package for its scrollable
12  * text object.
13  *
14  *------------------------------------------------------------------------*/
15
16 #include <afsconfig.h>
17 #include <afs/param.h>
18
19
20 #include "gtxtextcb.h"          /*Module interface */
21 #include <stdio.h>              /*Standard I/O stuff */
22 #include <errno.h>
23 #include <string.h>
24 #include <stdlib.h>
25
26 static int gator_textcb_debug;  /*Is debugging output turned on? */
27
28 /*------------------------------------------------------------------------
29  * gator_textcb_Init
30  *
31  * Description:
32  *      Initialize the text circular buffer package.
33  *
34  * Arguments:
35  *      a_debug : Should debugging output be turned on?
36  *
37  * Returns:
38  *      Zero if successful,
39  *      Error value otherwise.
40  *
41  * Environment:
42  *      MUST BE THE FIRST ROUTINE CALLED FROM THIS PACKAGE.
43  *
44  * Side Effects:
45  *      Just remembers if debugging output should be generated.
46  *------------------------------------------------------------------------*/
47
48 int
49 gator_textcb_Init(int a_debug)
50 {                               /*gator_textcb_Init */
51
52     static int initd;           /*Have we been called already? */
53     static char rn[] = "gator_textcb_Init";     /*Routine name */
54
55     if (initd) {
56         fprintf(stderr,
57                 "[%s] Initialization routine called multiple times!!\n", rn);
58         return (0);
59     } else
60         initd = 1;
61
62     gator_textcb_debug = a_debug;
63     return (0);
64
65 }                               /*gator_textcb_Init */
66
67 /*------------------------------------------------------------------------
68  * gator_textcb_Create
69  *
70  * Description:
71  *      Create a new circular buffer.
72  *
73  * Arguments:
74  *      int a_maxEntriesStored : How many entries should it have?
75  *      int a_maxCharsPerEntry : Max chars in each entry.
76  *
77  * Returns:
78  *      Ptr to the fully-initialized circular buffer hdr if successful,
79  *      Null pointer otherwise.
80  *
81  * Environment:
82  *      Makes sure the lock structure is properly initialized.
83  *
84  * Side Effects:
85  *      As advertised; space is allocated for the circ buff.
86  *------------------------------------------------------------------------*/
87
88 struct gator_textcb_hdr *
89 gator_textcb_Create(int a_maxEntriesStored, int a_maxCharsPerEntry)
90 {                               /*gator_textcb_Create */
91
92     static char rn[] = "gator_textcb_Create";   /*Routine name */
93     char *newBuff;              /*Ptr to new text buffer */
94     struct gator_textcb_entry *newEntries;      /*Ptr to new text entries */
95     struct gator_textcb_hdr *newHdr;    /*Ptr to new text hdr */
96     int bytesToAllocate;        /*Num bytes to allocate */
97     int numBuffBytes;           /*Num bytes in text buffer */
98     int curr_ent_num;           /*Current entry number */
99     struct gator_textcb_entry *curr_ent;        /*Ptr to current entry */
100     char *curr_buff;            /*Ptr to current buff pos */
101     int curr_inv;               /*Current inversion idx */
102     char *blankLine;            /*Ptr to blank line */
103     int i;                      /*Loop variable */
104
105     /*
106      * Start off by allocating the text buffer itself.  Don't forget we
107      * need to allocate one more character per line, to make sure we can
108      * always null-terminate them.  We also need to allocate the blank
109      * line buffer.
110      */
111     numBuffBytes = bytesToAllocate =
112         a_maxEntriesStored * (a_maxCharsPerEntry + 1);
113     if (gator_textcb_debug)
114         fprintf(stderr, "[%s] Allocating %d bytes for the text buffer\n", rn,
115                 bytesToAllocate);
116     newBuff = (char *)malloc(bytesToAllocate);
117     if (newBuff == NULL) {
118         fprintf(stderr,
119                 "[%s] Can't allocate %d bytes for text buffer; errno is %d\n",
120                 rn, bytesToAllocate, errno);
121         return ((struct gator_textcb_hdr *)0);
122     } else if (gator_textcb_debug)
123         fprintf(stderr, "[%s] Text buffer allocated at %p\n", rn, newBuff);
124     blankLine = (char *)malloc(a_maxCharsPerEntry + 1);
125     if (blankLine == NULL) {
126         fprintf(stderr,
127                 "[%s] Can't allocate %d bytes for blank line buffer; errno is %d\n",
128                 rn, a_maxCharsPerEntry + 1, errno);
129         free(newBuff);
130         return ((struct gator_textcb_hdr *)0);
131     }
132
133     /*
134      * Next, allocate the entry array.
135      */
136     bytesToAllocate = a_maxEntriesStored * sizeof(struct gator_textcb_entry);
137     if (gator_textcb_debug)
138         fprintf(stderr,
139                 "[%s] Allocating %d bytes for the %d text entry array items\n",
140                 rn, bytesToAllocate, a_maxEntriesStored);
141     newEntries = (struct gator_textcb_entry *)malloc(bytesToAllocate);
142     if (newEntries == (struct gator_textcb_entry *)0) {
143         fprintf(stderr,
144                 "[%s] Can't allocate %d bytes for the %d-member text entry array; errno is %d\n",
145                 rn, bytesToAllocate, a_maxEntriesStored, errno);
146         free(newBuff);
147         free(blankLine);
148         return ((struct gator_textcb_hdr *)0);
149     } else if (gator_textcb_debug)
150         fprintf(stderr, "[%s] Text buffer entry array allocated at %p\n",
151                 rn, newEntries);
152
153     /*
154      * Finish off by allocating the text circular buffer header.
155      */
156     bytesToAllocate = sizeof(struct gator_textcb_hdr);
157     if (gator_textcb_debug)
158         fprintf(stderr,
159                 "[%s] Allocating %d bytes for the text circular buffer header\n",
160                 rn, bytesToAllocate);
161     newHdr = (struct gator_textcb_hdr *)malloc(bytesToAllocate);
162     if (newHdr == (struct gator_textcb_hdr *)0) {
163         fprintf(stderr,
164                 "[%s] Can't allocate %d bytes for text circular buffer header; errno is %d\n",
165                 rn, bytesToAllocate, errno);
166         free(newBuff);
167         free(blankLine);
168         free(newEntries);
169         return ((struct gator_textcb_hdr *)0);
170     } else if (gator_textcb_debug)
171         fprintf(stderr,
172                 "[%s] Text circular buffer header allocated at %p\n", rn,
173                 newHdr);
174
175     /*
176      * Now, just initialize all the pieces and plug them in.
177      */
178     if (gator_textcb_debug)
179         fprintf(stderr, "[%s] Zeroing %d bytes in text buffer at %p\n", rn,
180                 numBuffBytes, newBuff);
181     memset(newBuff, 0, numBuffBytes);
182
183     if (gator_textcb_debug)
184         fprintf(stderr, "[%s] Initializing blank line buffer at %p\n", rn,
185                 blankLine);
186     for (i = 0; i < a_maxCharsPerEntry; i++)
187         *(blankLine + i) = ' ';
188     *(blankLine + a_maxCharsPerEntry) = '\0';
189
190     /*
191      * Initialize each buffer entry.
192      */
193     for (curr_ent_num = 0, curr_buff = newBuff, curr_ent = newEntries;
194          curr_ent_num < a_maxEntriesStored;
195          curr_ent++, curr_ent_num++, curr_buff += (a_maxCharsPerEntry + 1)) {
196         if (gator_textcb_debug)
197             fprintf(stderr,
198                     "[%s] Initializing buffer entry %d; its text buffer address is %p\n",
199                     rn, curr_ent_num, curr_buff);
200         curr_ent->ID = 0;
201         curr_ent->highlight = 0;
202         curr_ent->numInversions = 0;
203         curr_ent->charsUsed = 0;
204         curr_ent->textp = curr_buff;
205         memcpy(curr_ent->textp, blankLine, a_maxCharsPerEntry + 1);
206         for (curr_inv = 0; curr_inv < GATOR_TEXTCB_MAXINVERSIONS; curr_inv++)
207             curr_ent->inversion[curr_inv] = 0;
208
209     }                           /*Init each buffer entry */
210
211     if (gator_textcb_debug)
212         fprintf(stderr, "[%s] Filling in circ buff header at %p\n", rn,
213                 newHdr);
214     Lock_Init(&(newHdr->cbLock));
215     newHdr->maxEntriesStored = a_maxEntriesStored;
216     newHdr->maxCharsPerEntry = a_maxCharsPerEntry;
217     newHdr->currEnt = 0;
218     newHdr->currEntIdx = 0;
219     newHdr->oldestEnt = 0;
220     newHdr->oldestEntIdx = 0;
221     newHdr->entry = newEntries;
222     newHdr->blankLine = blankLine;
223
224     /*
225      * Finally, return the location of the new header.
226      */
227     return (newHdr);
228
229 }                               /*gator_textcb_Create */
230
231 /*------------------------------------------------------------------------
232  * bumpCB
233  *
234  * Description:
235  *      Move down to the next circular buffer entry.
236  *
237  * Arguments:
238  *      struct gator_textcb_hdr *a_cbhdr : Circ buff header to bump.
239  *
240  * Returns:
241  *      Ptr to the newest current entry.
242  *
243  * Environment:
244  *      Nothing interesting.
245  *
246  * Side Effects:
247  *      As advertised.
248  *------------------------------------------------------------------------*/
249
250 static struct gator_textcb_entry *
251 bumpEntry(struct gator_textcb_hdr *a_cbhdr)
252
253 {                               /*bumpEntry */
254
255     static char rn[] = "bumpEntry";     /*Routine name */
256     struct gator_textcb_entry *curr_ent;        /*Ptr to current entry */
257     int inv;                    /*Inversion number */
258
259     /*
260      * Bump the total number of writes, and don't forget to advance
261      * the oldest entry, if appropriate.
262      */
263     if (gator_textcb_debug)
264         fprintf(stderr,
265                 "[%s]: Bumping entry for circular buffer at %p; current values: currEnt=%d (idx %d), oldestEnt=%d (idx %d), maxEntriesStored=%d\n",
266                 rn, a_cbhdr, a_cbhdr->currEnt, a_cbhdr->currEntIdx,
267                 a_cbhdr->oldestEnt, a_cbhdr->oldestEntIdx,
268                 a_cbhdr->maxEntriesStored);
269
270     a_cbhdr->currEnt++;
271     if (++(a_cbhdr->currEntIdx) >= a_cbhdr->maxEntriesStored)
272         a_cbhdr->currEntIdx = 0;
273     curr_ent = a_cbhdr->entry + a_cbhdr->currEntIdx;
274
275     if (gator_textcb_debug)
276         fprintf(stderr, "[%s] Zeroing entry %d (idx %d) at %p\n", rn,
277                 a_cbhdr->currEnt, a_cbhdr->currEntIdx, curr_ent);
278
279     curr_ent->ID = a_cbhdr->currEnt;
280     curr_ent->highlight = 0;
281     curr_ent->numInversions = 0;
282     curr_ent->charsUsed = 0;
283     /*
284      * Copy over a blank line into the one we're initializing.  We
285      * copy over the trailing null, too.
286      */
287     memcpy(curr_ent->textp, a_cbhdr->blankLine,
288            a_cbhdr->maxCharsPerEntry + 1);
289     for (inv = 0; inv < GATOR_TEXTCB_MAXINVERSIONS; inv++)
290         curr_ent->inversion[inv] = 0;
291
292     /*
293      * If we've already stated circulating in the buffer, remember to
294      * bump the oldest entry info too.
295      */
296     if (a_cbhdr->currEnt >= a_cbhdr->maxEntriesStored) {
297         if (gator_textcb_debug)
298             fprintf(stderr,
299                     "[%s]: Advancing oldest entry number & index; was entry %d, index %d, now ",
300                     rn, a_cbhdr->oldestEnt, a_cbhdr->oldestEntIdx);
301         a_cbhdr->oldestEnt++;
302         if (++(a_cbhdr->oldestEntIdx) >= a_cbhdr->maxEntriesStored)
303             a_cbhdr->oldestEntIdx = 0;
304         if (gator_textcb_debug)
305             fprintf(stderr, "entry %d, index %d\n", a_cbhdr->oldestEnt,
306                     a_cbhdr->oldestEntIdx);
307     }
308
309     /*Bump oldest entry info */
310     /*
311      * Finally, return the address of the newest current entry.
312      */
313     return (curr_ent);
314
315 }                               /*bumpEntry */
316
317 /*------------------------------------------------------------------------
318  * gator_textcb_Write
319  *
320  * Description:
321  *      Write the given string to the text circular buffer.  Line
322  *      breaks are caused either by overflowing the current text
323  *      line or via explicit '\n's.
324  *
325  * Arguments:
326  *      struct gator_textcb_hdr *a_cbhdr : Ptr to circ buff hdr.
327  *      char *a_textToWrite              : Ptr to text to insert.
328  *      int a_numChars                   : Number of chars to write.
329  *      int a_highlight                  : Use highlighting?
330  *      int a_skip;                      : Force a skip to the next line?
331  *
332  * Returns:
333  *      Zero if successful,
334  *      Error value otherwise.
335  *
336  * Environment:
337  *      Circular buffer is consistent upon entry, namely the first and
338  *      last entry pointers are legal.
339  *
340  * Side Effects:
341  *      As advertised.
342  *------------------------------------------------------------------------*/
343
344 int
345 gator_textcb_Write(struct gator_textcb_hdr *a_cbhdr, char *a_textToWrite,
346                    int a_numChars, int a_highlight, int a_skip)
347 {                               /*gator_textcb_Write */
348
349     static char rn[] = "gator_textcb_Write";    /*Routine name */
350     struct gator_textcb_entry *curr_ent;        /*Ptr to current text entry */
351     int curr_ent_idx;           /*Index of current entry */
352     int max_chars;              /*Max chars per entry */
353     int chars_to_copy;          /*Num chars to copy in */
354     int effective_highlight;    /*Tmp highlight value */
355     char *dest;                 /*Destination of char copy */
356
357     /*
358      * Make sure we haven't been passed a bogus buffer, and lock it
359      * before we start.
360      */
361     if (a_cbhdr == (struct gator_textcb_hdr *)0) {
362         fprintf(stderr,
363                 "[%s]: Null pointer passed in for circ buff header!!  Aborting write operation.\n",
364                 rn);
365         return (-1);
366     }
367     ObtainWriteLock(&(a_cbhdr->cbLock));
368
369     curr_ent_idx = a_cbhdr->currEntIdx;
370     curr_ent = (a_cbhdr->entry) + curr_ent_idx;
371     max_chars = a_cbhdr->maxCharsPerEntry;
372     effective_highlight = curr_ent->highlight;
373     if (curr_ent->numInversions % 2)
374         effective_highlight = (effective_highlight ? 0 : 1);
375     if (gator_textcb_debug)
376         fprintf(stderr,
377                 "[%s]: Current entry: %d (at index %d, keeping %d max), effective highlight: %d, located at %p\n",
378                 rn, a_cbhdr->currEnt, curr_ent_idx, a_cbhdr->maxEntriesStored,
379                 effective_highlight, curr_ent);
380
381     while (a_numChars > 0) {
382         /*
383          * There are still characters to stuff into our circular buffer.
384          */
385         if (gator_textcb_debug)
386             fprintf(stderr,
387                     "[%s]: Top of write loop: %d char(s) left to write.\n",
388                     rn, a_numChars);
389
390         if (curr_ent->charsUsed >= max_chars) {
391             /*
392              * Bump the entry in the given circular buffer.
393              */
394             if (gator_textcb_debug)
395                 fprintf(stderr,
396                         "[%s]: Entry %d at index %d full, advancing to next one.\n",
397                         rn, a_cbhdr->currEnt, curr_ent_idx);
398             curr_ent = bumpEntry(a_cbhdr);
399             curr_ent_idx = a_cbhdr->currEntIdx;
400             if (gator_textcb_debug)
401                 fprintf(stderr,
402                         "[%s] New CB entry info: currEnt=%d (idx %d), oldestEnt=%d (idx %d), curr entry ptr is %p\n",
403                         rn, a_cbhdr->currEnt, a_cbhdr->currEntIdx,
404                         a_cbhdr->oldestEnt, a_cbhdr->oldestEntIdx, curr_ent);
405         }
406
407         /*Bump current entry */
408         /*
409          * At this point, the current entry has room for at least one more
410          * character, and we have at least one more character to write.
411          * Insert as much from the user text as possible.
412          */
413         chars_to_copy = max_chars - curr_ent->charsUsed;
414         if (a_numChars < chars_to_copy)
415             chars_to_copy = a_numChars;
416         dest = curr_ent->textp + curr_ent->charsUsed;
417         if (gator_textcb_debug)
418             fprintf(stderr,
419                     "[%s]: Copying %d char(s) into current entry at %p (entry buffer starts at %p)\n",
420                     rn, chars_to_copy, dest, curr_ent->textp);
421
422         /*
423          * Handle highlighting and inversions.
424          */
425         if (curr_ent->charsUsed == 0) {
426             /*
427              * No chars yet, so this sets the highlight field.
428              */
429             effective_highlight = curr_ent->highlight = a_highlight;
430         } else if (effective_highlight != a_highlight) {
431             /*
432              * We need a new inversion, if there's room.
433              */
434             if (gator_textcb_debug)
435                 fprintf(stderr,
436                         "[%s]: Highlight mismatch, recording inversion at char loc %d\n",
437                         rn, curr_ent->charsUsed);
438             if (curr_ent->numInversions < GATOR_TEXTCB_MAXINVERSIONS) {
439                 effective_highlight = a_highlight;
440                 curr_ent->inversion[curr_ent->numInversions] =
441                     curr_ent->charsUsed;
442                 curr_ent->numInversions++;
443             } else if (gator_textcb_debug)
444                 fprintf(stderr, "[%s]: Out of inversions!!\n", rn);
445         }
446
447         /*Handle inversion */
448         /*
449          * Move the string chunk into its place in the buffer, bump the
450          * number of chars used in the current entry.
451          */
452         strncpy(dest, a_textToWrite, chars_to_copy);
453         curr_ent->charsUsed += chars_to_copy;
454         a_textToWrite += chars_to_copy;
455         a_numChars -= chars_to_copy;
456
457     }                           /*while (a_numChars > 0) */
458
459     /*
460      * All characters have been copied in.  Handle the case where we've
461      * been asked to skip to the next entry, even if there's still room
462      * in the current one.
463      */
464     if (a_skip) {
465         if (gator_textcb_debug)
466             fprintf(stderr, "[%s] Handling request to skip to next entry\n",
467                     rn);
468         if (curr_ent->charsUsed > 0)
469             curr_ent = bumpEntry(a_cbhdr);
470         else if (gator_textcb_debug)
471             fprintf(stderr,
472                     "[%s] Not skipping, we're already on a fresh entry\n",
473                     rn);
474     }
475
476     /*Skip to the next entry */
477     /*
478      * We can now unlock the CB and return successfully.
479      */
480     ReleaseWriteLock(&(a_cbhdr->cbLock));
481     return (0);
482
483 }                               /*gator_textcb_Write */
484
485 /*------------------------------------------------------------------------
486  * gator_textcb_BlankLine
487  *
488  * Description:
489  *      Write out some number of blank lines to the given circular
490  *      buffer.
491  *
492  * Arguments:
493  *      struct gator_textcb_hdr *a_cbhdr : Ptr to circ buff hdr.
494  *      int *a_numBlanks                 : Num. blank lines to write.
495  *
496  * Returns:
497  *      Zero if successful,
498  *      Error value otherwise.
499  *
500  * Environment:
501  *      Circular buffer is consistent upon entry, namely the first and
502  *      last entry pointers are legal.
503  *
504  * Side Effects:
505  *      As advertised.
506  *------------------------------------------------------------------------*/
507
508 int
509 gator_textcb_BlankLine(struct gator_textcb_hdr *a_cbhdr,
510                        int a_numBlanks)
511 {                               /*gator_textcb_BlankLine */
512
513     static char rn[] = "gator_textcb_BlankLine";        /*Routine name */
514
515     if (gator_textcb_debug)
516         fprintf(stderr, "[%s] Putting out %d blank lines to the CB at %p\n",
517                 rn, a_numBlanks, a_cbhdr);
518
519     if (a_cbhdr == (struct gator_textcb_hdr *)0) {
520         if (gator_textcb_debug)
521             fprintf(stderr, "[%s] Null pointer passed for CB hdr!!\n", rn);
522         return (-1);
523     }
524
525     while (a_numBlanks > 0) {
526         /*
527          * The bumpEntry routine returns a struct gator_textcb_entry
528          * pointer, but we don't need it here, so we don't assign it.
529          */
530         bumpEntry(a_cbhdr);
531         a_numBlanks--;
532     }
533
534     /*
535      * Return happily and successfully.
536      */
537     return (0);
538
539 }                               /*gator_textcb_Write */
540
541 /*------------------------------------------------------------------------
542  * gator_textcb_Delete
543  *
544  * Description:
545  *      Delete the storage used by the given circular buffer, including
546  *      the header itself.
547  *
548  * Arguments:
549  *      struct gator_textcb_hdr *a_cbhdr : Ptr to the header of the
550  *                                              circ buffer to reap.
551  *
552  * Returns:
553  *      Zero if successful,
554  *      Error value otherwise.
555  *
556  * Environment:
557  *      We write-lock the buffer before deleting it, which is slightly
558  *      silly, since it will stop existing after we're done.  At least
559  *      we'll make sure nobody is actively writing to it while it's
560  *      being deleted.
561  *
562  * Side Effects:
563  *      As advertised.
564  *------------------------------------------------------------------------*/
565
566 int
567 gator_textcb_Delete(struct gator_textcb_hdr *a_cbhdr)
568 {                               /*gator_textcb_Delete */
569
570     static char rn[] = "gator_textcb_Delete";   /*Routine name */
571
572     if (gator_textcb_debug)
573         fprintf(stderr, "[%s]: Deleting text circular buffer at %p\n", rn,
574                 a_cbhdr);
575     ObtainWriteLock(&(a_cbhdr->cbLock));
576
577     /*
578      * The beginning of the text buffer itself is pointed to by the
579      * first text entry.
580      */
581     if (gator_textcb_debug)
582         fprintf(stderr,
583                 "[%s]: Freeing text buffer proper at %p (%d bytes)\n", rn,
584                 a_cbhdr->entry[0].textp,
585                 (a_cbhdr->maxEntriesStored * a_cbhdr->maxCharsPerEntry));
586     free(a_cbhdr->entry[0].textp);
587     a_cbhdr->entry[0].textp = NULL;
588
589     if (gator_textcb_debug)
590         fprintf(stderr, "[%s]: Freeing text entry array at %p (%" AFS_SIZET_FMT " bytes)\n",
591                 rn, a_cbhdr->entry,
592                 (a_cbhdr->maxEntriesStored *
593                  sizeof(struct gator_textcb_entry)));
594     free(a_cbhdr->entry);
595     a_cbhdr->entry = (struct gator_textcb_entry *)0;
596     free(a_cbhdr->blankLine);
597     a_cbhdr->blankLine = NULL;
598
599     /*
600      * Release the write lock on it, then free the header itself.
601      */
602     ReleaseWriteLock(&(a_cbhdr->cbLock));
603     if (gator_textcb_debug)
604         fprintf(stderr, "[%s] Freeing cicular buffer header at %p\n", rn,
605                 a_cbhdr);
606     free(a_cbhdr);
607     return (0);
608
609 }                               /*gator_textcb_Delete */