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