VirtualBox

source: vbox/trunk/src/VBox/Additions/x11/VBoxClient/logging.cpp

Last change on this file was 99689, checked in by vboxsync, 12 months ago

Guest Additions/VBoxClient: Dropped the idea of having an own logging facility (VBGHLogXXX) for shared guest/host code again; the (release) logger is flexible enough for this. bugref:10427

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 15.9 KB
Line 
1/* $Id: logging.cpp 99689 2023-05-09 08:28:38Z vboxsync $ */
2/** @file
3 * VirtualBox Guest Additions - X11 Client.
4 */
5
6/*
7 * Copyright (C) 2006-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28
29#include <sys/wait.h>
30#include <stdlib.h>
31
32#include <iprt/buildconfig.h>
33#include <iprt/file.h>
34#include <iprt/message.h>
35#include <iprt/process.h>
36#include <iprt/stream.h>
37#include <iprt/system.h>
38
39#ifdef VBOX_WITH_DBUS
40# include <VBox/dbus.h>
41#endif
42#include <VBox/VBoxGuestLib.h>
43
44#include <package-generated.h>
45#include "VBoxClient.h"
46
47/** Logging parameters. */
48/** @todo Make this configurable later. */
49static PRTLOGGER g_pLoggerRelease = NULL;
50static uint32_t g_cHistory = 10; /* Enable log rotation, 10 files. */
51static uint32_t g_uHistoryFileTime = RT_SEC_1DAY; /* Max 1 day per file. */
52static uint64_t g_uHistoryFileSize = 100 * _1M; /* Max 100MB per file. */
53
54/** Custom log prefix (to be set externally). */
55static char *g_pszCustomLogPrefix;
56
57extern unsigned g_cRespawn;
58
59
60/**
61 * Fallback notification helper using 'notify-send'.
62 *
63 * @returns VBox status code.
64 * @returns VERR_NOT_SUPPORTED if 'notify-send' is not available, or there was an error while running 'notify-send'.
65 * @param pszMessage Message to notify desktop environment with.
66 */
67int vbclNotifyFallbackNotifySend(const char *pszMessage)
68{
69 AssertPtrReturn(pszMessage, VERR_INVALID_POINTER);
70
71 int rc = VINF_SUCCESS;
72
73 if (g_cRespawn == 0)
74 {
75 char *pszCommand = RTStrAPrintf2("notify-send \"VBoxClient: %s\"", pszMessage);
76 if (pszCommand)
77 {
78 int status = system(pszCommand);
79
80 RTStrFree(pszCommand);
81
82 if (WEXITSTATUS(status) != 0) /* Utility or extension not available. */
83 {
84 pszCommand = RTStrAPrintf2("xmessage -buttons OK:0 -center \"VBoxClient: %s\"",
85 pszMessage);
86 if (pszCommand)
87 {
88 status = system(pszCommand);
89 if (WEXITSTATUS(status) != 0) /* Utility or extension not available. */
90 rc = VERR_NOT_SUPPORTED;
91
92 RTStrFree(pszCommand);
93 }
94 else
95 rc = VERR_NO_MEMORY;
96 }
97 }
98 else
99 rc = VERR_NO_MEMORY;
100 }
101
102 return rc;
103}
104
105/**
106 * Shows a notification on the desktop.
107 *
108 * @returns VBox status code.
109 * @returns VERR_NOT_SUPPORTED if the current desktop environment is not supported.
110 * @param pszHeader Header text to show.
111 * @param pszBody Body text to show.
112 *
113 * @note How this notification will look like depends on the actual desktop environment implementing
114 * the actual notification service. Currently only D-BUS-compatible environments are supported.
115 *
116 * Most notification implementations have length limits on their header / body texts, so keep
117 * the text(s) short.
118 */
119int VBClShowNotify(const char *pszHeader, const char *pszBody)
120{
121 AssertPtrReturn(pszHeader, VERR_INVALID_POINTER);
122 AssertPtrReturn(pszBody, VERR_INVALID_POINTER);
123
124 int rc;
125# ifdef VBOX_WITH_DBUS
126 rc = RTDBusLoadLib(); /** @todo Does this init / load the lib only once? Need to check this. */
127 if (RT_FAILURE(rc))
128 {
129 VBClLogError("D-Bus seems not to be installed; no desktop notifications available\n");
130 return rc;
131 }
132
133 DBusConnection *conn;
134 DBusMessage* msg = NULL;
135 conn = dbus_bus_get(DBUS_BUS_SESSION, NULL);
136 if (conn == NULL)
137 {
138 VBClLogError("Could not retrieve D-BUS session bus\n");
139 rc = VERR_INVALID_HANDLE;
140 }
141 else
142 {
143 msg = dbus_message_new_method_call("org.freedesktop.Notifications",
144 "/org/freedesktop/Notifications",
145 "org.freedesktop.Notifications",
146 "Notify");
147 if (msg == NULL)
148 {
149 VBClLogError("Could not create D-BUS message!\n");
150 rc = VERR_INVALID_HANDLE;
151 }
152 else
153 rc = VINF_SUCCESS;
154 }
155 if (RT_SUCCESS(rc))
156 {
157 uint32_t msg_replace_id = 0;
158 const char *msg_app = "VBoxClient";
159 const char *msg_icon = "";
160 const char *msg_summary = pszHeader;
161 const char *msg_body = pszBody;
162 int32_t msg_timeout = -1; /* Let the notification server decide */
163
164 DBusMessageIter iter;
165 DBusMessageIter array;
166 /*DBusMessageIter dict; - unused */
167 /*DBusMessageIter value; - unused */
168 /*DBusMessageIter variant; - unused */
169 /*DBusMessageIter data; - unused */
170
171 /* Format: UINT32 org.freedesktop.Notifications.Notify
172 * (STRING app_name, UINT32 replaces_id, STRING app_icon, STRING summary, STRING body,
173 * ARRAY actions, DICT hints, INT32 expire_timeout)
174 */
175 dbus_message_iter_init_append(msg,&iter);
176 dbus_message_iter_append_basic(&iter,DBUS_TYPE_STRING,&msg_app);
177 dbus_message_iter_append_basic(&iter,DBUS_TYPE_UINT32,&msg_replace_id);
178 dbus_message_iter_append_basic(&iter,DBUS_TYPE_STRING,&msg_icon);
179 dbus_message_iter_append_basic(&iter,DBUS_TYPE_STRING,&msg_summary);
180 dbus_message_iter_append_basic(&iter,DBUS_TYPE_STRING,&msg_body);
181 dbus_message_iter_open_container(&iter,DBUS_TYPE_ARRAY,DBUS_TYPE_STRING_AS_STRING,&array);
182 dbus_message_iter_close_container(&iter,&array);
183 dbus_message_iter_open_container(&iter,DBUS_TYPE_ARRAY,"{sv}",&array);
184 dbus_message_iter_close_container(&iter,&array);
185 dbus_message_iter_append_basic(&iter,DBUS_TYPE_INT32,&msg_timeout);
186
187 DBusError err;
188 dbus_error_init(&err);
189
190 DBusMessage *reply;
191 reply = dbus_connection_send_with_reply_and_block(conn, msg, 30 * 1000 /* 30 seconds timeout */, &err);
192 if (dbus_error_is_set(&err))
193 VBClLogError("D-BUS returned an error while sending the notification: %s", err.message);
194 else if (reply)
195 {
196 dbus_connection_flush(conn);
197 dbus_message_unref(reply);
198 }
199 if (dbus_error_is_set(&err))
200 dbus_error_free(&err);
201 }
202 if (msg != NULL)
203 dbus_message_unref(msg);
204# else
205 /** @todo Implement me */
206 RT_NOREF(pszHeader, pszBody);
207 rc = VERR_NOT_SUPPORTED;
208# endif /* VBOX_WITH_DBUS */
209
210 /* Try to use a fallback if the stuff above fails or is not available. */
211 if (RT_FAILURE(rc))
212 rc = vbclNotifyFallbackNotifySend(pszBody);
213
214 /* If everything fails, still print out our notification to stdout, in the hope
215 * someone still gets aware of it. */
216 if (RT_FAILURE(rc))
217 VBClLogInfo("*** Notification: %s - %s ***\n", pszHeader, pszBody);
218
219 return rc;
220}
221
222/**
223 * Logs a message with a given prefix, format string and a va_list.
224 *
225 * @param pszPrefix Log prefix to use.
226 * @param pszFormat Format string to use.
227 * @param va va_list to use.
228 */
229static void vbclLogV(const char *pszPrefix, const char *pszFormat, va_list va)
230{
231 char *psz = NULL;
232 RTStrAPrintfV(&psz, pszFormat, va);
233 AssertPtrReturnVoid(psz);
234 LogRel(("%s%s", pszPrefix ? pszPrefix : "", psz));
235 RTStrFree(psz);
236}
237
238/**
239 * Logs a fatal error, notifies the desktop environment via a message and
240 * exits the application immediately.
241 *
242 * @param pszFormat Format string to log.
243 * @param ... Variable arguments for format string. Optional.
244 */
245void VBClLogFatalError(const char *pszFormat, ...)
246{
247 va_list va;
248 va_start(va, pszFormat);
249 vbclLogV("Fatal Error: ", pszFormat, va);
250 va_end(va);
251}
252
253/**
254 * Logs an error message to the (release) logging instance.
255 *
256 * @param pszFormat Format string to log.
257 */
258void VBClLogError(const char *pszFormat, ...)
259{
260 va_list va;
261 va_start(va, pszFormat);
262 vbclLogV("Error: ", pszFormat, va);
263 va_end(va);
264}
265
266/**
267 * Logs an info message to the (release) logging instance.
268 *
269 * @param pszFormat Format string to log.
270 */
271void VBClLogInfo(const char *pszFormat, ...)
272{
273 va_list va;
274 va_start(va, pszFormat);
275 vbclLogV("", pszFormat, va);
276 va_end(va);
277}
278
279/**
280 * Displays a verbose message based on the currently
281 * set global verbosity level.
282 *
283 * @param iLevel Minimum log level required to display this message.
284 * @param pszFormat The message text.
285 * @param ... Format arguments.
286 */
287void VBClLogVerbose(unsigned iLevel, const char *pszFormat, ...)
288{
289 va_list va;
290 va_start(va, pszFormat);
291 if (iLevel <= g_cVerbosity)
292 vbclLogV("", pszFormat, va);
293 va_end(va);
294}
295
296/**
297 * @callback_method_impl{FNRTLOGPHASE, Release logger callback}
298 */
299static DECLCALLBACK(void) vbClLogHeaderFooter(PRTLOGGER pLoggerRelease, RTLOGPHASE enmPhase, PFNRTLOGPHASEMSG pfnLog)
300{
301 /* Some introductory information. */
302 static RTTIMESPEC s_TimeSpec;
303 char szTmp[256];
304 if (enmPhase == RTLOGPHASE_BEGIN)
305 RTTimeNow(&s_TimeSpec);
306 RTTimeSpecToString(&s_TimeSpec, szTmp, sizeof(szTmp));
307
308 switch (enmPhase)
309 {
310 case RTLOGPHASE_BEGIN:
311 {
312 pfnLog(pLoggerRelease,
313 "VBoxClient %s r%s (verbosity: %u) %s (%s %s) release log\n"
314 "Log opened %s\n",
315 RTBldCfgVersion(), RTBldCfgRevisionStr(), g_cVerbosity, VBOX_BUILD_TARGET,
316 __DATE__, __TIME__, szTmp);
317
318 int vrc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szTmp, sizeof(szTmp));
319 if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
320 pfnLog(pLoggerRelease, "OS Product: %s\n", szTmp);
321 vrc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szTmp, sizeof(szTmp));
322 if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
323 pfnLog(pLoggerRelease, "OS Release: %s\n", szTmp);
324 vrc = RTSystemQueryOSInfo(RTSYSOSINFO_VERSION, szTmp, sizeof(szTmp));
325 if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
326 pfnLog(pLoggerRelease, "OS Version: %s\n", szTmp);
327 vrc = RTSystemQueryOSInfo(RTSYSOSINFO_SERVICE_PACK, szTmp, sizeof(szTmp));
328 if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
329 pfnLog(pLoggerRelease, "OS Service Pack: %s\n", szTmp);
330
331 /* the package type is interesting for Linux distributions */
332 char szExecName[RTPATH_MAX];
333 char *pszExecName = RTProcGetExecutablePath(szExecName, sizeof(szExecName));
334 pfnLog(pLoggerRelease,
335 "Executable: %s\n"
336 "Process ID: %u\n"
337 "Package type: %s"
338#ifdef VBOX_OSE
339 " (OSE)"
340#endif
341 "\n",
342 pszExecName ? pszExecName : "unknown",
343 RTProcSelf(),
344 VBOX_PACKAGE_STRING);
345 break;
346 }
347
348 case RTLOGPHASE_PREROTATE:
349 pfnLog(pLoggerRelease, "Log rotated - Log started %s\n", szTmp);
350 break;
351
352 case RTLOGPHASE_POSTROTATE:
353 pfnLog(pLoggerRelease, "Log continuation - Log started %s\n", szTmp);
354 break;
355
356 case RTLOGPHASE_END:
357 pfnLog(pLoggerRelease, "End of log file - Log started %s\n", szTmp);
358 break;
359
360 default:
361 /* nothing */
362 break;
363 }
364}
365
366static DECLCALLBACK(size_t) vbClLogPrefixCb(PRTLOGGER pLogger, char *pchBuf, size_t cchBuf, void *pvUser)
367{
368 size_t cbPrefix = 0;
369
370 RT_NOREF(pLogger);
371 RT_NOREF(pvUser);
372
373 if (g_pszCustomLogPrefix)
374 {
375 cbPrefix = RT_MIN(strlen(g_pszCustomLogPrefix), cchBuf);
376 memcpy(pchBuf, g_pszCustomLogPrefix, cbPrefix);
377 }
378
379 return cbPrefix;
380}
381
382/**
383 * Creates the default release logger outputting to the specified file, extended version.
384 *
385 * @return IPRT status code.
386 * @param pszLogFile Filename for log output. Empty filename allowed.
387 * @param fPrintHeader Whether to print the VBoxClient logging header or not.
388 */
389int VBClLogCreateEx(const char *pszLogFile, bool fPrintHeader)
390{
391 if (!pszLogFile)
392 return VINF_SUCCESS;
393
394 /* Create release logger (stdout + file). */
395 static const char * const s_apszGroups[] = VBOX_LOGGROUP_NAMES;
396 RTUINT fFlags = RTLOGFLAGS_PREFIX_THREAD | RTLOGFLAGS_PREFIX_TIME | RTLOGFLAGS_PREFIX_CUSTOM;
397#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
398 fFlags |= RTLOGFLAGS_USECRLF;
399#endif
400 int rc = RTLogCreateEx(&g_pLoggerRelease, "VBOXCLIENT_RELEASE_LOG", fFlags, "all",
401 RT_ELEMENTS(s_apszGroups), s_apszGroups, UINT32_MAX /*cMaxEntriesPerGroup*/,
402 0 /*cBufDescs*/, NULL /*paBufDescs*/, RTLOGDEST_STDOUT | RTLOGDEST_USER,
403 fPrintHeader ? vbClLogHeaderFooter : NULL, g_cHistory, g_uHistoryFileSize, g_uHistoryFileTime,
404 NULL /*pOutputIf*/, NULL /*pvOutputIfUser*/,
405 NULL /*pErrInfo*/, "%s", pszLogFile ? pszLogFile : "");
406 if (RT_SUCCESS(rc))
407 {
408 /* register this logger as the release logger */
409 RTLogRelSetDefaultInstance(g_pLoggerRelease);
410
411 rc = RTLogSetCustomPrefixCallback(g_pLoggerRelease, vbClLogPrefixCb, NULL);
412 if (RT_FAILURE(rc))
413 RTMsgError("unable to register custom log prefix callback\n");
414
415 /* Explicitly flush the log in case of VBOXSERVICE_RELEASE_LOG=buffered. */
416 RTLogFlush(g_pLoggerRelease);
417 }
418 else
419 RTMsgError("Failed to create release log '%s', rc=%Rrc\n", pszLogFile, rc);
420
421 return rc;
422}
423
424/**
425 * Creates the default release logger outputting to the specified file.
426 *
427 * @return IPRT status code.
428 * @param pszLogFile Filename for log output. Empty filename allowed.
429 */
430int VBClLogCreate(const char *pszLogFile)
431{
432 return VBClLogCreateEx(pszLogFile, true /* fPrintHeader */);
433}
434
435/**
436 * Destroys the currently active logging instance.
437 */
438void VBClLogDestroy(void)
439{
440 RTLogDestroy(RTLogRelSetDefaultInstance(NULL));
441}
442
443/**
444 * Modifies the (release) log settings.
445 *
446 * @returns VBox status code.
447 * @param pszDest Log destination string to set.
448 * @param uVerbosity Verbosity level to set.
449 *
450 * @note Errors will be logged to stderr.
451 */
452int VBClLogModify(const char *pszDest, unsigned uVerbosity)
453{
454 AssertPtrReturn(pszDest, VERR_INVALID_POINTER);
455 AssertPtrReturn(g_pLoggerRelease, VERR_INVALID_POINTER);
456
457 int rc = RTLogDestinations(g_pLoggerRelease, pszDest);
458 if (RT_SUCCESS(rc))
459 {
460#define LOG_GROUP_SET_BREAK(a_Val) \
461 rc = RTLogGroupSettings(g_pLoggerRelease, "all.e" a_Val); break;
462
463 switch (uVerbosity)
464 {
465 case 0: LOG_GROUP_SET_BREAK("");
466 case 1: LOG_GROUP_SET_BREAK(".l");
467 case 2: LOG_GROUP_SET_BREAK(".l.l2");
468 case 3: LOG_GROUP_SET_BREAK(".l.l2.l3");
469 default: LOG_GROUP_SET_BREAK(".l.l2.l3.l4");
470 }
471#undef LOG_GROUP_SET_BREAK
472 }
473
474 if (RT_FAILURE(rc)) /* Print to stderr in the hope that anyone can read this. */
475 RTMsgError("Failed to set/modify log output, rc=%Rrc", rc);
476
477 return rc;
478}
479
480/**
481 * Set custom log prefix.
482 *
483 * @param pszPrefix Custom log prefix string.
484 */
485void VBClLogSetLogPrefix(const char *pszPrefix)
486{
487 g_pszCustomLogPrefix = (char *)pszPrefix;
488}
489
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use