1 /*
2 * Copyright © 2024 Intel Corporation
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 * IN THE SOFTWARE.
22 */
23
24 #include <assert.h>
25 #include <ctype.h>
26 #include <dirent.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <stdarg.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <errno.h>
33
34 #include "screenshot_params.h"
35
36 #include "util/os_socket.h"
37
38 enum LogType LOG_TYPE = REQUIRED;
39
print_log_type(enum LogType log_type)40 static const char *print_log_type(enum LogType log_type) {
41 switch(log_type)
42 {
43 case(DEBUG):
44 return "DEBUG";
45 case(ERROR):
46 return "ERROR";
47 case(INFO):
48 return "INFO";
49 case(NO_PREFIX):
50 return "NO_PREFIX";
51 case(REQUIRED):
52 return "REQUIRED";
53 case(WARN):
54 return "WARN";
55 default:
56 /* Don't show log type*/
57 return "";
58 }
59 }
60
LOG(enum LogType log_type,const char * format,...)61 void LOG(enum LogType log_type, const char *format, ...) {
62 FILE *file_type;
63 va_list args;
64 if (log_type == WARN || log_type == ERROR) {
65 file_type = stderr;
66 } else {
67 file_type = stdout;
68 }
69 if (log_type == DEBUG && LOG_TYPE != DEBUG) {
70 return;
71 } else if (log_type == INFO && (LOG_TYPE != INFO && LOG_TYPE != DEBUG)) {
72 return;
73 }
74 if (log_type != NO_PREFIX)
75 fprintf(file_type, "mesa-screenshot: %s: ", print_log_type(log_type));
76 va_start(args, format);
77 vfprintf(file_type, format, args);
78 va_end(args);
79 }
80
81 static const char *
parse_control(const char * str)82 parse_control(const char *str)
83 {
84 static char control_str[64];
85 if (strlen(str) > 63) {
86 LOG(ERROR, "control string too long. Must be < 64 chars\n");
87 return NULL;
88 }
89 strcpy(control_str, str);
90
91 return control_str;
92 }
93
94 /* Inserts frame nodes in ascending order */
insert_frame(struct frame_list * list,uint32_t new_frame_num)95 static void insert_frame(struct frame_list *list, uint32_t new_frame_num)
96 {
97 struct frame_node *new_node, *curr, *next;
98 new_node = (struct frame_node*)malloc(sizeof(struct frame_node));
99 new_node->frame_num = new_frame_num;
100 new_node->next = NULL;
101 curr = list->head;
102
103 /* Empty list */
104 if (list->head == NULL)
105 list->head = new_node;
106 /* Insert as new head of list */
107 else if (list->head->frame_num > new_frame_num) {
108 list->head = new_node;
109 new_node->next = curr;
110 /* Traverse list & insert frame number in correct, ascending location */
111 } else {
112 while (curr != NULL) {
113 if (curr->frame_num == new_frame_num) {
114 free(new_node);
115 return; // Avoid inserting duplicates
116 }
117 next = curr->next;
118 if (next) {
119 if (next->frame_num > new_frame_num) {
120 curr->next = new_node;
121 new_node->next = next;
122 break;
123 }
124 } else {
125 curr->next = new_node;
126 break;
127 }
128 curr = curr->next;
129 }
130 }
131 list->size++;
132 }
133
remove_node(struct frame_list * list,struct frame_node * prev,struct frame_node * node)134 void remove_node(struct frame_list *list,
135 struct frame_node *prev,
136 struct frame_node *node) {
137 if (node) {
138 if (prev)
139 prev->next = node->next;
140 else {
141 list->head = node->next;
142 }
143 free(node);
144 list->size--;
145 } else
146 LOG(ERROR, "Encountered null node while removing from frame list\n");
147 }
148
destroy_frame_list(struct frame_list * list)149 void destroy_frame_list(struct frame_list *list)
150 {
151 struct frame_node *curr, *prev;
152 if (!list || !list->head)
153 return;
154 else {
155 curr = list->head;
156 while (curr != NULL) {
157 prev = curr;
158 curr = curr->next;
159 free(prev);
160 }
161 }
162 }
163
164 static unsigned
parse_unsigned(const char * str)165 parse_unsigned(const char *str)
166 {
167 return strtol(str, NULL, 0);
168 }
169
is_frame_delimiter(char c)170 static bool is_frame_delimiter(char c)
171 {
172 return c == 0 || c == '/' || c == '-';
173 }
174
175 static struct frame_list *
parse_frames(const char * str)176 parse_frames(const char *str)
177 {
178 int32_t range_start;
179 uint32_t range_counter, range_interval, range_end;
180 range_start = -1;
181 range_counter = 0;
182 uint32_t range_delimit_count = 0;
183 range_interval = 1;
184 char *prev_delim = NULL;
185 char str_buf[256] = {0};
186 char *str_buf_ptr;
187 str_buf_ptr = str_buf;
188 struct frame_list *list = (struct frame_list*)malloc(sizeof(struct frame_list));
189 list->size = 0;
190 list->all_frames = false;
191
192 if (!strcmp(str, "all")) {
193 /* Don't bother counting, we want all frames */
194 list->all_frames = true;
195 } else {
196 while (*str != 0) { // Still string left to parse
197 for (; !is_frame_delimiter(*str); str++, str_buf_ptr++) {
198 if (!isdigit(*str))
199 {
200 LOG(ERROR, "mesa-screenshot: syntax error: unexpected non-digit "
201 "'%c' while parsing the frame numbers\n", *str);
202 destroy_frame_list(list);
203 return NULL;
204 }
205 *str_buf_ptr = *str;
206 }
207 if (strlen(str_buf) == 0) {
208 LOG(ERROR, "mesa-screenshot: syntax error: empty string given in frame range\n");
209 return NULL;
210 } else if (strlen(str_buf) > 0 && *str == '/') {
211 if (prev_delim && *prev_delim == '-') {
212 LOG(ERROR, "mesa-screenshot: syntax error: detected invalid individual " \
213 "frame selection (/) after range selection (-)\n");
214 return NULL;
215 }
216 LOG(DEBUG, "Adding frame: %u\n", parse_unsigned(str_buf));
217 insert_frame(list, parse_unsigned(str_buf));
218 } else if (strlen(str_buf) > 0 && (*str == '-' || *str == 0 )) {
219 if (range_delimit_count < 1) {
220 LOG(DEBUG, "Range start set\n");
221 range_start = parse_unsigned(str_buf);
222 range_delimit_count++;
223 } else if(range_delimit_count < 2) {
224 LOG(DEBUG, "Range counter set\n");
225 range_counter = parse_unsigned(str_buf);
226 range_delimit_count++;
227 } else {
228 LOG(DEBUG, "Range interval set\n");
229 range_interval = parse_unsigned(str_buf);
230 break;
231 }
232 if (*str == 0) {
233 break;
234 }
235 prev_delim = (char *)str;
236 }
237 str++;
238 /* Reset buffer for next set of numbers */
239 memset(str_buf, '\0', sizeof(str_buf));
240 str_buf_ptr = str_buf;
241 }
242 range_end = range_start + (range_counter * range_interval);
243 if (range_start >= 0) {
244 int i = range_start;
245 do {
246 insert_frame(list, i);
247 i += range_interval;
248 } while (i < range_end);
249 }
250 }
251 LOG(INFO, "frame range: ");
252 if (list->all_frames) {
253 LOG(NO_PREFIX, "all");
254 } else {
255 for (struct frame_node *iter = list->head; iter != NULL; iter = iter->next) {
256 LOG(NO_PREFIX, "%u", iter->frame_num);
257 if(iter->next) {
258 LOG(NO_PREFIX, ", ");
259 }
260 }
261 }
262 LOG(NO_PREFIX, "\n");
263 return list;
264 }
265
266 static bool
parse_help(const char * str)267 parse_help(const char *str)
268 {
269 LOG(NO_PREFIX, "Layer params using VK_LAYER_MESA_SCREENSHOT_CONFIG=\n");
270 #define SCREENSHOT_PARAM_BOOL(name) \
271 LOG(NO_PREFIX, "\t%s=0|1\n", #name);
272 #define SCREENSHOT_PARAM_CUSTOM(name)
273 SCREENSHOT_PARAMS
274 #undef SCREENSHOT_PARAM_BOOL
275 #undef SCREENSHOT_PARAM_CUSTOM
276 LOG(NO_PREFIX, "\tlog_type=info|debug (if no selection, no logs besides errors are given)\n");
277 LOG(NO_PREFIX, "\toutput_dir='/path/to/dir'\n");
278 LOG(NO_PREFIX, "\tframes=Individual frames, separated by '/', followed by " \
279 "a range setup, separated by '-', <range start>-<range count>-<range interval>\n" \
280 "\tFor example '1/5/7/15-4-5' = [1,5,7,15,20,25,30]\n" \
281 "\tframes='all' will select all frames.");
282
283 return true;
284 }
285
286 static enum LogType
parse_log_type(const char * str)287 parse_log_type(const char *str)
288 {
289 if(!strcmp(str, "info")) {
290 return INFO;
291 } else if (!strcmp(str, "debug")) {
292 return DEBUG;
293 } else {
294 /* Required logs only */
295 return REQUIRED;
296 }
297 }
298
299 /* TODO: Improve detection of proper directory path */
300 static const char *
parse_output_dir(const char * str)301 parse_output_dir(const char *str)
302 {
303 static char output_dir[256];
304 strcpy(output_dir, str);
305 uint32_t last_char_index = strlen(str)-1;
306 // Ensure we're in bounds and the last character is '/'
307 if (last_char_index > 0 &&
308 str[last_char_index] != '/' &&
309 last_char_index < 254) {
310 output_dir[last_char_index+1] = '/';
311 }
312 DIR *dir = opendir(output_dir);
313 assert(dir);
314 closedir(dir);
315
316 return output_dir;
317 }
318
is_delimiter(char c)319 static bool is_delimiter(char c)
320 {
321 return c == 0 || c == ',' || c == ':' || c == ';' || c == '=';
322 }
323
324 static int
parse_string(const char * s,char * out_param,char * out_value)325 parse_string(const char *s, char *out_param, char *out_value)
326 {
327 int i = 0;
328
329 for (; !is_delimiter(*s); s++, out_param++, i++)
330 *out_param = *s;
331
332 *out_param = 0;
333
334 if (*s == '=') {
335 s++;
336 i++;
337 for (; !is_delimiter(*s); s++, out_value++, i++)
338 *out_value = *s;
339 } else
340 *(out_value++) = '1';
341 *out_value = 0;
342
343 if (*s && is_delimiter(*s)) {
344 s++;
345 i++;
346 }
347
348 if (*s && !i) {
349 LOG(ERROR, "mesa-screenshot: syntax error: unexpected '%c' (%i) while "
350 "parsing a string\n", *s, *s);
351 }
352 return i;
353 }
354
355 const char *screenshot_param_names[] = {
356 #define SCREENSHOT_PARAM_BOOL(name) #name,
357 #define SCREENSHOT_PARAM_CUSTOM(name)
358 SCREENSHOT_PARAMS
359 #undef SCREENSHOT_PARAM_BOOL
360 #undef SCREENSHOT_PARAM_CUSTOM
361 };
362
363 void
parse_screenshot_env(struct screenshot_params * params,const char * env)364 parse_screenshot_env(struct screenshot_params *params,
365 const char *env)
366 {
367
368 if (!env)
369 return;
370
371 uint32_t num;
372 const char *itr = env;
373 char key[256], value[256];
374
375 memset(params, 0, sizeof(*params));
376
377 params->control = "mesa_screenshot";
378 params->frames = NULL;
379 params->output_dir = NULL;
380
381 /* Loop once first until log options found (if they exist) */
382 while ((num = parse_string(itr, key, value)) != 0) {
383 itr += num;
384 if (!strcmp("log_type", key)) {
385 LOG_TYPE = parse_log_type(value);
386 break;
387 }
388 }
389 /* Reset the iterator */
390 itr = env;
391
392 while ((num = parse_string(itr, key, value)) != 0) {
393 itr += num;
394 if (!strcmp("log_type", key)) {
395 /* Skip if matched again*/
396 continue;
397 }
398 #define SCREENSHOT_PARAM_BOOL(name) \
399 if (!strcmp(#name, key)) { \
400 params->enabled[SCREENSHOT_PARAM_ENABLED_##name] = \
401 strtol(value, NULL, 0); \
402 continue; \
403 }
404 #define SCREENSHOT_PARAM_CUSTOM(name) \
405 if (!strcmp(#name, key)) { \
406 params->name = parse_##name(value); \
407 continue; \
408 }
409 SCREENSHOT_PARAMS
410 #undef SCREENSHOT_PARAM_BOOL
411 #undef SCREENSHOT_PARAM_CUSTOM
412 LOG(ERROR, "Unknown option '%s'\n", key);
413 }
414 }
415