Merge "Support many at-spi clients" into tizen
[platform/upstream/at-spi2-core.git] / test / at_spi2_tool.c
1 #include "atspi/atspi.h"
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <getopt.h>
6 #include <stdbool.h>
7 #include <gio/gio.h>
8
9 #define ERROR_STATE -1
10 #define SAFE_BUFFER_SIZE 2048
11 #define CHECK_OUTPUT_WIDTH 42
12 #define MINIMAL_MODULE_WIDTH 3
13 #define ATTR_WIDTH 50
14 #define NUMBER_WIDTH 6
15 #define ARRAY_SIZE(x)  (sizeof(x) / sizeof((x)[0]))
16 #define COLUMN_NO 3
17 #define FLAG_NO 3
18 #define DUMP 0
19 #define CHECK 1
20 #define FIRST_MATCH 2
21 #define VERSION "1.1"
22
23 static unsigned indent_width = 2;
24 static int module_name_limit = -1;
25
26 static const char* atspi_state_names[] = {
27         [ATSPI_STATE_INVALID] = "INVALID",
28         [ATSPI_STATE_ACTIVE] = "ACTIVE",
29         [ATSPI_STATE_ARMED] = "ARMED",
30         [ATSPI_STATE_BUSY] =  "BUSY",
31         [ATSPI_STATE_CHECKED] = "CHECKED",
32         [ATSPI_STATE_COLLAPSED] = "COLLAPSED",
33         [ATSPI_STATE_DEFUNCT] = "DEFUNCT",
34         [ATSPI_STATE_EDITABLE] = "EDITABLE",
35         [ATSPI_STATE_ENABLED] = "ENABLED",
36         [ATSPI_STATE_EXPANDABLE] = "EXPANDABLE",
37         [ATSPI_STATE_EXPANDED] = "EXPANDED",
38         [ATSPI_STATE_FOCUSABLE] = "FOCUSABLE",
39         [ATSPI_STATE_FOCUSED] = "FOCUSED",
40         [ATSPI_STATE_HAS_TOOLTIP] = "HAS_TOOLTIP",
41         [ATSPI_STATE_HORIZONTAL] = "HORIZONTAL",
42         [ATSPI_STATE_ICONIFIED] = "ICONIFIED",
43         [ATSPI_STATE_MULTI_LINE] = "MULTI_LINE",
44         [ATSPI_STATE_MULTISELECTABLE] = "MULTISELECTABLE",
45         [ATSPI_STATE_OPAQUE] = "OPAQUE",
46         [ATSPI_STATE_PRESSED] = "PRESSED",
47         [ATSPI_STATE_RESIZABLE] = "RESIZABLE",
48         [ATSPI_STATE_SELECTABLE] = "SELECTABLE",
49         [ATSPI_STATE_SELECTED] = "SELECTED",
50         [ATSPI_STATE_SENSITIVE] = "SENSITIVE",
51         [ATSPI_STATE_SHOWING] = "SHOWING",
52         [ATSPI_STATE_SINGLE_LINE] = "SINGLE_LINE",
53         [ATSPI_STATE_STALE] = "STALE",
54         [ATSPI_STATE_TRANSIENT] = "TRANSIENT",
55         [ATSPI_STATE_VERTICAL] = "VERTICAL",
56         [ATSPI_STATE_VISIBLE] = "VISIBLE",
57         [ATSPI_STATE_MANAGES_DESCENDANTS] = "MANAGES_DESCENDANTS",
58         [ATSPI_STATE_INDETERMINATE] = "INDETERMINATE",
59         [ATSPI_STATE_REQUIRED] = "REQUIRED",
60         [ATSPI_STATE_TRUNCATED] = "TRUNCATED",
61         [ATSPI_STATE_ANIMATED] = "ANIMATED",
62         [ATSPI_STATE_INVALID_ENTRY] = "INVALID_ENTRY",
63         [ATSPI_STATE_SUPPORTS_AUTOCOMPLETION] = "SUPPORTS_AUTOCOMPLETION",
64         [ATSPI_STATE_SELECTABLE_TEXT] = "SELECTABLE_TEXT",
65         [ATSPI_STATE_IS_DEFAULT] = "IS_DEFAULT",
66         [ATSPI_STATE_VISITED] = "VISITED",
67         [ATSPI_STATE_CHECKABLE] = "CHECKABLE",
68         [ATSPI_STATE_MODAL] = "MODAL",
69         [ATSPI_STATE_HIGHLIGHTED] = "HIGHLIGHTED",
70         [ATSPI_STATE_HIGHLIGHTABLE] = "HIGHLIGHTABLE",
71         [ATSPI_STATE_HAS_POPUP] = "HAS_POPUP",
72         [ATSPI_STATE_READ_ONLY] = "READ_ONLY",
73         [ATSPI_STATE_LAST_DEFINED] = "LAST_DEFINED"
74 };
75
76 typedef struct _Box_Size {
77         char *x;
78         char *y;
79         char *height;
80         char *width;
81 } Box_Size;
82
83 char *_strdup(const char *c)
84 {
85         char *result = (char *)calloc(1, strlen(c) + 1);
86         return result ? strcpy(result, c) : result;
87 }
88
89 static char *_multiply_string(char c, unsigned number)
90 {
91         char *result = (char *)calloc(1, number + 1);
92
93         if (result == NULL)
94                 return result;
95
96         memset(result, c, number);
97
98         return result;
99 }
100
101 static void _print_module_legend()
102 {
103         printf("\n[[node],[node role name],[attributes list],[x,y,width,height],[node name],[states list][flow to node, flow from node]\n\n");
104 }
105
106 static void _print_atspi_states_legend()
107 {
108         _print_module_legend();
109
110         int array_len = ARRAY_SIZE(atspi_state_names);
111         int line_count = (array_len + COLUMN_NO - 1)/COLUMN_NO;
112         for (int line_idx = 0; line_idx < line_count; line_idx++) {
113                 if ((line_idx < line_count - 1) || (array_len % COLUMN_NO == 0)) {
114                         printf("[%d]\t%-24s[%d]\t%-24s[%d]\t%s\n",
115                                         line_idx * COLUMN_NO,
116                                         atspi_state_names[line_idx * COLUMN_NO],
117                                         (line_idx * COLUMN_NO) + 1,
118                                         atspi_state_names[(line_idx * COLUMN_NO) + 1],
119                                         (line_idx * COLUMN_NO) + 2,
120                                         atspi_state_names[(line_idx * COLUMN_NO) + 2]);
121                 } else if (array_len % COLUMN_NO == 2) {
122                         printf("[%d]\t%-24s[%d]\t%s\n",
123                                         line_idx * COLUMN_NO,
124                                         atspi_state_names[line_idx * COLUMN_NO],
125                                         (line_idx * COLUMN_NO) + 1,
126                                         atspi_state_names[(line_idx * COLUMN_NO) + 1]);
127                 } else {
128                         printf("[%d]\t%s\n", line_idx * COLUMN_NO, atspi_state_names[line_idx * COLUMN_NO]);
129                 }
130         }
131 }
132
133 static void _set_indent(int indent_size)
134 {
135         if (indent_size < 0) {
136                 fprintf(stderr, "WARNING: Invalid indent size. Default value retained.\n");
137                 return;
138         }
139
140         indent_width = indent_size;
141 }
142
143 static void _set_module_length_limit(int limit)
144 {
145         if (limit < MINIMAL_MODULE_WIDTH) {
146                 fprintf(stderr, "WARNING: Invalid maximum field size. Default value retained.\n");
147                 return;
148         }
149
150         module_name_limit = limit;
151 }
152
153 static int _int_sort_function(gconstpointer a, gconstpointer b)
154 {
155         int int_a = GPOINTER_TO_INT (a);
156         int int_b = GPOINTER_TO_INT (b);
157
158         return int_a - int_b;
159 }
160
161 static void _truncate_string(char *string, int length_limit)
162 {
163         if (length_limit < MINIMAL_MODULE_WIDTH || !string)
164                 return;
165
166         int len = strlen(string);
167
168         if (len > length_limit)
169                 memcpy(string + length_limit - 3, "...", 4);
170 }
171
172 static size_t _combine_strings(char **destination, const char *source)
173 {
174         if (!source || !*source || !destination)
175                 return 0;
176
177         size_t old_len = *destination ? strlen(*destination) : 0;
178         size_t source_len = strlen(source);
179         size_t len = old_len + source_len;
180         char *buf = realloc(*destination, (len + 1) * sizeof(char));
181
182         if (!buf) {
183                 fprintf(stderr, "_combine_strings: allocation failed");
184                 return old_len;
185         }
186
187         memcpy(buf + old_len, source, source_len + 1);
188
189         *destination = buf;
190
191         return len;
192 }
193
194 static Box_Size *_get_box_size(AtspiAccessible *node)
195 {
196         Box_Size *box_size = (Box_Size *)malloc(sizeof(Box_Size));
197         if (box_size == NULL)
198                 return NULL;
199
200         char *x = NULL;
201         char *y = NULL;
202         char *width = NULL;
203         char *height = NULL;
204
205         AtspiComponent *component = atspi_accessible_get_component_iface(node);
206         AtspiRect *extent = component ? atspi_component_get_extents(component, ATSPI_COORD_TYPE_SCREEN, NULL) : NULL;
207
208         if (extent == NULL) {
209                 _combine_strings(&x, "_");
210                 _combine_strings(&y, "_");
211                 _combine_strings(&width, "_");
212                 _combine_strings(&height, "_");
213         } else {
214                 char temp_buff[NUMBER_WIDTH];
215
216                 snprintf(temp_buff, NUMBER_WIDTH, "%d", extent->x);
217                 _combine_strings(&x, temp_buff);
218
219                 snprintf(temp_buff, NUMBER_WIDTH, "%d", extent->y);
220                 _combine_strings(&y, temp_buff);
221
222                 snprintf(temp_buff, NUMBER_WIDTH, "%d", extent->width);
223                 _combine_strings(&width, temp_buff);
224
225                 snprintf(temp_buff, NUMBER_WIDTH, "%d", extent->height);
226                 _combine_strings(&height, temp_buff);
227
228                 g_object_unref(component);
229                 g_free(extent);
230         }
231
232         box_size->x = x;
233         box_size->y = y;
234         box_size->width = width;
235         box_size->height = height;
236
237         return box_size;
238 }
239
240 static char *_get_states(AtspiAccessible *node, int length_limit)
241 {
242         AtspiStateSet *node_state_set = atspi_accessible_get_state_set(node);
243         GArray *states = atspi_state_set_get_states(node_state_set);
244         if (!states) return NULL;
245         g_array_sort(states, _int_sort_function);
246
247         AtspiStateType state_type;
248         char *state_string = NULL;
249
250         for (int i = 0; i < states->len; i++) {
251                 state_type = g_array_index(states, AtspiStateType, i);
252
253                 char node_state_str[8];
254                 sprintf(node_state_str, "(%d)", state_type);
255                 _combine_strings(&state_string, node_state_str);
256         }
257
258         g_array_free(states, 0);
259         g_object_unref(node_state_set);
260
261         _truncate_string(state_string, length_limit);
262
263         return state_string;
264 }
265
266 static char *_get_attributes(AtspiAccessible *node, int length_limit)
267 {
268         GHashTable *attributes = atspi_accessible_get_attributes(node, NULL);
269
270         if (!attributes)
271                 return NULL;
272
273         GHashTableIter attributes_iter;
274         gpointer attr_key;
275         gpointer attr_value;
276         g_hash_table_iter_init (&attributes_iter, attributes);
277         char *result = NULL;
278         while (g_hash_table_iter_next (&attributes_iter, &attr_key, &attr_value)) {
279                 char attributes_string[ATTR_WIDTH];
280                 int ret = snprintf(attributes_string, ATTR_WIDTH, "(%s=%s)", (char *)attr_key, (char *)attr_value);
281                 if (ret >= ATTR_WIDTH)
282                         fprintf(stderr, "\n_get_attributes: generated string is too long. Buffer overflow\n");
283
284                 _combine_strings(&result, attributes_string);
285         }
286         g_hash_table_unref(attributes);
287
288         _truncate_string(result, length_limit);
289         return result;
290 }
291
292 static char *_get_object_in_relation(AtspiAccessible *source, AtspiRelationType search_type)
293 {
294         if (source == NULL)
295                 return "";
296
297         GArray *relations = atspi_accessible_get_relation_set(source, NULL);
298
299         if (relations == NULL)
300                 return "";
301
302         AtspiAccessible *ret = NULL;
303         for (int i = 0; i < relations->len; ++i) {
304                 AtspiRelation *relation = g_array_index(relations, AtspiRelation *, i);
305                 AtspiRelationType type = atspi_relation_get_relation_type(relation);
306
307                 if (type == search_type) {
308                         ret = atspi_relation_get_target(relation, 0);
309                         break;
310                 }
311         }
312
313         g_array_free(relations, TRUE);
314
315         return ret ? atspi_accessible_get_unique_id(ret, NULL) : g_strdup("");
316 }
317
318 static char *_get_info(AtspiAccessible *node, int length_limit)
319 {
320         if (!node)
321                 return NULL;
322
323         char *node_name = atspi_accessible_get_name(node, NULL);
324         char *node_role_name = atspi_accessible_get_role_name(node, NULL);
325         char *unique_id = atspi_accessible_get_unique_id(node, NULL);
326
327         char *attributes = _get_attributes(node, length_limit);
328         Box_Size *box_size = _get_box_size(node);
329         char *states = _get_states(node, length_limit);
330
331         char *flows_to = _get_object_in_relation(node, ATSPI_RELATION_FLOWS_TO);
332         char *flows_from = _get_object_in_relation(node, ATSPI_RELATION_FLOWS_FROM);
333
334         char result[SAFE_BUFFER_SIZE];
335         int ret = snprintf(result, SAFE_BUFFER_SIZE, "[[%s],[%s],[%s],[%s,%s,%s,%s],[%s],[%s],[%s,%s]]",
336                                                 unique_id,
337                                                 node_role_name,
338                                                 attributes,
339                                                 box_size->x,
340                                                 box_size->y,
341                                                 box_size->width,
342                                                 box_size->height,
343                                                 node_name,
344                                                 states,
345                                                 flows_to,
346                                                 flows_from);
347
348         if (ret >= SAFE_BUFFER_SIZE)
349                 fprintf(stderr, "\n%s, %s %d: generated string is too long. Buffer overflow\n", __FILE__, __FUNCTION__, __LINE__);
350
351         free(node_name);
352         free(node_role_name);
353         free(unique_id);
354         free(attributes);
355         if (box_size) {
356                 free(box_size->width);
357                 free(box_size->height);
358                 free(box_size);
359         }
360         free(states);
361         free(flows_to);
362         free(flows_from);
363
364         return _strdup(result);
365 }
366
367 static void _test_atspi_parent_child_relation(AtspiAccessible *obj, AtspiAccessible *parent_candidate, int parent_candidate_index)
368 {
369         int parent_index = atspi_accessible_get_index_in_parent(obj, NULL);
370         AtspiAccessible *parent = atspi_accessible_get_parent(obj, NULL);
371
372         char output[CHECK_OUTPUT_WIDTH];
373         if (parent_index != parent_candidate_index || parent_candidate != parent) {
374                 char parent_status[NUMBER_WIDTH];
375
376                 if (parent == NULL)
377                         strcpy(parent_status, "_");
378                 else
379                         snprintf(parent_status, NUMBER_WIDTH, "%d", parent_index);
380
381                 char *parent_unique_id = atspi_accessible_get_unique_id(parent, NULL);
382                 char *parent_candidate_unique_id = atspi_accessible_get_unique_id(parent_candidate, NULL);
383                 snprintf(output, CHECK_OUTPUT_WIDTH, "[FAIL<%d,%s><%s,%s>]", parent_candidate_index, parent_status,
384                                 parent_candidate_unique_id, parent_unique_id);
385                 free(parent_unique_id);
386                 free(parent_candidate_unique_id);
387
388         } else {
389                 snprintf(output, CHECK_OUTPUT_WIDTH, "[OK]");
390         }
391
392         printf("%-*s\t", CHECK_OUTPUT_WIDTH, output);
393 }
394
395 static int _print_atspi_tree_verify_maybe_r(int indent_number, AtspiAccessible *object, bool check_integrity, int length_limit)
396 {
397         char *indent = _multiply_string(' ', indent_number*indent_width);
398         if (indent != NULL) {
399                 printf("%s", indent);
400                 free(indent);
401         }
402
403         char *node_info = _get_info(object, length_limit);
404         if (node_info != NULL) {
405                 printf("%s\n", node_info);
406                 free(node_info);
407         }
408
409         int count = atspi_accessible_get_child_count(object, NULL);
410         for (int i = 0; i < count; i++) {
411                 AtspiAccessible *child = atspi_accessible_get_child_at_index(object, i, NULL);
412                 if (child) {
413                         if (check_integrity)
414                                 _test_atspi_parent_child_relation(child, object, i);
415
416                         _print_atspi_tree_verify_maybe_r(indent_number + 1, child, check_integrity, length_limit);
417                 }
418         }
419         return 0;
420 }
421
422 void _print_help()
423 {
424         printf("AT-SPI2-CORE-UTIL\n\n");
425         printf("USAGE: at_spi2_tool [OPTION] [PARAMETER] ...\n\n");
426         printf("OPTION LIST:\n");
427         printf("-h, --help\t\tshow this message\n");
428         printf("-v, --version\t\tshow actual version of tool\n");
429         printf("-g, --show-legend\tprint AT-SPI state legend\n");
430         printf("-l, --list-apps\t\tlist all applications of desktop\n");
431         printf("-a, --at-spi-client <true|false>\tenable/disable org.a11y.Status.IsEnabled property\n");
432         printf("-s, --sleep <N>\tsleep N seconds\n");
433         printf("-d, --tree-dump\t\tdump tree for selected application\n");
434         printf("-c, --tree-check\tcheck tree for selected application\n");
435         printf("-f, --first-match\tperform dump or check only for first matching application\n");
436         printf("-i, --indent\t\tset indentation size, default value stands at 2\n");
437         printf("-t, --truncate\t\tset maximum single field size, default value stands at 30\n");
438         printf("\nEXAMPLE:\n");
439         printf("\tat_spi2_tool -i3 -d quickpanel\n");
440         printf("\t  show AT-SPI tree for node \"quickpanel\" using three-space indentation\n");
441         printf("\tat_spi2_tool -i1 -c starter\n");
442         printf("\t  show AT-SPI tree with integrity test for node \"starter\" using one-space indentation\n");
443 }
444 void _print_version()
445 {
446         printf("AT-SPI2-CORE-UTIL v%s\n", VERSION);
447 }
448
449 static void _atspi_tree_traverse(const char *app_name, bool dump, bool check, bool first_match, int length_limit)
450 {
451
452         AtspiAccessible *desktop = atspi_get_desktop(0);
453         if (!desktop) {
454                 fprintf(stderr, "atspi_get_desktop failed\n");
455                 return;
456         }
457
458         int count = atspi_accessible_get_child_count(desktop, NULL);
459         bool app_name_matched = false;
460         for (int i = 0; i < count; i++) {
461                 AtspiAccessible *child = atspi_accessible_get_child_at_index(desktop, i, NULL);
462                 if (child == NULL) {
463                         fprintf(stderr, "\n%s, %s %d: Null child occured. Results may be misleading.\n", __FILE__, __FUNCTION__, __LINE__);
464                         continue;
465                 }
466
467                 char *name = atspi_accessible_get_name(child, NULL);
468
469                 if (!dump && !check)
470                         printf("%s\n", name);
471
472                 if ((check || dump) && name && !strcmp(name, app_name)) {
473                         app_name_matched = true;
474
475                         _print_module_legend();
476
477                         if (check)
478                                 _test_atspi_parent_child_relation(child, desktop, i);
479
480                         _print_atspi_tree_verify_maybe_r(0, child, check, length_limit);
481
482                         if (first_match) {
483                                 free(name);
484                                 break;
485                         } else
486                                 printf("\n");
487
488                         free(name);
489                 }
490         }
491
492         if (!app_name_matched && (dump || check))
493                 fprintf(stderr, "There is no application with name: %s. Try again.\n", app_name);
494
495         g_object_unref(desktop);
496 }
497
498 static void _at_spi_client_enable(gboolean enabled)
499 {
500         static GDBusProxy *proxy = NULL; //we keep proxy (dbus connection) until program exits
501         GVariant *result;
502         GError *error = NULL;
503         GDBusProxyFlags flags = G_DBUS_PROXY_FLAGS_NONE;
504
505
506         if (!proxy) {
507                 proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
508                                         flags,
509                                         NULL, /* GDBusInterfaceInfo */
510                                         "org.a11y.Bus",
511                                         "/org/a11y/bus",
512                                         "org.freedesktop.DBus.Properties",
513                                         NULL, /* GCancellable */
514                                         &error);
515                 if (error) {
516                         fprintf(stderr, "Failed to create proxy object for '/org/a11y/bus': %s\n", error->message);
517                         g_error_free(error);
518                         return;
519                 }
520         }
521
522         result = g_dbus_proxy_call_sync(proxy,
523                                         "Set",
524                                         g_variant_new ("(ssv)",  "org.a11y.Status", "IsEnabled", g_variant_new_boolean(enabled)),
525                                         G_DBUS_CALL_FLAGS_NONE,
526                                         -1,
527                                         NULL,
528                                         &error);
529         if (result)
530                 g_variant_unref(result);
531
532         if (error) {
533                 fprintf(stderr, "Fail to call org.freedesktop.DBus.Properties.Set: %s\n", error->message);
534                 g_error_free(error);
535         }
536 }
537
538 static void _run_command(int argc, char *argv[])
539 {
540         struct option long_options[] = {
541                 {"help", no_argument, 0, 'h'},
542                 {"version", no_argument, 0, 'v'},
543                 {"show-legend", no_argument, 0, 'g'},
544                 {"list-apps", no_argument, 0, 'l'},
545                 {"at-spi-client", no_argument, 0, 'a'},
546                 {"sleep", required_argument, 0, 's'},
547                 {"tree-dump", required_argument, 0, 'd'},
548                 {"tree-check", required_argument, 0, 'c'},
549                 {"first-match", no_argument, 0, 'f'},
550                 {"indent", required_argument, 0, 'i'},
551                 {"truncate", required_argument, 0, 't'},
552         };
553
554         int command = 0;
555         int option_index = 0;
556         bool traverse_flags[FLAG_NO] = {false};
557         char *app_name = NULL;
558         gboolean enable_at_spi_client;
559
560         while (TRUE) {
561                 command = getopt_long(argc, argv, "hvgla:s:d:c:ft:i:", long_options, &option_index);
562
563                 if (command == ERROR_STATE)
564                         break;
565
566                 switch (command) {
567                 case 'h':
568                         _print_help();
569                         break;
570
571                 case 'v':
572                         _print_version();
573                         break;
574
575                 case 'g':
576                         _print_atspi_states_legend();
577                         break;
578
579                 case 'l':
580                         _atspi_tree_traverse(NULL, false, false, false, module_name_limit);
581                         break;
582
583                 case 'a':
584                         enable_at_spi_client = TRUE;
585                         if(optarg[0] == 'f' || optarg[0] == '0')
586                                 enable_at_spi_client = FALSE;
587
588                         _at_spi_client_enable(enable_at_spi_client);
589                         break;
590
591                 case 's':
592                         sleep(atoi(optarg));
593                         break;
594
595                 case 'd':
596                         traverse_flags[DUMP] = true;
597                         app_name = optarg;
598                         break;
599
600                 case 'c':
601                         traverse_flags[CHECK] = true;
602                         app_name = optarg;
603                         break;
604
605                 case 'f':
606                         traverse_flags[FIRST_MATCH] = true;
607                         break;
608
609                 case 'i':
610                         _set_indent(atoi(optarg));
611                         break;
612
613                 case 't':
614                         _set_module_length_limit(atoi(optarg));
615                         break;
616
617                 case '?':
618                         fprintf(stderr, "Invalid parameter. Use: \"--help\"\n");
619                         break;
620
621                 default:
622                         abort();
623                 }
624         }
625
626         if (traverse_flags[DUMP] || traverse_flags[CHECK])
627                 _atspi_tree_traverse(app_name, traverse_flags[DUMP], traverse_flags[CHECK], traverse_flags[FIRST_MATCH], module_name_limit);
628 }
629
630 int main(int argc, char *argv[])
631 {
632         if (argc < 2) {
633                 printf("Arguments required. Type %s --help.\n", argv[0]);
634                 return -1;
635         }
636
637         int result = atspi_init();
638         if (result != 0)
639         {
640                 fprintf(stderr, "Unable to initilize AT-SPI infrastructure, atspi_init failed\n");
641                 return -1;
642         }
643
644         _run_command(argc, argv);
645
646         return 0;
647 }