1 /*
2  * Copyright (c) 2006-2023, RT-Thread Development Team
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  *
6  * Change Logs:
7  * Date           Author       Notes
8  * 2023-11-13     Shell        init ver.
9  */
10 
11 #include "../bsd_porting.h"
12 #include "../terminal.h"
13 
14 /*-
15  * SPDX-License-Identifier: BSD-2-Clause
16  *
17  * Copyright (c) 2008 Ed Schouten <ed@FreeBSD.org>
18  * All rights reserved.
19  *
20  * Portions of this software were developed under sponsorship from Snow
21  * B.V., the Netherlands.
22  *
23  * Redistribution and use in source and binary forms, with or without
24  * modification, are permitted provided that the following conditions
25  * are met:
26  * 1. Redistributions of source code must retain the above copyright
27  *    notice, this list of conditions and the following disclaimer.
28  * 2. Redistributions in binary form must reproduce the above copyright
29  *    notice, this list of conditions and the following disclaimer in the
30  *    documentation and/or other materials provided with the distribution.
31  *
32  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
33  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
34  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
35  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
36  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
37  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
38  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
39  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
40  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
41  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
42  * SUCH DAMAGE.
43  */
44 
45 /*
46  * TTY input queue buffering.
47  *
48  * Unlike the output queue, the input queue has more features that are
49  * needed to properly implement various features offered by the TTY
50  * interface:
51  *
52  * - Data can be removed from the tail of the queue, which is used to
53  *   implement backspace.
54  * - Once in a while, input has to be `canonicalized'. When ICANON is
55  *   turned on, this will be done after a CR has been inserted.
56  *   Otherwise, it should be done after any character has been inserted.
57  * - The input queue can store one bit per byte, called the quoting bit.
58  *   This bit is used by TTYDISC to make backspace work on quoted
59  *   characters.
60  *
61  * In most cases, there is probably less input than output, so unlike
62  * the outq, we'll stick to 128 byte blocks here.
63  */
64 
65 static int ttyinq_flush_secure = 1;
66 
67 #define TTYINQ_QUOTESIZE (TTYINQ_DATASIZE / BMSIZE)
68 #define BMSIZE           32
69 #define GETBIT(tib, boff) \
70     ((tib)->tib_quotes[(boff) / BMSIZE] & (1 << ((boff) % BMSIZE)))
71 #define SETBIT(tib, boff) \
72     ((tib)->tib_quotes[(boff) / BMSIZE] |= (1 << ((boff) % BMSIZE)))
73 #define CLRBIT(tib, boff) \
74     ((tib)->tib_quotes[(boff) / BMSIZE] &= ~(1 << ((boff) % BMSIZE)))
75 
76 struct ttyinq_block
77 {
78     struct ttyinq_block *tib_prev;
79     struct ttyinq_block *tib_next;
80     uint32_t tib_quotes[TTYINQ_QUOTESIZE];
81     char tib_data[TTYINQ_DATASIZE];
82 };
83 
84 static uma_zone_t ttyinq_zone;
85 
86 #define TTYINQ_INSERT_TAIL(ti, tib)                     \
87     do                                                  \
88     {                                                   \
89         if (ti->ti_end == 0)                            \
90         {                                               \
91             tib->tib_prev = NULL;                       \
92             tib->tib_next = ti->ti_firstblock;          \
93             ti->ti_firstblock = tib;                    \
94         }                                               \
95         else                                            \
96         {                                               \
97             tib->tib_prev = ti->ti_lastblock;           \
98             tib->tib_next = ti->ti_lastblock->tib_next; \
99             ti->ti_lastblock->tib_next = tib;           \
100         }                                               \
101         if (tib->tib_next != NULL)                      \
102             tib->tib_next->tib_prev = tib;              \
103         ti->ti_nblocks++;                               \
104     } while (0)
105 
106 #define TTYINQ_REMOVE_HEAD(ti)                           \
107     do                                                   \
108     {                                                    \
109         ti->ti_firstblock = ti->ti_firstblock->tib_next; \
110         if (ti->ti_firstblock != NULL)                   \
111             ti->ti_firstblock->tib_prev = NULL;          \
112         ti->ti_nblocks--;                                \
113     } while (0)
114 
115 #define TTYINQ_RECYCLE(ti, tib)             \
116     do                                      \
117     {                                       \
118         if (ti->ti_quota <= ti->ti_nblocks) \
119             uma_zfree(ttyinq_zone, tib);    \
120         else                                \
121             TTYINQ_INSERT_TAIL(ti, tib);    \
122     } while (0)
123 
ttyinq_setsize(struct ttyinq * ti,struct lwp_tty * tp,size_t size)124 int ttyinq_setsize(struct ttyinq *ti, struct lwp_tty *tp, size_t size)
125 {
126     struct ttyinq_block *tib;
127 
128     ti->ti_quota = howmany(size, TTYINQ_DATASIZE);
129 
130     while (ti->ti_quota > ti->ti_nblocks)
131     {
132         /*
133          * List is getting bigger.
134          * Add new blocks to the tail of the list.
135          *
136          * We must unlock the TTY temporarily, because we need
137          * to allocate memory. This won't be a problem, because
138          * in the worst case, another thread ends up here, which
139          * may cause us to allocate too many blocks, but this
140          * will be caught by the loop below.
141          */
142         tty_unlock(tp);
143         tib = uma_zalloc(ttyinq_zone, M_WAITOK);
144         tty_lock(tp);
145 
146         if (tty_gone(tp))
147         {
148             uma_zfree(ttyinq_zone, tib);
149             return -ENXIO;
150         }
151 
152         TTYINQ_INSERT_TAIL(ti, tib);
153     }
154     return 0;
155 }
156 
ttyinq_free(struct ttyinq * ti)157 void ttyinq_free(struct ttyinq *ti)
158 {
159     struct ttyinq_block *tib;
160 
161     ttyinq_flush(ti);
162     ti->ti_quota = 0;
163 
164     while ((tib = ti->ti_firstblock) != NULL)
165     {
166         TTYINQ_REMOVE_HEAD(ti);
167         uma_zfree(ttyinq_zone, tib);
168     }
169 
170     MPASS(ti->ti_nblocks == 0);
171 }
172 
ttyinq_read_uio(struct ttyinq * ti,struct lwp_tty * tp,struct uio * uio,size_t rlen,size_t flen)173 int ttyinq_read_uio(struct ttyinq *ti, struct lwp_tty *tp, struct uio *uio,
174                     size_t rlen, size_t flen)
175 {
176     MPASS(rlen <= uio->uio_resid);
177 
178     while (rlen > 0)
179     {
180         int error;
181         struct ttyinq_block *tib;
182         size_t cbegin, cend, clen;
183 
184         /* See if there still is data. */
185         if (ti->ti_begin == ti->ti_linestart)
186             return 0;
187         tib = ti->ti_firstblock;
188         if (tib == NULL)
189             return 0;
190 
191         /*
192          * The end address should be the lowest of these three:
193          * - The write pointer
194          * - The blocksize - we can't read beyond the block
195          * - The end address if we could perform the full read
196          */
197         cbegin = ti->ti_begin;
198         cend = MIN(MIN(ti->ti_linestart, ti->ti_begin + rlen), TTYINQ_DATASIZE);
199         clen = cend - cbegin;
200         MPASS(clen >= flen);
201         rlen -= clen;
202 
203         /*
204          * We can prevent buffering in some cases:
205          * - We need to read the block until the end.
206          * - We don't need to read the block until the end, but
207          *   there is no data beyond it, which allows us to move
208          *   the write pointer to a new block.
209          */
210         if (cend == TTYINQ_DATASIZE || cend == ti->ti_end)
211         {
212             /*
213              * Fast path: zero copy. Remove the first block,
214              * so we can unlock the TTY temporarily.
215              */
216             TTYINQ_REMOVE_HEAD(ti);
217             ti->ti_begin = 0;
218 
219             /*
220              * Because we remove the first block, we must
221              * fix up the block offsets.
222              */
223 #define CORRECT_BLOCK(t)          \
224     do                            \
225     {                             \
226         if (t <= TTYINQ_DATASIZE) \
227             t = 0;                \
228         else                      \
229             t -= TTYINQ_DATASIZE; \
230     } while (0)
231             CORRECT_BLOCK(ti->ti_linestart);
232             CORRECT_BLOCK(ti->ti_reprint);
233             CORRECT_BLOCK(ti->ti_end);
234 #undef CORRECT_BLOCK
235 
236             /*
237              * Temporary unlock and copy the data to
238              * userspace. We may need to flush trailing
239              * bytes, like EOF characters.
240              */
241             tty_unlock(tp);
242             error = uiomove(tib->tib_data + cbegin, clen - flen, uio);
243             tty_lock(tp);
244 
245             /* Block can now be readded to the list. */
246             TTYINQ_RECYCLE(ti, tib);
247         }
248         else
249         {
250             char ob[TTYINQ_DATASIZE - 1];
251 
252             /*
253              * Slow path: store data in a temporary buffer.
254              */
255             memcpy(ob, tib->tib_data + cbegin, clen - flen);
256             ti->ti_begin += clen;
257             MPASS(ti->ti_begin < TTYINQ_DATASIZE);
258 
259             /* Temporary unlock and copy the data to userspace. */
260             tty_unlock(tp);
261             error = uiomove(ob, clen - flen, uio);
262             tty_lock(tp);
263         }
264 
265         if (error != 0)
266             return error;
267         if (tty_gone(tp))
268             return -ENXIO;
269     }
270 
271     return 0;
272 }
273 
ttyinq_set_quotes(struct ttyinq_block * tib,size_t offset,size_t length,int value)274 rt_inline void ttyinq_set_quotes(struct ttyinq_block *tib, size_t offset,
275                                  size_t length, int value)
276 {
277     if (value)
278     {
279         /* Set the bits. */
280         for (; length > 0; length--, offset++) SETBIT(tib, offset);
281     }
282     else
283     {
284         /* Unset the bits. */
285         for (; length > 0; length--, offset++) CLRBIT(tib, offset);
286     }
287 }
288 
ttyinq_write(struct ttyinq * ti,const void * buf,size_t nbytes,int quote)289 size_t ttyinq_write(struct ttyinq *ti, const void *buf, size_t nbytes,
290                     int quote)
291 {
292     const char *cbuf = buf;
293     struct ttyinq_block *tib;
294     unsigned int boff;
295     size_t l;
296 
297     while (nbytes > 0)
298     {
299         boff = ti->ti_end % TTYINQ_DATASIZE;
300 
301         if (ti->ti_end == 0)
302         {
303             /* First time we're being used or drained. */
304             MPASS(ti->ti_begin == 0);
305             tib = ti->ti_firstblock;
306             if (tib == NULL)
307             {
308                 /* Queue has no blocks. */
309                 break;
310             }
311             ti->ti_lastblock = tib;
312         }
313         else if (boff == 0)
314         {
315             /* We reached the end of this block on last write. */
316             tib = ti->ti_lastblock->tib_next;
317             if (tib == NULL)
318             {
319                 /* We've reached the watermark. */
320                 break;
321             }
322             ti->ti_lastblock = tib;
323         }
324         else
325         {
326             tib = ti->ti_lastblock;
327         }
328 
329         /* Don't copy more than was requested. */
330         l = MIN(nbytes, TTYINQ_DATASIZE - boff);
331         MPASS(l > 0);
332         memcpy(tib->tib_data + boff, cbuf, l);
333 
334         /* Set the quoting bits for the proper region. */
335         ttyinq_set_quotes(tib, boff, l, quote);
336 
337         cbuf += l;
338         nbytes -= l;
339         ti->ti_end += l;
340     }
341 
342     return (cbuf - (const char *)buf);
343 }
344 
ttyinq_write_nofrag(struct ttyinq * ti,const void * buf,size_t nbytes,int quote)345 int ttyinq_write_nofrag(struct ttyinq *ti, const void *buf, size_t nbytes,
346                         int quote)
347 {
348     size_t ret __unused;
349 
350     if (ttyinq_bytesleft(ti) < nbytes)
351         return -1;
352 
353     /* We should always be able to write it back. */
354     ret = ttyinq_write(ti, buf, nbytes, quote);
355     MPASS(ret == nbytes);
356 
357     return 0;
358 }
359 
ttyinq_canonicalize(struct ttyinq * ti)360 void ttyinq_canonicalize(struct ttyinq *ti)
361 {
362     ti->ti_linestart = ti->ti_reprint = ti->ti_end;
363     ti->ti_startblock = ti->ti_reprintblock = ti->ti_lastblock;
364 }
365 
ttyinq_findchar(struct ttyinq * ti,const char * breakc,size_t maxlen,char * lastc)366 size_t ttyinq_findchar(struct ttyinq *ti, const char *breakc, size_t maxlen,
367                        char *lastc)
368 {
369     struct ttyinq_block *tib = ti->ti_firstblock;
370     unsigned int boff = ti->ti_begin;
371     unsigned int bend =
372         MIN(MIN(TTYINQ_DATASIZE, ti->ti_linestart), ti->ti_begin + maxlen);
373 
374     MPASS(maxlen > 0);
375 
376     if (tib == NULL)
377         return 0;
378 
379     while (boff < bend)
380     {
381         if (strchr(breakc, tib->tib_data[boff]) && !GETBIT(tib, boff))
382         {
383             *lastc = tib->tib_data[boff];
384             return (boff - ti->ti_begin + 1);
385         }
386         boff++;
387     }
388 
389     /* Not found - just process the entire block. */
390     return (bend - ti->ti_begin);
391 }
392 
ttyinq_flush(struct ttyinq * ti)393 void ttyinq_flush(struct ttyinq *ti)
394 {
395     struct ttyinq_block *tib;
396 
397     ti->ti_begin = 0;
398     ti->ti_linestart = 0;
399     ti->ti_reprint = 0;
400     ti->ti_end = 0;
401 
402     /* Zero all data in the input queue to get rid of passwords. */
403     if (ttyinq_flush_secure)
404     {
405         for (tib = ti->ti_firstblock; tib != NULL; tib = tib->tib_next)
406             memset(&tib->tib_data, 0, sizeof tib->tib_data);
407     }
408 }
409 
ttyinq_peekchar(struct ttyinq * ti,char * c,int * quote)410 int ttyinq_peekchar(struct ttyinq *ti, char *c, int *quote)
411 {
412     unsigned int boff;
413     struct ttyinq_block *tib = ti->ti_lastblock;
414 
415     if (ti->ti_linestart == ti->ti_end)
416         return -1;
417 
418     MPASS(ti->ti_end > 0);
419     boff = (ti->ti_end - 1) % TTYINQ_DATASIZE;
420 
421     *c = tib->tib_data[boff];
422     *quote = GETBIT(tib, boff);
423 
424     return 0;
425 }
426 
ttyinq_unputchar(struct ttyinq * ti)427 void ttyinq_unputchar(struct ttyinq *ti)
428 {
429     MPASS(ti->ti_linestart < ti->ti_end);
430 
431     if (--ti->ti_end % TTYINQ_DATASIZE == 0)
432     {
433         /* Roll back to the previous block. */
434         ti->ti_lastblock = ti->ti_lastblock->tib_prev;
435         /*
436          * This can only fail if we are unputchar()'ing the
437          * first character in the queue.
438          */
439         MPASS((ti->ti_lastblock == NULL) == (ti->ti_end == 0));
440     }
441 }
442 
ttyinq_reprintpos_set(struct ttyinq * ti)443 void ttyinq_reprintpos_set(struct ttyinq *ti)
444 {
445     ti->ti_reprint = ti->ti_end;
446     ti->ti_reprintblock = ti->ti_lastblock;
447 }
448 
ttyinq_reprintpos_reset(struct ttyinq * ti)449 void ttyinq_reprintpos_reset(struct ttyinq *ti)
450 {
451     ti->ti_reprint = ti->ti_linestart;
452     ti->ti_reprintblock = ti->ti_startblock;
453 }
454 
ttyinq_line_iterate(struct ttyinq * ti,ttyinq_line_iterator_t * iterator,void * data,unsigned int offset,struct ttyinq_block * tib)455 static void ttyinq_line_iterate(struct ttyinq *ti,
456                                 ttyinq_line_iterator_t *iterator, void *data,
457                                 unsigned int offset, struct ttyinq_block *tib)
458 {
459     unsigned int boff;
460 
461     /* Use the proper block when we're at the queue head. */
462     if (offset == 0)
463         tib = ti->ti_firstblock;
464 
465     /* Iterate all characters and call the iterator function. */
466     for (; offset < ti->ti_end; offset++)
467     {
468         boff = offset % TTYINQ_DATASIZE;
469         MPASS(tib != NULL);
470 
471         /* Call back the iterator function. */
472         iterator(data, tib->tib_data[boff], GETBIT(tib, boff));
473 
474         /* Last byte iterated - go to the next block. */
475         if (boff == TTYINQ_DATASIZE - 1)
476             tib = tib->tib_next;
477     }
478 }
479 
ttyinq_line_iterate_from_linestart(struct ttyinq * ti,ttyinq_line_iterator_t * iterator,void * data)480 void ttyinq_line_iterate_from_linestart(struct ttyinq *ti,
481                                         ttyinq_line_iterator_t *iterator,
482                                         void *data)
483 {
484     ttyinq_line_iterate(ti, iterator, data, ti->ti_linestart,
485                         ti->ti_startblock);
486 }
487 
ttyinq_line_iterate_from_reprintpos(struct ttyinq * ti,ttyinq_line_iterator_t * iterator,void * data)488 void ttyinq_line_iterate_from_reprintpos(struct ttyinq *ti,
489                                          ttyinq_line_iterator_t *iterator,
490                                          void *data)
491 {
492     ttyinq_line_iterate(ti, iterator, data, ti->ti_reprint,
493                         ti->ti_reprintblock);
494 }
495 
ttyinq_startup(void)496 static int ttyinq_startup(void)
497 {
498     ttyinq_zone = uma_zcreate("ttyinq", sizeof(struct ttyinq_block), NULL, NULL,
499                               NULL, NULL, UMA_ALIGN_PTR, 0);
500     return 0;
501 }
502 INIT_PREV_EXPORT(ttyinq_startup);
503 
504 #if 0
505 
506 SYSINIT(ttyinq, SI_SUB_DRIVERS, SI_ORDER_FIRST, ttyinq_startup, NULL);
507 #endif
508