1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
|
/* Line Control for VTY Terminal output
* Copyright (C) 2010 Chris Hall (GMCH), Highwayman
*
* This file is part of GNU Zebra.
*
* GNU Zebra is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation; either version 2, or (at your
* option) any later version.
*
* GNU Zebra is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GNU Zebra; see the file COPYING. If not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include "misc.h"
#include "memory.h"
#include "vio_lines.h"
#include "qiovec.h"
/*==============================================================================
* Line control handles the output of simple text to a telnet connection,
* folding and counting lines (for "--more--" purposes) if required.
*
* LIMITATIONS:
*
* 1) does not handle '\r' except as part of '\r''\n' pairs.
*
* Telnet requires that bare '\r' be sent as '\r''\0'. That is not
* implemented.
*
* The handling of '\r' which is not part of '\r''\n' is UNDEFINED.
* (In particular, the '\r' may be sent as is, or not sent at all.)
*
* 2) does not worry about '\t' or '\b' or any other control character.
*
* Apart from '\r' and '\n' all characters are deemed to be printing
* characters -- and to have width == 1.
*
* 3) has no idea about escape sequences or telnet commands.
*
* In particular: when looking for '\n' (and '\r') has no way of telling
* if those are part of an escape sequence.
*
* 4) DOES NOT handle 0xFF character value.
*
* For Telnet this should be escaped. It isn't.
*
* Current use of VTY command output will not be troubled by these limitations.
* To do more would cost code and cpu unnecessarily.
*
* WHAT IT DOES DO:
*
* 1) maps bare '\n' to '\r''\n' -- if required.
*
* Swallows '\r' immediately before '\n' if present.
*
* 2) if required, breaks output into screen width chunks, and counts
* down the height of a "screen full".
*
*/
/*==============================================================================
* Initialise, allocate, reset etc.
*/
/*------------------------------------------------------------------------------
* Initialise new vio_line_control -- allocate if required.
*
* This is for initialising a new structure. Any current contents are lost.
*
* A width of <= 0 => very large width indeed.
* A height of <= 0 => indefinite height
*
* Returns: address of vio_line_control
*/
extern vio_line_control
vio_lc_init_new(vio_line_control lc, int width, int height, const char* newline)
{
if (lc == NULL)
lc = XCALLOC(MTYPE_VIO_LC, sizeof(struct vio_line_control)) ;
else
memset(lc, 0, sizeof(vio_line_control_t)) ;
/* Zeroising has set:
*
* width = X -- set below.
* height = X -- set below
*
* counter = X -- set below
*
* incomplete = false -- no incomplete line in hand
* fragments = NULL -- set below
* here = NULL -- set below
*
* qiov = NULL -- set below
*
* newline = zeros -- set below
*/
lc->width = width >= 0 ? width : 0 ;
lc->height = height >= 0 ? height : 0 ;
vio_lc_counter_reset(lc) ;
lc->fragments = qiovec_init_new(NULL) ;
lc->here = qs_new(120) ;
lc->qiov = qiovec_init_new(NULL) ;
lc->newline->base = newline ;
lc->newline->len = strlen(newline) ;
return lc ;
} ;
/*------------------------------------------------------------------------------
* Reset vio_line_control (if any) -- release body and (if required) free the
* structure.
*
* Returns: address of vio_line_control (if any) -- NULL if structure released
*
* NB: if the line control is not freed, it MUST be reinitialised by
* vio_lc_init_new() before it is used again !
*
* NB: it is the callers responsibility to release anything which was buffered
* for the line control to look after.
*
* It is also the callers responsibility to release the newline string,
* if required.
*/
extern vio_line_control
vio_lc_reset(vio_line_control lc, free_keep_b free_structure)
{
if (lc != NULL)
{
lc->fragments = qiovec_free(lc->fragments) ;
lc->here = qs_free(lc->here) ;
lc->qiov = qiovec_free(lc->qiov) ;
if (free_structure)
XFREE(MTYPE_VIO_LC, lc) ; /* sets lc = NULL */
else
memset(lc, 0, sizeof(vio_line_control_t)) ;
} ;
return lc ;
} ;
/*------------------------------------------------------------------------------
* Clear given vio_line_control -- discard all buffered lines and reset the
* counter.
*
* NB: it is the callers responsibility to release anything buffered because
* it was earlier appended.
*/
extern void
vio_lc_clear(vio_line_control lc)
{
if (lc == NULL)
return ;
vio_lc_counter_reset(lc) ;
lc->incomplete = false ;
qiovec_clear(lc->fragments) ;
qs_clear(lc->here) ;
qiovec_clear(lc->qiov) ;
} ;
/*------------------------------------------------------------------------------
* Sets width and height for line control
*
* A width of <= 0 => very large width indeed.
* A height of <= 0 => indefinite height
*
* This may happen at almost any time when a Telnet terminal is resized.
* From line control perspective, can happen between calls of vio_lc_append(),
* vio_lc_flush() and vio_lc_write_nb().
*
* If happens while the line control has stuff buffered, then it is too late to
* change anything, unless was an incomplete line.
*
* Tries to sort out the counter... but is is not possible to do this perfectly.
*/
extern void
vio_lc_set_window(vio_line_control lc, int width, int height)
{
unsigned depth ;
depth = lc->height ;
lc->width = width >= 0 ? width : 0 ;
lc->height = height >= 0 ? height : 0 ;
/* If counter already exhausted, or just set indefinite height, need do
* nothing more.
*/
if ((lc->counter <= 0) || (lc->height == 0))
return ;
/* Counter not already exhausted, and is setting a definite height.
*
* If no height was set before, start from scratch, now.
*/
if (depth == 0)
return vio_lc_counter_reset(lc) ;
/* Had a definite height and setting a new one.
*
* Now calculate the depth, which is how far down the old height have
* got to. (The counter should be less than the old height, but if it
* isn't we end up with a huge depth here...)
*/
depth -= lc->counter ;
/* If the new height is > depth set the counter to be the new height - depth.
*
* Otherwise, set the counter to 0, so is immediately exhausted.
*
* Cannot solve the problem of what to do if the width has changed !
*/
lc->counter = (lc->height > depth) ? lc->height - depth : 0 ;
} ;
/*==============================================================================
* Appending and writing
*/
static uint vio_lc_trim(vio_line_control lc, qiov_item item, const char* e) ;
static void vio_lc_append_line(vio_line_control lc, qiov_item item) ;
/*------------------------------------------------------------------------------
* Append a lump of output to the given line control's buffers.
*
* Breaks the output into screen lines which are no longer than the lc->width.
* If that is zero (unset) we use a very large width indeed.
*
* Trims trailing whitespace from each line (but not from screen lines), before
* breaking up into screen lines.
*
* NB: this means that does not process an incoming line until gets a '\n', or
* the line control is flushed -- see vio_lc_flush().
*
* Effect of line counter:
*
* * if definite height, will put only as many screen lines as it can into
* the iovec, including none at all.
*
* In this case, the counter will reach exactly 0 and then stop.
*
* * if indefinite height, when the line counter is reset it is set to some
* limit on the number of screen lines to buffer in the line control.
*
* Note that this is not exact, in particular will *not* stop in the
* middle of a complete line, just because the limit of screen lines has
* been exceeded.
*
* In this case, the counter may go negative before it stops.
*
* Maps '\n' to '\r''\n'.
*
* Discards '\r' amongst trailing whitespace, but not otherwise.
*
* Returns: number of bytes able to append to the line control.
*
* NB: the buffer presented MUST be retained until the contents of the line
* control's buffers have been written -- see vio_lc_write_nb().
*
* NB: the line control may buffer stuff "in hand", before it is put into the
* iovec, either because:
*
* a) the current line is incomplete.
*
* b) the line counter was exhausted while processing a complete line.
*
* When there is nothing to append to the line control, then need to check
* whether there is stuff still in hand. A call of vio_lc_append() with
* zero bytes will cause any complete line fragments to be processed.
*
* NB: all output *from* the line control ends with a newline.
*/
extern size_t
vio_lc_append(vio_line_control lc, const void* buf, size_t len)
{
qiov_item_t item ;
const char* bp ;
const char* be ;
/* If we have a complete line which was being processed, but was interrupted
* part way through by line counter expiry... try to empty that out, first.
*
* This may be prevented by the line counter (partly or completely), or may
* exhaust it, which will be noticed, shortly, below.
*/
if (!qiovec_empty(lc->fragments) && !lc->incomplete)
{
qiovec_shift(lc->fragments, item) ; /* want first fragment */
vio_lc_append_line(lc, item) ;
} ;
/* Append: stop when run out of data or run out of lines
*/
be = (const char*)buf + len ;
bp = buf ;
/* If is counter exhausted, stop appending now.
*
* In vio_lc_append_line() updates the counter, and may set it -ve.
*/
while ((bp < be) && (lc->counter > 0))
{
item->base = bp ; /* start of current fragment */
/* scan for '\n' -- note that we look for a complete line,
* before worrying about the line
*/
bp = memchr(bp, '\n', (be - bp)) ;
if (bp == NULL)
{
/* We do not have a '\n' -- rats -- have incomplete line in-hand
*/
item->len = be - item->base ;
qiovec_push(lc->fragments, item) ;
lc->incomplete = true ;
bp = be ;
break ; /* done */
} ;
/* We have a '\n' -- hurrah !
*
* Have a complete line ready to be added to the qiov -- the
* current fragment and any buffered ones.
*
* Trim off any trailing whitespace and establish the length of the
* last fragment of the current (complete) line. If the current
* fragment is all whitespace, works its way up any in-hand fragments.
*/
lc->incomplete = false ; /* definitely not */
item->len = vio_lc_trim(lc, item, bp) ;
++bp ; /* step past the '\n' */
/* If have fragments in hand, then want to have the first fragment
* as the current fragment (currently the current fragment is the
* last fragment).
*/
if (!qiovec_empty(lc->fragments))
{
qiovec_push(lc->fragments, item) ;
qiovec_shift(lc->fragments, item) ;
} ;
/* We are now ready to break the current line up into width
* sections, if required -- to the extent allowed by the counter.
*
* Have trimmed off any trailing whitespace, including back across
* any fragments. So:
*
* item = first fragment of the line
*/
vio_lc_append_line(lc, item) ;
} ;
/* Exhausted the available data or the line count
*/
return (bp - (const char*)buf) ; /* what have taken */
} ;
/*------------------------------------------------------------------------------
* Flush anything which the line control has buffered.
*
* There are two possible cases:
*
* 1) had collected a complete line in vio_lc_append(), but when putting to
* the qiovec was stopped by the line counter.
*
* So have one or more screen lines buffered up, ready to go.
*
* 2) had collected an incomplete line in vio_lc_append().
*
* That will now be flushed as if a newline had been appended, except that
* if the result would be a newline *only*, then all the whitespace is
* discarded and nothing is added to the qiovec.
*
* If there is nothing to flush, or an incomplete line turns out to be
* entirely whitespace, return false.
*
* Otherwise we have something to flush, and will attempt to do so.
*
* Effect of line counter:
*
* * if definite height, will prepare only as many screen lines as it can,
* including none at all -- counter may reach 0, exactly.
*
* * if indefinite height, will prepare as many screen lines as it takes,
* whatever the state of the line counter -- which may go -ve.
*
* Returns: true <=> have something to flush (but counter may be exhausted)
* false <=> nothing to be flushed
*/
extern bool
vio_lc_flush(vio_line_control lc)
{
qiov_item_t item ;
if (qiovec_count(lc->fragments) == 0)
return false ;
/* We have something in hand.
*
* If was an incomplete line, now is the time to trim off any trailing
* whitespace -- more or less as if there had been a '\n' at this point.
*/
if (lc->incomplete)
{
lc->incomplete = false ;
qiovec_pop(lc->fragments, item) ;
item->len = vio_lc_trim(lc, item, item->base + item->len) ;
if (item->len == 0)
{
qassert(qiovec_empty(lc->fragments)) ;
return false ; /* it was all whitespace which has all
been discarded. */
} ;
qiovec_push(lc->fragments, item) ;
} ;
/* Have something in hand, so now attempt to add it to the iovec.
*/
qiovec_shift(lc->fragments, item) ;
vio_lc_append_line(lc, item) ;
return true ;
} ;
/*------------------------------------------------------------------------------
* Trim trailing whitespace from given fragment -- ' ', '\t' and '\r'.
*
* If required, pops fragments until finds some non-whitespace, or runs out
* of fragments.
*
* Returns: resulting length.
*/
static uint
vio_lc_trim(vio_line_control lc, qiov_item item, const char* e)
{
const char* p ;
while (1)
{
p = item->base ;
while (e > p)
{
char ch ;
ch = *(e - 1) ;
if ( (ch != '\r') && (ch != ' ') && (ch != '\t') )
return e - p ; /* <-<- found non-whitespace <-<- exit */
--e ;
} ;
qiovec_pop(lc->fragments, item) ; /* pops a NULL if empty */
if (item->len == 0)
return 0 ;
e = item->base + item->len ;
} ;
} ;
/*------------------------------------------------------------------------------
* Append incoming line to the qiov -- breaking it up into width screen lines,
* inserting newlines and looking out for and updating the line counter.
*
* If definite height, may stop immediately, without appending anything.
* If indefinite height, will append the entire current line, whatever the
* state of the line counter.
*
* Note that do not trim whitespace from screen lines.
*
* Requires that have previously trimmed off any trailing whitespace, including
* back across any fragments. So:
*
* item = first fragment of the line -- NOT in lc->fragments.
*
* It is possible for the first fragment to be empty (empty line !).
*/
static void
vio_lc_append_line(vio_line_control lc, qiov_item item)
{
bool done = false ;
while ((lc->counter > 0) || (lc->height == 0))
{
uint take ;
/* Take fragments or parts thereof until we run out (set done == true)
* or reach the width.
*/
take = (lc->width > 0) ? lc->width : UINT_MAX ;
while (1)
{
if (item->len <= take)
{
/* Use entire fragment, and step to next (if any)
*
* Note that qiovec_push() ignores zero length items, so if
* this is an empty line, will push no fragments and will stop
* here.
*/
qiovec_push(lc->qiov, item) ;
if (qiovec_empty(lc->fragments))
{
done = true ; /* signal all done */
break ;
}
else
{
take -= item->len ;
qiovec_shift(lc->fragments, item) ;
}
}
else
{
/* Use leading part of fragment, and reduce same
*/
qiovec_push_this(lc->qiov, item->base, take) ;
item->base += take ;
item->len -= take ;
break ;
} ;
} ;
qiovec_push(lc->qiov, lc->newline) ;
/* Count down & exit if all done.
*/
--lc->counter ;
if (done)
return ;
} ;
/* Counter exhausted, but we are not done with the current line.
*
* unshift the current item onto the (front of) the fragments qiovec.
* (We know we can do this straightforwardly, because either the qiovec is
* empty, or what was the first item has previously been shifted off.)
*/
qiovec_unshift(lc->fragments, item) ;
} ;
/*------------------------------------------------------------------------------
* Write away any collected screen lines -- assuming NON-BLOCKING.
*
* Does nothing if the line control is empty.
*
* Loops internally if gets EINTR.
*
* Returns: > 0 => one or more bytes left to output
* 0 => all done -- zero bytes left to output
* -1 => failed -- see errno
*
* Sets lc->writing if write does not complete
*
* NB: when says "all done" (or failed) the caller may release all buffered
* material that has been accepted by vio_lc_append() -- and not before.
*/
extern int
vio_lc_write_nb(int fd, vio_line_control lc)
{
int ret ;
ret = qiovec_write_nb(fd, lc->qiov) ;
if (ret <= 0)
{
/* About to promise that all buffered material previously accepted by
* vio_lc_append() may now be released.
*
* Unfortunately, if we have any line fragments in hand, have to buffer
* those locally, now.
*
* NB: if have a particularly long original line, or a particularly
* narrow or short screen, it is possible to pass through here
* more than once for the same original line.
*
* In this obscure case, the line will already be buffered in
* lc->here. Happily s_set_len_nn() does not disturb the body of
* the qstring, and qs_append_n will append from within its own
* body !
*/
if (!qiovec_empty(lc->fragments))
{
qs_set_len_nn(lc->here, 0) ; /* ready to append stuff */
do
{
qiov_item_t item ;
qiovec_shift(lc->fragments, item) ;
qs_append_n(lc->here, item->base, item->len) ;
}
while (!qiovec_empty(lc->fragments)) ;
/* One fragment in hand, collected from all previous fragments
*/
qiovec_push_this(lc->fragments, qs_char(lc->here), qs_len(lc->here)) ;
} ;
} ;
return ret ;
} ;
|