115002f60SMarc-André Lureau /* 215002f60SMarc-André Lureau * Error reporting 315002f60SMarc-André Lureau * 415002f60SMarc-André Lureau * Copyright (C) 2010 Red Hat Inc. 515002f60SMarc-André Lureau * 615002f60SMarc-André Lureau * Authors: 715002f60SMarc-André Lureau * Markus Armbruster <armbru@redhat.com>, 815002f60SMarc-André Lureau * 915002f60SMarc-André Lureau * This work is licensed under the terms of the GNU GPL, version 2 or later. 1015002f60SMarc-André Lureau * See the COPYING file in the top-level directory. 1115002f60SMarc-André Lureau */ 1215002f60SMarc-André Lureau 1315002f60SMarc-André Lureau #include "qemu/osdep.h" 1415002f60SMarc-André Lureau #include "monitor/monitor.h" 1515002f60SMarc-André Lureau #include "qemu/error-report.h" 1615002f60SMarc-André Lureau 1715002f60SMarc-André Lureau /* 1815002f60SMarc-André Lureau * @report_type is the type of message: error, warning or 1915002f60SMarc-André Lureau * informational. 2015002f60SMarc-André Lureau */ 2115002f60SMarc-André Lureau typedef enum { 2215002f60SMarc-André Lureau REPORT_TYPE_ERROR, 2315002f60SMarc-André Lureau REPORT_TYPE_WARNING, 2415002f60SMarc-André Lureau REPORT_TYPE_INFO, 2515002f60SMarc-André Lureau } report_type; 2615002f60SMarc-André Lureau 2715002f60SMarc-André Lureau /* Prepend timestamp to messages */ 2815002f60SMarc-André Lureau bool message_with_timestamp; 2915002f60SMarc-André Lureau bool error_with_guestname; 3015002f60SMarc-André Lureau const char *error_guest_name; 3115002f60SMarc-André Lureau 3215002f60SMarc-André Lureau int error_printf(const char *fmt, ...) 3315002f60SMarc-André Lureau { 3415002f60SMarc-André Lureau va_list ap; 3515002f60SMarc-André Lureau int ret; 3615002f60SMarc-André Lureau 3715002f60SMarc-André Lureau va_start(ap, fmt); 3815002f60SMarc-André Lureau ret = error_vprintf(fmt, ap); 3915002f60SMarc-André Lureau va_end(ap); 4015002f60SMarc-André Lureau return ret; 4115002f60SMarc-André Lureau } 4215002f60SMarc-André Lureau 4315002f60SMarc-André Lureau static Location std_loc = { 4415002f60SMarc-André Lureau .kind = LOC_NONE 4515002f60SMarc-André Lureau }; 4615002f60SMarc-André Lureau static Location *cur_loc = &std_loc; 4715002f60SMarc-André Lureau 4815002f60SMarc-André Lureau /* 4915002f60SMarc-André Lureau * Push location saved in LOC onto the location stack, return it. 5015002f60SMarc-André Lureau * The top of that stack is the current location. 5115002f60SMarc-André Lureau * Needs a matching loc_pop(). 5215002f60SMarc-André Lureau */ 5315002f60SMarc-André Lureau Location *loc_push_restore(Location *loc) 5415002f60SMarc-André Lureau { 5515002f60SMarc-André Lureau assert(!loc->prev); 5615002f60SMarc-André Lureau loc->prev = cur_loc; 5715002f60SMarc-André Lureau cur_loc = loc; 5815002f60SMarc-André Lureau return loc; 5915002f60SMarc-André Lureau } 6015002f60SMarc-André Lureau 6115002f60SMarc-André Lureau /* 6215002f60SMarc-André Lureau * Initialize *LOC to "nowhere", push it onto the location stack. 6315002f60SMarc-André Lureau * The top of that stack is the current location. 6415002f60SMarc-André Lureau * Needs a matching loc_pop(). 6515002f60SMarc-André Lureau * Return LOC. 6615002f60SMarc-André Lureau */ 6715002f60SMarc-André Lureau Location *loc_push_none(Location *loc) 6815002f60SMarc-André Lureau { 6915002f60SMarc-André Lureau loc->kind = LOC_NONE; 7015002f60SMarc-André Lureau loc->prev = NULL; 7115002f60SMarc-André Lureau return loc_push_restore(loc); 7215002f60SMarc-André Lureau } 7315002f60SMarc-André Lureau 7415002f60SMarc-André Lureau /* 7515002f60SMarc-André Lureau * Pop the location stack. 7615002f60SMarc-André Lureau * LOC must be the current location, i.e. the top of the stack. 7715002f60SMarc-André Lureau */ 7815002f60SMarc-André Lureau Location *loc_pop(Location *loc) 7915002f60SMarc-André Lureau { 8015002f60SMarc-André Lureau assert(cur_loc == loc && loc->prev); 8115002f60SMarc-André Lureau cur_loc = loc->prev; 8215002f60SMarc-André Lureau loc->prev = NULL; 8315002f60SMarc-André Lureau return loc; 8415002f60SMarc-André Lureau } 8515002f60SMarc-André Lureau 8615002f60SMarc-André Lureau /* 8715002f60SMarc-André Lureau * Save the current location in LOC, return LOC. 8815002f60SMarc-André Lureau */ 8915002f60SMarc-André Lureau Location *loc_save(Location *loc) 9015002f60SMarc-André Lureau { 9115002f60SMarc-André Lureau *loc = *cur_loc; 9215002f60SMarc-André Lureau loc->prev = NULL; 9315002f60SMarc-André Lureau return loc; 9415002f60SMarc-André Lureau } 9515002f60SMarc-André Lureau 9615002f60SMarc-André Lureau /* 9715002f60SMarc-André Lureau * Change the current location to the one saved in LOC. 9815002f60SMarc-André Lureau */ 9915002f60SMarc-André Lureau void loc_restore(Location *loc) 10015002f60SMarc-André Lureau { 10115002f60SMarc-André Lureau Location *prev = cur_loc->prev; 10215002f60SMarc-André Lureau assert(!loc->prev); 10315002f60SMarc-André Lureau *cur_loc = *loc; 10415002f60SMarc-André Lureau cur_loc->prev = prev; 10515002f60SMarc-André Lureau } 10615002f60SMarc-André Lureau 10715002f60SMarc-André Lureau /* 10815002f60SMarc-André Lureau * Change the current location to "nowhere in particular". 10915002f60SMarc-André Lureau */ 11015002f60SMarc-André Lureau void loc_set_none(void) 11115002f60SMarc-André Lureau { 11215002f60SMarc-André Lureau cur_loc->kind = LOC_NONE; 11315002f60SMarc-André Lureau } 11415002f60SMarc-André Lureau 11515002f60SMarc-André Lureau /* 11615002f60SMarc-André Lureau * Change the current location to argument ARGV[IDX..IDX+CNT-1]. 11715002f60SMarc-André Lureau */ 11815002f60SMarc-André Lureau void loc_set_cmdline(char **argv, int idx, int cnt) 11915002f60SMarc-André Lureau { 12015002f60SMarc-André Lureau cur_loc->kind = LOC_CMDLINE; 12115002f60SMarc-André Lureau cur_loc->num = cnt; 12215002f60SMarc-André Lureau cur_loc->ptr = argv + idx; 12315002f60SMarc-André Lureau } 12415002f60SMarc-André Lureau 12515002f60SMarc-André Lureau /* 12615002f60SMarc-André Lureau * Change the current location to file FNAME, line LNO. 12715002f60SMarc-André Lureau */ 12815002f60SMarc-André Lureau void loc_set_file(const char *fname, int lno) 12915002f60SMarc-André Lureau { 13015002f60SMarc-André Lureau assert (fname || cur_loc->kind == LOC_FILE); 13115002f60SMarc-André Lureau cur_loc->kind = LOC_FILE; 13215002f60SMarc-André Lureau cur_loc->num = lno; 13315002f60SMarc-André Lureau if (fname) { 13415002f60SMarc-André Lureau cur_loc->ptr = fname; 13515002f60SMarc-André Lureau } 13615002f60SMarc-André Lureau } 13715002f60SMarc-André Lureau 13815002f60SMarc-André Lureau /* 13915002f60SMarc-André Lureau * Print current location to current monitor if we have one, else to stderr. 14015002f60SMarc-André Lureau */ 14115002f60SMarc-André Lureau static void print_loc(void) 14215002f60SMarc-André Lureau { 14315002f60SMarc-André Lureau const char *sep = ""; 14415002f60SMarc-André Lureau int i; 14515002f60SMarc-André Lureau const char *const *argp; 14615002f60SMarc-André Lureau 14715002f60SMarc-André Lureau if (!monitor_cur() && g_get_prgname()) { 14811fd78dcSMarc-André Lureau error_printf("%s:", g_get_prgname()); 14915002f60SMarc-André Lureau sep = " "; 15015002f60SMarc-André Lureau } 15115002f60SMarc-André Lureau switch (cur_loc->kind) { 15215002f60SMarc-André Lureau case LOC_CMDLINE: 15315002f60SMarc-André Lureau argp = cur_loc->ptr; 15415002f60SMarc-André Lureau for (i = 0; i < cur_loc->num; i++) { 15515002f60SMarc-André Lureau error_printf("%s%s", sep, argp[i]); 15615002f60SMarc-André Lureau sep = " "; 15715002f60SMarc-André Lureau } 15815002f60SMarc-André Lureau error_printf(": "); 15915002f60SMarc-André Lureau break; 16015002f60SMarc-André Lureau case LOC_FILE: 16115002f60SMarc-André Lureau error_printf("%s:", (const char *)cur_loc->ptr); 16215002f60SMarc-André Lureau if (cur_loc->num) { 16315002f60SMarc-André Lureau error_printf("%d:", cur_loc->num); 16415002f60SMarc-André Lureau } 16515002f60SMarc-André Lureau error_printf(" "); 16615002f60SMarc-André Lureau break; 16715002f60SMarc-André Lureau default: 16815002f60SMarc-André Lureau error_printf("%s", sep); 16915002f60SMarc-André Lureau } 17015002f60SMarc-André Lureau } 17115002f60SMarc-André Lureau 17215002f60SMarc-André Lureau static char * 17315002f60SMarc-André Lureau real_time_iso8601(void) 17415002f60SMarc-André Lureau { 17515002f60SMarc-André Lureau #if GLIB_CHECK_VERSION(2,62,0) 176*5c08edd1SHaiyue Wang g_autoptr(GDateTime) dt = g_date_time_new_now_utc(); 177951cc9dfSMarc-André Lureau /* ignore deprecation warning, since GLIB_VERSION_MAX_ALLOWED is 2.56 */ 178951cc9dfSMarc-André Lureau #pragma GCC diagnostic push 179951cc9dfSMarc-André Lureau #pragma GCC diagnostic ignored "-Wdeprecated-declarations" 18015002f60SMarc-André Lureau return g_date_time_format_iso8601(dt); 181951cc9dfSMarc-André Lureau #pragma GCC diagnostic pop 18215002f60SMarc-André Lureau #else 18315002f60SMarc-André Lureau GTimeVal tv; 18415002f60SMarc-André Lureau g_get_current_time(&tv); 18515002f60SMarc-André Lureau return g_time_val_to_iso8601(&tv); 18615002f60SMarc-André Lureau #endif 18715002f60SMarc-André Lureau } 18815002f60SMarc-André Lureau 18915002f60SMarc-André Lureau /* 19015002f60SMarc-André Lureau * Print a message to current monitor if we have one, else to stderr. 19115002f60SMarc-André Lureau * @report_type is the type of message: error, warning or informational. 19215002f60SMarc-André Lureau * Format arguments like vsprintf(). The resulting message should be 19315002f60SMarc-André Lureau * a single phrase, with no newline or trailing punctuation. 19415002f60SMarc-André Lureau * Prepend the current location and append a newline. 19515002f60SMarc-André Lureau */ 19615002f60SMarc-André Lureau static void vreport(report_type type, const char *fmt, va_list ap) 19715002f60SMarc-André Lureau { 19815002f60SMarc-André Lureau gchar *timestr; 19915002f60SMarc-André Lureau 20015002f60SMarc-André Lureau if (message_with_timestamp && !monitor_cur()) { 20115002f60SMarc-André Lureau timestr = real_time_iso8601(); 20215002f60SMarc-André Lureau error_printf("%s ", timestr); 20315002f60SMarc-André Lureau g_free(timestr); 20415002f60SMarc-André Lureau } 20515002f60SMarc-André Lureau 20615002f60SMarc-André Lureau /* Only prepend guest name if -msg guest-name and -name guest=... are set */ 20715002f60SMarc-André Lureau if (error_with_guestname && error_guest_name && !monitor_cur()) { 20815002f60SMarc-André Lureau error_printf("%s ", error_guest_name); 20915002f60SMarc-André Lureau } 21015002f60SMarc-André Lureau 21115002f60SMarc-André Lureau print_loc(); 21215002f60SMarc-André Lureau 21315002f60SMarc-André Lureau switch (type) { 21415002f60SMarc-André Lureau case REPORT_TYPE_ERROR: 21515002f60SMarc-André Lureau break; 21615002f60SMarc-André Lureau case REPORT_TYPE_WARNING: 21715002f60SMarc-André Lureau error_printf("warning: "); 21815002f60SMarc-André Lureau break; 21915002f60SMarc-André Lureau case REPORT_TYPE_INFO: 22015002f60SMarc-André Lureau error_printf("info: "); 22115002f60SMarc-André Lureau break; 22215002f60SMarc-André Lureau } 22315002f60SMarc-André Lureau 22415002f60SMarc-André Lureau error_vprintf(fmt, ap); 22515002f60SMarc-André Lureau error_printf("\n"); 22615002f60SMarc-André Lureau } 22715002f60SMarc-André Lureau 22815002f60SMarc-André Lureau /* 22915002f60SMarc-André Lureau * Print an error message to current monitor if we have one, else to stderr. 23015002f60SMarc-André Lureau * Format arguments like vsprintf(). The resulting message should be 23115002f60SMarc-André Lureau * a single phrase, with no newline or trailing punctuation. 23215002f60SMarc-André Lureau * Prepend the current location and append a newline. 23315002f60SMarc-André Lureau * It's wrong to call this in a QMP monitor. Use error_setg() there. 23415002f60SMarc-André Lureau */ 23515002f60SMarc-André Lureau void error_vreport(const char *fmt, va_list ap) 23615002f60SMarc-André Lureau { 23715002f60SMarc-André Lureau vreport(REPORT_TYPE_ERROR, fmt, ap); 23815002f60SMarc-André Lureau } 23915002f60SMarc-André Lureau 24015002f60SMarc-André Lureau /* 24115002f60SMarc-André Lureau * Print a warning message to current monitor if we have one, else to stderr. 24215002f60SMarc-André Lureau * Format arguments like vsprintf(). The resulting message should be 24315002f60SMarc-André Lureau * a single phrase, with no newline or trailing punctuation. 24415002f60SMarc-André Lureau * Prepend the current location and append a newline. 24515002f60SMarc-André Lureau */ 24615002f60SMarc-André Lureau void warn_vreport(const char *fmt, va_list ap) 24715002f60SMarc-André Lureau { 24815002f60SMarc-André Lureau vreport(REPORT_TYPE_WARNING, fmt, ap); 24915002f60SMarc-André Lureau } 25015002f60SMarc-André Lureau 25115002f60SMarc-André Lureau /* 25215002f60SMarc-André Lureau * Print an information message to current monitor if we have one, else to 25315002f60SMarc-André Lureau * stderr. 25415002f60SMarc-André Lureau * Format arguments like vsprintf(). The resulting message should be 25515002f60SMarc-André Lureau * a single phrase, with no newline or trailing punctuation. 25615002f60SMarc-André Lureau * Prepend the current location and append a newline. 25715002f60SMarc-André Lureau */ 25815002f60SMarc-André Lureau void info_vreport(const char *fmt, va_list ap) 25915002f60SMarc-André Lureau { 26015002f60SMarc-André Lureau vreport(REPORT_TYPE_INFO, fmt, ap); 26115002f60SMarc-André Lureau } 26215002f60SMarc-André Lureau 26315002f60SMarc-André Lureau /* 26415002f60SMarc-André Lureau * Print an error message to current monitor if we have one, else to stderr. 26515002f60SMarc-André Lureau * Format arguments like sprintf(). The resulting message should be 26615002f60SMarc-André Lureau * a single phrase, with no newline or trailing punctuation. 26715002f60SMarc-André Lureau * Prepend the current location and append a newline. 26815002f60SMarc-André Lureau * It's wrong to call this in a QMP monitor. Use error_setg() there. 26915002f60SMarc-André Lureau */ 27015002f60SMarc-André Lureau void error_report(const char *fmt, ...) 27115002f60SMarc-André Lureau { 27215002f60SMarc-André Lureau va_list ap; 27315002f60SMarc-André Lureau 27415002f60SMarc-André Lureau va_start(ap, fmt); 27515002f60SMarc-André Lureau vreport(REPORT_TYPE_ERROR, fmt, ap); 27615002f60SMarc-André Lureau va_end(ap); 27715002f60SMarc-André Lureau } 27815002f60SMarc-André Lureau 27915002f60SMarc-André Lureau /* 28015002f60SMarc-André Lureau * Print a warning message to current monitor if we have one, else to stderr. 28115002f60SMarc-André Lureau * Format arguments like sprintf(). The resulting message should be a 28215002f60SMarc-André Lureau * single phrase, with no newline or trailing punctuation. 28315002f60SMarc-André Lureau * Prepend the current location and append a newline. 28415002f60SMarc-André Lureau */ 28515002f60SMarc-André Lureau void warn_report(const char *fmt, ...) 28615002f60SMarc-André Lureau { 28715002f60SMarc-André Lureau va_list ap; 28815002f60SMarc-André Lureau 28915002f60SMarc-André Lureau va_start(ap, fmt); 29015002f60SMarc-André Lureau vreport(REPORT_TYPE_WARNING, fmt, ap); 29115002f60SMarc-André Lureau va_end(ap); 29215002f60SMarc-André Lureau } 29315002f60SMarc-André Lureau 29415002f60SMarc-André Lureau /* 29515002f60SMarc-André Lureau * Print an information message to current monitor if we have one, else to 29615002f60SMarc-André Lureau * stderr. 29715002f60SMarc-André Lureau * Format arguments like sprintf(). The resulting message should be a 29815002f60SMarc-André Lureau * single phrase, with no newline or trailing punctuation. 29915002f60SMarc-André Lureau * Prepend the current location and append a newline. 30015002f60SMarc-André Lureau */ 30115002f60SMarc-André Lureau void info_report(const char *fmt, ...) 30215002f60SMarc-André Lureau { 30315002f60SMarc-André Lureau va_list ap; 30415002f60SMarc-André Lureau 30515002f60SMarc-André Lureau va_start(ap, fmt); 30615002f60SMarc-André Lureau vreport(REPORT_TYPE_INFO, fmt, ap); 30715002f60SMarc-André Lureau va_end(ap); 30815002f60SMarc-André Lureau } 30915002f60SMarc-André Lureau 31015002f60SMarc-André Lureau /* 31115002f60SMarc-André Lureau * Like error_report(), except print just once. 31215002f60SMarc-André Lureau * If *printed is false, print the message, and flip *printed to true. 31315002f60SMarc-André Lureau * Return whether the message was printed. 31415002f60SMarc-André Lureau */ 31515002f60SMarc-André Lureau bool error_report_once_cond(bool *printed, const char *fmt, ...) 31615002f60SMarc-André Lureau { 31715002f60SMarc-André Lureau va_list ap; 31815002f60SMarc-André Lureau 31915002f60SMarc-André Lureau assert(printed); 32015002f60SMarc-André Lureau if (*printed) { 32115002f60SMarc-André Lureau return false; 32215002f60SMarc-André Lureau } 32315002f60SMarc-André Lureau *printed = true; 32415002f60SMarc-André Lureau va_start(ap, fmt); 32515002f60SMarc-André Lureau vreport(REPORT_TYPE_ERROR, fmt, ap); 32615002f60SMarc-André Lureau va_end(ap); 32715002f60SMarc-André Lureau return true; 32815002f60SMarc-André Lureau } 32915002f60SMarc-André Lureau 33015002f60SMarc-André Lureau /* 33115002f60SMarc-André Lureau * Like warn_report(), except print just once. 33215002f60SMarc-André Lureau * If *printed is false, print the message, and flip *printed to true. 33315002f60SMarc-André Lureau * Return whether the message was printed. 33415002f60SMarc-André Lureau */ 33515002f60SMarc-André Lureau bool warn_report_once_cond(bool *printed, const char *fmt, ...) 33615002f60SMarc-André Lureau { 33715002f60SMarc-André Lureau va_list ap; 33815002f60SMarc-André Lureau 33915002f60SMarc-André Lureau assert(printed); 34015002f60SMarc-André Lureau if (*printed) { 34115002f60SMarc-André Lureau return false; 34215002f60SMarc-André Lureau } 34315002f60SMarc-André Lureau *printed = true; 34415002f60SMarc-André Lureau va_start(ap, fmt); 34515002f60SMarc-André Lureau vreport(REPORT_TYPE_WARNING, fmt, ap); 34615002f60SMarc-André Lureau va_end(ap); 34715002f60SMarc-André Lureau return true; 34815002f60SMarc-André Lureau } 34915002f60SMarc-André Lureau 35015002f60SMarc-André Lureau static char *qemu_glog_domains; 35115002f60SMarc-André Lureau 35215002f60SMarc-André Lureau static void qemu_log_func(const gchar *log_domain, 35315002f60SMarc-André Lureau GLogLevelFlags log_level, 35415002f60SMarc-André Lureau const gchar *message, 35515002f60SMarc-André Lureau gpointer user_data) 35615002f60SMarc-André Lureau { 35715002f60SMarc-André Lureau switch (log_level & G_LOG_LEVEL_MASK) { 35815002f60SMarc-André Lureau case G_LOG_LEVEL_DEBUG: 35915002f60SMarc-André Lureau case G_LOG_LEVEL_INFO: 36015002f60SMarc-André Lureau /* 36115002f60SMarc-André Lureau * Use same G_MESSAGES_DEBUG logic as glib to enable/disable debug 36215002f60SMarc-André Lureau * messages 36315002f60SMarc-André Lureau */ 36415002f60SMarc-André Lureau if (qemu_glog_domains == NULL) { 36515002f60SMarc-André Lureau break; 36615002f60SMarc-André Lureau } 36715002f60SMarc-André Lureau if (strcmp(qemu_glog_domains, "all") != 0 && 36815002f60SMarc-André Lureau (log_domain == NULL || !strstr(qemu_glog_domains, log_domain))) { 36915002f60SMarc-André Lureau break; 37015002f60SMarc-André Lureau } 37115002f60SMarc-André Lureau /* Fall through */ 37215002f60SMarc-André Lureau case G_LOG_LEVEL_MESSAGE: 37315002f60SMarc-André Lureau info_report("%s%s%s", 37415002f60SMarc-André Lureau log_domain ?: "", log_domain ? ": " : "", message); 37515002f60SMarc-André Lureau 37615002f60SMarc-André Lureau break; 37715002f60SMarc-André Lureau case G_LOG_LEVEL_WARNING: 37815002f60SMarc-André Lureau warn_report("%s%s%s", 37915002f60SMarc-André Lureau log_domain ?: "", log_domain ? ": " : "", message); 38015002f60SMarc-André Lureau break; 38115002f60SMarc-André Lureau case G_LOG_LEVEL_CRITICAL: 38215002f60SMarc-André Lureau case G_LOG_LEVEL_ERROR: 38315002f60SMarc-André Lureau error_report("%s%s%s", 38415002f60SMarc-André Lureau log_domain ?: "", log_domain ? ": " : "", message); 38515002f60SMarc-André Lureau break; 38615002f60SMarc-André Lureau } 38715002f60SMarc-André Lureau } 38815002f60SMarc-André Lureau 38915002f60SMarc-André Lureau void error_init(const char *argv0) 39015002f60SMarc-André Lureau { 39115002f60SMarc-André Lureau const char *p = strrchr(argv0, '/'); 39215002f60SMarc-André Lureau 39315002f60SMarc-André Lureau /* Set the program name for error_print_loc(). */ 39415002f60SMarc-André Lureau g_set_prgname(p ? p + 1 : argv0); 39515002f60SMarc-André Lureau 39615002f60SMarc-André Lureau /* 39715002f60SMarc-André Lureau * This sets up glib logging so libraries using it also print their logs 39815002f60SMarc-André Lureau * through error_report(), warn_report(), info_report(). 39915002f60SMarc-André Lureau */ 40015002f60SMarc-André Lureau g_log_set_default_handler(qemu_log_func, NULL); 40115002f60SMarc-André Lureau g_warn_if_fail(qemu_glog_domains == NULL); 40215002f60SMarc-André Lureau qemu_glog_domains = g_strdup(g_getenv("G_MESSAGES_DEBUG")); 40315002f60SMarc-André Lureau } 404