1 // Copyright 2020 Michael Reilly (mreilly@resiliware.com).
2 //
3 // Redistribution and use in source and binary forms, with or without
4 // modification, are permitted provided that the following conditions
5 // are met:
6 // 1. Redistributions of source code must retain the above copyright
7 //    notice, this list of conditions and the following disclaimer.
8 // 2. Redistributions in binary form must reproduce the above copyright
9 //    notice, this list of conditions and the following disclaimer in the
10 //    documentation and/or other materials provided with the distribution.
11 // 3. Neither the names of the copyright holders nor the names of the
12 //    contributors may be used to endorse or promote products derived from
13 //    this software without specific prior written permission.
14 //
15 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16 // ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
18 // PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
19 // OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20 // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 
27 #define SRCNAME "hexpeek_console.c"
28 
29 #include <hexpeek.h>
30 
31 #include <stdlib.h>
32 #include <stdarg.h>
33 #include <string.h>
34 #include <errno.h>
35 
36 #ifdef HEXPEEK_EDITABLE_CONSOLE
37 
38 #include <histedit.h>
39 
40 static EditLine *ConsoleInfo = NULL;
41 
42 static History *ConsoleHistory = NULL;
43 
consolePrompt(void * UNUSED)44 static char *consolePrompt(void *UNUSED)
45 {
46     return PromptString;
47 }
48 
49 #endif
50 
consoleInit()51 void consoleInit()
52 {
53 #ifdef HEXPEEK_EDITABLE_CONSOLE
54     if(Params.editable_console)
55     {
56         HistEvent UNUSED;
57 
58         ConsoleHistory = history_init();
59         assert(ConsoleHistory);
60         assert(history(ConsoleHistory, &UNUSED, H_SETSIZE, 64) >= 0);
61         assert(history(ConsoleHistory, &UNUSED, H_SETUNIQUE, 1) >= 0);
62 
63         ConsoleInfo = el_init(PRGNM, stdin, stdout, stderr);
64         assert(ConsoleInfo);
65         assert(el_set(ConsoleInfo, EL_EDITOR, "emacs") == 0);
66         assert(el_set(ConsoleInfo, EL_EDITMODE, 1) == 0);
67         assert(el_set(ConsoleInfo, EL_PROMPT, consolePrompt) == 0);
68         assert(el_set(ConsoleInfo, EL_SIGNAL, 1) == 0);
69         assert(el_set(ConsoleInfo, EL_HIST, history, ConsoleHistory) == 0);
70 
71         el_source(ConsoleInfo, NULL);                      // @!!IGNORE_RESULT
72         int editable = 0;
73         if(el_get(ConsoleInfo, EL_EDITMODE, &editable) == 0 && editable == 0)
74             consoleClose();
75         else
76             assert(atexit(consoleClose) == 0);
77     }
78 #endif
79 }
80 
consoleClose()81 void consoleClose()
82 {
83 #ifdef HEXPEEK_EDITABLE_CONSOLE
84     if(ConsoleInfo)
85     {
86         el_end(ConsoleInfo);
87         ConsoleInfo = NULL;
88     }
89     if(ConsoleHistory)
90     {
91         history_end(ConsoleHistory);
92         ConsoleHistory = NULL;
93     }
94 #endif
95 }
96 
consoleFlush()97 void consoleFlush()
98 {
99     assert(fflush(stdout) == 0);
100 }
101 
consoleIn()102 char *consoleIn()
103 {
104     if(interactive())
105     {
106 #ifdef HEXPEEK_EDITABLE_CONSOLE
107         if(ConsoleInfo)
108         {
109             HistEvent UNUSED;
110             int count = 0;
111             char const *result = el_gets(ConsoleInfo, &count);
112             if(result == NULL || count <= 0)
113                 return NULL;
114             assert(history(ConsoleHistory, &UNUSED, H_ENTER, result) >= 0);
115             // Copy result because el_gets() returns a const string
116             count++; // for '\0'
117             if(LnInputSz < count)
118             {
119                 if(LnInput_mal)
120                     free(LnInput_mal);
121                 assert(LnInputSz > 0);
122                 for( ; LnInputSz < count; LnInputSz *= 2);
123                 LnInput_mal = Malloc(LnInputSz * sizeof *LnInput_mal);
124             }
125             assert(LnInput_mal);
126             strncpy(LnInput_mal, result, LnInputSz);
127             LnInput_mal[LnInputSz - 1] = '\0';
128             return LnInput_mal;
129         }
130 #endif
131         console("%s", PromptString);
132         consoleFlush();
133     }
134 
135     if(getline(&LnInput_mal, &LnInputSz, stdin) <= 0)
136         return NULL;
137     return LnInput_mal;
138 }
139 
140 #define VARIADIC_PRINTF(s, f) \
141     va_list vl; \
142     va_start(vl, (s)); \
143     if(vprintf((f), vl) < 0) \
144     { \
145         prerr("error writing output, aborting\n"); \
146         terminate(RC_CRIT); \
147     } \
148     va_end(vl);
149 
150 #define VARIADIC_EXTRA_PRINTF(f) \
151     char changed[strlen((f)) + 1]; \
152     hexpeek_genf(changed, (f)); \
153     VARIADIC_PRINTF((f), changed);
154 
155 // Handles ordinary format strings, but not the PRI_hxpkint format identifier
console(char const * format,...)156 void console(char const *format, ...)
157 {
158     VARIADIC_PRINTF(format, format);
159 }
160 
161 // Out must be at least as long (including '\0') as in
hexpeek_genf(char * out,char const * in)162 void hexpeek_genf(char *out, char const *in)
163 {
164     char *cur = out;
165     char const *last = in;
166     for(char const *ptr = in;
167         (ptr = strstr(ptr, PRI_hxpkint)) != NULL;
168         last = ptr)
169     {
170         strncpy(cur, last, ptr - last);
171         cur += (ptr - last);
172         *(cur++) = (Params.hexlower ? 'x' : 'X');
173         ptr += strlen(PRI_hxpkint);
174     }
175     strcpy(cur, last);
176 }
177 
178 // This function can handle any format string, but it is only used for strings
179 // requiring processing of PRI_hxpkint due to the performance degradation
180 // resulting from the copying of the format string.
consoleOutf(char const * format,...)181 void consoleOutf(char const *format, ...)
182 {
183     VARIADIC_EXTRA_PRINTF(format);
184 }
185 
186 // Prompt the user for a response
consoleAsk(char const * format,...)187 int consoleAsk(char const *format, ...)
188 {
189     VARIADIC_EXTRA_PRINTF(format);
190     console(" ('y' or 'n')? ");
191     consoleFlush();
192     if(interactive())
193     {
194         if(getline(&LnInput_mal, &LnInputSz, stdin) <= 0)
195             return 1;
196         stripTrailingSpaces(LnInput_mal);
197         return strcmp(LnInput_mal, "y");
198     }
199     return 1;
200 }
201