1 /*
2   Copyright 2021 Northern.tech AS
3 
4   This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5 
6   This program is free software; you can redistribute it and/or modify it
7   under the terms of the GNU General Public License as published by the
8   Free Software Foundation; version 3.
9 
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14 
15   You should have received a copy of the GNU General Public License
16   along with this program; if not, write to the Free Software
17   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
18 
19   To the extent this program is licensed as part of the Enterprise
20   versions of CFEngine, the applicable Commercial Open Source License
21   (COSL) may apply to this file if you as a licensee so wish it. See
22   included file COSL.txt.
23 */
24 
25 #include <platform.h>
26 #include <writer.h>
27 
28 #include <misc_lib.h>
29 #include <alloc.h>
30 
31 typedef enum
32 {
33     WT_STRING,
34     WT_FILE,
35 } WriterType;
36 
37 typedef struct
38 {
39     char *data;
40     size_t len;                 /* Does not include trailing zero */
41     size_t allocated;           /* Includes trailing zero */
42 } StringWriterImpl;
43 
44 struct Writer_
45 {
46     WriterType type;
47     union
48     {
49         StringWriterImpl string;
50         FILE *file;
51     };
52 };
53 
54 /*********************************************************************/
55 
FileWriter(FILE * file)56 Writer *FileWriter(FILE *file)
57 {
58     Writer *writer = xcalloc(1, sizeof(Writer));
59 
60     writer->type = WT_FILE;
61     writer->file = file;
62     return writer;
63 }
64 
65 /*********************************************************************/
66 
StringWriter(void)67 Writer *StringWriter(void)
68 {
69     Writer *writer = xcalloc(1, sizeof(Writer));
70 
71     writer->type = WT_STRING;
72     writer->string.data = xstrdup("");
73     writer->string.allocated = 1;
74     writer->string.len = 0;
75     return writer;
76 }
77 
78 /*********************************************************************/
79 
StringWriterReallocate(Writer * writer,size_t extra_length)80 static void StringWriterReallocate(Writer *writer, size_t extra_length)
81 {
82     assert(writer != NULL);
83     writer->string.allocated = MAX(writer->string.allocated * 2, writer->string.len + extra_length + 1);
84     writer->string.data = xrealloc(writer->string.data, writer->string.allocated);
85 }
86 
StringWriterWriteChar(Writer * writer,char c)87 static size_t StringWriterWriteChar(Writer *writer, char c)
88 {
89     assert(writer != NULL);
90     if (writer->string.len + 2 > writer->string.allocated)
91     {
92         StringWriterReallocate(writer, 2);
93     }
94 
95     writer->string.data[writer->string.len] = c;
96     writer->string.data[writer->string.len + 1] = '\0';
97     writer->string.len++;
98 
99     return 1;
100 }
101 
StringWriterWriteLen(Writer * writer,const char * str,size_t len_)102 static size_t StringWriterWriteLen(Writer *writer, const char *str, size_t len_)
103 {
104     assert(writer != NULL);
105     /* NB: str[:len_] may come from read(), which hasn't '\0'-terminated */
106     size_t len = strnlen(str, len_);
107 
108     if (writer->string.len + len + 1 > writer->string.allocated)
109     {
110         StringWriterReallocate(writer, len);
111     }
112 
113     memcpy(writer->string.data + writer->string.len, str, len);
114     writer->string.data[writer->string.len + len] = '\0';
115     writer->string.len += len;
116 
117     return len;
118 }
119 
120 /*********************************************************************/
121 
FileWriterWriteF(Writer * writer,const char * fmt,va_list ap)122 static size_t FileWriterWriteF(Writer *writer, const char *fmt, va_list ap)
123 {
124     assert(writer != NULL);
125     return vfprintf(writer->file, fmt, ap);
126 }
127 
128 /*********************************************************************/
129 
FileWriterWriteLen(Writer * writer,const char * str,size_t len_)130 static size_t FileWriterWriteLen(Writer *writer, const char *str, size_t len_)
131 {
132     assert(writer != NULL);
133     size_t len = strnlen(str, len_);
134 
135 #ifdef CFENGINE_TEST
136     return CFENGINE_TEST_fwrite(str, 1, len, writer->file);
137 #else
138     return fwrite(str, 1, len, writer->file);
139 #endif
140 }
141 
142 /*********************************************************************/
143 
WriterWriteF(Writer * writer,const char * fmt,...)144 size_t WriterWriteF(Writer *writer, const char *fmt, ...)
145 {
146     va_list ap;
147 
148     va_start(ap, fmt);
149     size_t size = WriterWriteVF(writer, fmt, ap);
150 
151     va_end(ap);
152     return size;
153 }
154 
155 /*********************************************************************/
156 
WriterWriteVF(Writer * writer,const char * fmt,va_list ap)157 size_t WriterWriteVF(Writer *writer, const char *fmt, va_list ap)
158 {
159     assert(writer != NULL);
160     if (writer->type == WT_STRING)
161     {
162         char *str = NULL;
163 
164         xvasprintf(&str, fmt, ap);
165         size_t size = StringWriterWriteLen(writer, str, INT_MAX);
166 
167         free(str);
168         return size;
169     }
170     else
171     {
172         return FileWriterWriteF(writer, fmt, ap);
173     }
174 }
175 
176 /*********************************************************************/
177 
WriterWriteLen(Writer * writer,const char * str,size_t len)178 size_t WriterWriteLen(Writer *writer, const char *str, size_t len)
179 {
180     assert(writer != NULL);
181     if (writer->type == WT_STRING)
182     {
183         return StringWriterWriteLen(writer, str, len);
184     }
185     else
186     {
187         return FileWriterWriteLen(writer, str, len);
188     }
189 }
190 
191 /*********************************************************************/
192 
WriterWrite(Writer * writer,const char * str)193 size_t WriterWrite(Writer *writer, const char *str)
194 {
195     return WriterWriteLen(writer, str, INT_MAX);
196 }
197 
198 /*********************************************************************/
199 
WriterWriteChar(Writer * writer,char c)200 size_t WriterWriteChar(Writer *writer, char c)
201 {
202     assert(writer != NULL);
203     if (writer->type == WT_STRING)
204     {
205         return StringWriterWriteChar(writer, c);
206     }
207     else
208     {
209         char s[2] = { c, '\0' };
210         return FileWriterWriteLen(writer, s, 1);
211     }
212 }
213 
214 /*********************************************************************/
215 
StringWriterLength(const Writer * writer)216 size_t StringWriterLength(const Writer *writer)
217 {
218     assert(writer != NULL);
219     if (writer->type != WT_STRING)
220     {
221         ProgrammingError("Wrong writer type");
222     }
223 
224     return writer->string.len;
225 }
226 
227 /*********************************************************************/
228 
StringWriterData(const Writer * writer)229 const char *StringWriterData(const Writer *writer)
230 {
231     assert(writer != NULL);
232     if (writer->type != WT_STRING)
233     {
234         ProgrammingError("Wrong writer type");
235     }
236 
237     return writer->string.data;
238 }
239 
240 /*********************************************************************/
241 
WriterClose(Writer * writer)242 void WriterClose(Writer *writer)
243 {
244     assert(writer != NULL);
245     if (writer->type == WT_STRING)
246     {
247         free(writer->string.data);
248     }
249     else
250     {
251 #ifdef CFENGINE_TEST
252         CFENGINE_TEST_fclose(writer->file);
253 #else
254         fclose(writer->file);
255 #endif
256     }
257     free(writer);
258 }
259 
260 /*********************************************************************/
261 
StringWriterClose(Writer * writer)262 char *StringWriterClose(Writer *writer)
263 // NOTE: transfer of ownership for allocated return value
264 {
265     assert(writer != NULL);
266 
267     if (writer->type != WT_STRING)
268     {
269         ProgrammingError("Wrong writer type");
270     }
271     char *data = writer->string.data;
272 
273     free(writer);
274     return data;
275 }
276 
FileWriterDetach(Writer * writer)277 FILE *FileWriterDetach(Writer *writer)
278 {
279     assert(writer != NULL);
280     if (writer->type != WT_FILE)
281     {
282         ProgrammingError("Wrong writer type");
283     }
284     FILE *file = writer->file;
285     free(writer);
286     return file;
287 }
288 
WriterWriteOptions(Writer * w,const struct option options[],const char * const hints[])289 static void WriterWriteOptions(Writer *w, const struct option options[],
290                                const char *const hints[])
291 {
292     WriterWriteF(w, "\nOptions:\n");
293     for (int i = 0; options[i].name != NULL; i++)
294     {
295         char short_option[] = ", -*";
296         if (options[i].val < 128)
297         {
298             // Within ASCII range, means there is a short option.
299             short_option[3] = options[i].val;
300         }
301         else
302         {
303             // No short option.
304             short_option[0] = '\0';
305         }
306         if (options[i].has_arg)
307         {
308             WriterWriteF(w, "  --%-12s%s value - %s\n", options[i].name,
309                          short_option, hints[i]);
310         }
311         else
312         {
313             WriterWriteF(w, "  --%-12s%-10s - %s\n", options[i].name,
314                          short_option, hints[i]);
315         }
316     }
317 }
318 
WriterWriteCommands(Writer * w,const Description * commands)319 static void WriterWriteCommands(Writer *w, const Description *commands)
320 {
321     assert(commands != NULL);
322     WriterWriteF(w, "\nCommands:\n");
323     for (int i = 0; commands[i].name != NULL; i++)
324     {
325         WriterWriteF(w, "  %-12s - %s.\n", commands[i].name,
326                                            commands[i].description);
327         WriterWriteF(w, "  %-12s   Usage: %s\n", "", commands[i].usage);
328     }
329 }
330 
WriterWriteHelp(Writer * w,const char * component,const struct option options[],const char * const hints[],const Description * commands,bool command_first,bool accepts_file_argument)331 void WriterWriteHelp(Writer *w, const char *component,
332                      const struct option options[], const char *const hints[],
333                      const Description *commands, bool command_first,
334                      bool accepts_file_argument)
335 {
336     WriterWriteF(w, "Usage: %s%s [OPTIONS]%s%s\n", component,
337                  (commands && command_first) ? " COMMAND" : "",
338                  (commands && !command_first) ? " COMMAND" : "",
339                  accepts_file_argument ? " [FILE]" : "");
340     if ((commands != NULL) && command_first)
341     {
342         WriterWriteCommands(w, commands);
343     }
344     WriterWriteOptions(w, options, hints);
345     if ((commands != NULL) && !command_first)
346     {
347         WriterWriteCommands(w, commands);
348     }
349     WriterWriteF(w, "\nWebsite: http://www.cfengine.com\n");
350     WriterWriteF(w, "This software is Copyright (C) "
351                     "2008,2010-present Northern.tech AS.\n");
352 }
353