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[STANDARD_BUFFER_SIZE] = {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
getRegionFromInput(const char * str)266 struct ImageRegion getRegionFromInput(const char *str) {
267 struct ImageRegion region;
268 region.useImageRegion = false;
269 region.startX = 0;
270 region.startY = 0;
271 region.endX = 1;
272 region.endY = 1;
273 /* Expected form is a tuple of four float entries, representing a percentage,
274 so need to attempt to convert the values to floating point type and ensure
275 the values are in the range 0.00 <= x <= 1.00.
276
277 An example of proper input would be:
278 "0.20/0.20/0.75/0.60"
279 */
280 if (strlen(str) == 0) {
281 LOG(ERROR, "Region input was empty!\n");
282 return region;
283 }
284 errno = 0;
285 float dimensions[] = {0, 0, 1, 1};
286 char *dup = strdup(str);
287 char *token = strtok(dup, "/");
288 char *endptr;
289 int i;
290 for (i = 0; i < 4; i++, token = strtok(NULL, "/")) {
291 if (!token) {
292 LOG(ERROR, "Four region entries were not detected!\n");
293 break;
294 }
295 dimensions[i] = strtof(token, &endptr);
296 if (errno || endptr == token) {
297 LOG(ERROR, "Found non-float in region description: %s\n", token, errno);
298 break;
299 }
300 if (dimensions[i] < 0 || 1 < dimensions[i] ) {
301 LOG(ERROR, "Found invalid region value, region value must be between 0 and 1: %f\n", dimensions[i]);
302 break;
303 }
304 }
305 if (i == 4) {
306 if (dimensions[0] < dimensions[2] && dimensions[1] < dimensions[3]) {
307 region.startX = dimensions[0];
308 region.startY = dimensions[1];
309 region.endX = dimensions[2];
310 region.endY = dimensions[3];
311 region.useImageRegion = true;
312 } else {
313 LOG(ERROR, "Region end values need to be greater than region start values!\n");
314 }
315 }
316 free(dup);
317 return region;
318 }
319
parse_region(const char * str)320 static struct ImageRegion parse_region(const char *str)
321 {
322 return getRegionFromInput(str);
323 }
324
325 static bool
parse_help(const char * str)326 parse_help(const char *str)
327 {
328 LOG(NO_PREFIX, "Layer params using VK_LAYER_MESA_SCREENSHOT_CONFIG=\n");
329 #define SCREENSHOT_PARAM_BOOL(name) \
330 LOG(NO_PREFIX, "\t%s=0|1\n", #name);
331 #define SCREENSHOT_PARAM_CUSTOM(name)
332 SCREENSHOT_PARAMS
333 #undef SCREENSHOT_PARAM_BOOL
334 #undef SCREENSHOT_PARAM_CUSTOM
335 LOG(NO_PREFIX, "\tlog_type=info|debug (if no selection, no logs besides errors are given)\n");
336 LOG(NO_PREFIX, "\toutput_dir='/path/to/dir'\n");
337 LOG(NO_PREFIX, "\tframes=Individual frames, separated by '/', followed by " \
338 "a range setup, separated by '-', <range start>-<range count>-<range interval>\n" \
339 "\tFor example '1/5/7/15-4-5' = [1,5,7,15,20,25,30]\n" \
340 "\tframes='all' will select all frames.");
341
342 return true;
343 }
344
345 static enum LogType
parse_log_type(const char * str)346 parse_log_type(const char *str)
347 {
348 if(!strcmp(str, "info")) {
349 return INFO;
350 } else if (!strcmp(str, "debug")) {
351 return DEBUG;
352 } else {
353 /* Required logs only */
354 return REQUIRED;
355 }
356 }
357
358 /* TODO: Improve detection of proper directory path */
359 static const char *
parse_output_dir(const char * str)360 parse_output_dir(const char *str)
361 {
362 static char output_dir[LARGE_BUFFER_SIZE];
363 strcpy(output_dir, str);
364 uint32_t last_char_index = strlen(str)-1;
365 // Ensure we're in bounds and the last character is '/'
366 if (last_char_index > 0 &&
367 str[last_char_index] != '/' &&
368 last_char_index < LARGE_BUFFER_SIZE-1) {
369 output_dir[last_char_index+1] = '/';
370 }
371 DIR *dir = opendir(output_dir);
372 assert(dir);
373 closedir(dir);
374
375 return output_dir;
376 }
377
is_delimiter(char c)378 static bool is_delimiter(char c)
379 {
380 return c == 0 || c == ',' || c == ':' || c == ';' || c == '=';
381 }
382
383 static int
parse_string(const char * s,char * out_param,char * out_value)384 parse_string(const char *s, char *out_param, char *out_value)
385 {
386 int i = 0;
387
388 for (; !is_delimiter(*s); s++, out_param++, i++)
389 *out_param = *s;
390
391 *out_param = 0;
392
393 if (*s == '=') {
394 s++;
395 i++;
396 for (; !is_delimiter(*s); s++, out_value++, i++)
397 *out_value = *s;
398 } else
399 *(out_value++) = '1';
400 *out_value = 0;
401
402 if (*s && is_delimiter(*s)) {
403 s++;
404 i++;
405 }
406
407 if (*s && !i) {
408 LOG(ERROR, "mesa-screenshot: syntax error: unexpected '%c' (%i) while "
409 "parsing a string\n", *s, *s);
410 }
411 return i;
412 }
413
414 const char *screenshot_param_names[] = {
415 #define SCREENSHOT_PARAM_BOOL(name) #name,
416 #define SCREENSHOT_PARAM_CUSTOM(name)
417 SCREENSHOT_PARAMS
418 #undef SCREENSHOT_PARAM_BOOL
419 #undef SCREENSHOT_PARAM_CUSTOM
420 };
421
422 void
parse_screenshot_env(struct screenshot_params * params,const char * env)423 parse_screenshot_env(struct screenshot_params *params,
424 const char *env)
425 {
426
427 if (!env)
428 return;
429
430 uint32_t num;
431 const char *itr = env;
432 char key[STANDARD_BUFFER_SIZE], value[LARGE_BUFFER_SIZE];
433
434 memset(params, 0, sizeof(*params));
435
436 params->control = "mesa_screenshot";
437 params->frames = NULL;
438 params->output_dir = NULL;
439 params->region.useImageRegion = false;
440
441 /* Loop once first until log options found (if they exist) */
442 while ((num = parse_string(itr, key, value)) != 0) {
443 itr += num;
444 if (!strcmp("log_type", key)) {
445 LOG_TYPE = parse_log_type(value);
446 break;
447 }
448 }
449 /* Reset the iterator */
450 itr = env;
451
452 while ((num = parse_string(itr, key, value)) != 0) {
453 itr += num;
454 if (!strcmp("log_type", key)) {
455 /* Skip if matched again*/
456 continue;
457 }
458 #define SCREENSHOT_PARAM_BOOL(name) \
459 if (!strcmp(#name, key)) { \
460 params->enabled[SCREENSHOT_PARAM_ENABLED_##name] = \
461 strtol(value, NULL, 0); \
462 continue; \
463 }
464 #define SCREENSHOT_PARAM_CUSTOM(name) \
465 if (!strcmp(#name, key)) { \
466 params->name = parse_##name(value); \
467 continue; \
468 }
469 SCREENSHOT_PARAMS
470 #undef SCREENSHOT_PARAM_BOOL
471 #undef SCREENSHOT_PARAM_CUSTOM
472 LOG(ERROR, "Unknown option '%s'\n", key);
473 }
474 }
475