VirtualBox

source: vbox/trunk/src/VBox/Main/src-server/UpdateAgentImpl.cpp@ 94756

Last change on this file since 94756 was 94756, checked in by vboxsync, 3 years ago

Main/VBoxManage/Update check: More code + docs for proxy settings handling. bugref:7983

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 36.3 KB
Line 
1/* $Id: UpdateAgentImpl.cpp 94756 2022-04-29 08:55:44Z vboxsync $ */
2/** @file
3 * IUpdateAgent COM class implementations.
4 */
5
6/*
7 * Copyright (C) 2020-2022 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19#define LOG_GROUP LOG_GROUP_MAIN_UPDATEAGENT
20
21#include <iprt/cpp/utils.h>
22#include <iprt/param.h>
23#include <iprt/path.h>
24#include <iprt/http.h>
25#include <iprt/system.h>
26#include <iprt/message.h>
27#include <iprt/pipe.h>
28#include <iprt/env.h>
29#include <iprt/process.h>
30#include <iprt/assert.h>
31#include <iprt/err.h>
32#include <iprt/stream.h>
33#include <iprt/time.h>
34#include <VBox/com/defs.h>
35#include <VBox/err.h>
36#include <VBox/version.h>
37
38#include "HostImpl.h"
39#include "UpdateAgentImpl.h"
40#include "ProgressImpl.h"
41#include "AutoCaller.h"
42#include "LoggingNew.h"
43#include "VirtualBoxImpl.h"
44#include "VBoxEvents.h"
45#include "SystemPropertiesImpl.h"
46#include "ThreadTask.h"
47#include "VirtualBoxImpl.h"
48#include "VirtualBoxBase.h"
49
50
51/*********************************************************************************************************************************
52* Update agent task implementation *
53*********************************************************************************************************************************/
54
55/**
56 * Base task class for asynchronous update agent tasks.
57 */
58class UpdateAgentTask : public ThreadTask
59{
60public:
61 UpdateAgentTask(UpdateAgentBase *aThat, Progress *aProgress)
62 : m_pParent(aThat)
63 , m_pProgress(aProgress)
64 {
65 m_strTaskName = "UpdateAgentTask";
66 }
67 virtual ~UpdateAgentTask(void) { }
68
69private:
70 void handler(void);
71
72 /** Weak pointer to parent (update agent). */
73 UpdateAgentBase *m_pParent;
74 /** Smart pointer to the progress object for this job. */
75 ComObjPtr<Progress> m_pProgress;
76
77 friend class UpdateAgent; // allow member functions access to private data
78};
79
80void UpdateAgentTask::handler(void)
81{
82 UpdateAgentBase *pUpdateAgent = this->m_pParent;
83 AssertPtr(pUpdateAgent);
84
85 /** @todo Differentiate tasks once we have more stuff to do (downloading, installing, ++). */
86
87 HRESULT rc = pUpdateAgent->i_checkForUpdateTask(this);
88
89 if (!m_pProgress.isNull())
90 m_pProgress->i_notifyComplete(rc);
91
92 LogFlowFunc(("rc=%Rhrc\n", rc)); RT_NOREF(rc);
93}
94
95
96/*********************************************************************************************************************************
97* Update agent base class implementation *
98*********************************************************************************************************************************/
99
100/**
101 * Returns platform information as a string.
102 *
103 * @returns Platform information as string.
104 */
105/* static */
106Utf8Str UpdateAgentBase::i_getPlatformInfo(void)
107{
108 /* Prepare platform report: */
109 Utf8Str strPlatform;
110
111# if defined (RT_OS_WINDOWS)
112 strPlatform = "win";
113# elif defined (RT_OS_LINUX)
114 strPlatform = "linux";
115# elif defined (RT_OS_DARWIN)
116 strPlatform = "macosx";
117# elif defined (RT_OS_OS2)
118 strPlatform = "os2";
119# elif defined (RT_OS_FREEBSD)
120 strPlatform = "freebsd";
121# elif defined (RT_OS_SOLARIS)
122 strPlatform = "solaris";
123# else
124 strPlatform = "unknown";
125# endif
126
127 /* The format is <system>.<bitness>: */
128 strPlatform.appendPrintf(".%lu", ARCH_BITS);
129
130 /* Add more system information: */
131 int vrc;
132# ifdef RT_OS_LINUX
133 // WORKAROUND:
134 // On Linux we try to generate information using script first of all..
135
136 /* Get script path: */
137 char szAppPrivPath[RTPATH_MAX];
138 vrc = RTPathAppPrivateNoArch(szAppPrivPath, sizeof(szAppPrivPath));
139 AssertRC(vrc);
140 if (RT_SUCCESS(vrc))
141 vrc = RTPathAppend(szAppPrivPath, sizeof(szAppPrivPath), "/VBoxSysInfo.sh");
142 AssertRC(vrc);
143 if (RT_SUCCESS(vrc))
144 {
145 RTPIPE hPipeR;
146 RTHANDLE hStdOutPipe;
147 hStdOutPipe.enmType = RTHANDLETYPE_PIPE;
148 vrc = RTPipeCreate(&hPipeR, &hStdOutPipe.u.hPipe, RTPIPE_C_INHERIT_WRITE);
149 AssertLogRelRC(vrc);
150
151 char const *szAppPrivArgs[2];
152 szAppPrivArgs[0] = szAppPrivPath;
153 szAppPrivArgs[1] = NULL;
154 RTPROCESS hProc = NIL_RTPROCESS;
155
156 /* Run script: */
157 vrc = RTProcCreateEx(szAppPrivPath, szAppPrivArgs, RTENV_DEFAULT, 0 /*fFlags*/, NULL /*phStdin*/, &hStdOutPipe,
158 NULL /*phStderr*/, NULL /*pszAsUser*/, NULL /*pszPassword*/, NULL /*pvExtraData*/, &hProc);
159
160 (void) RTPipeClose(hStdOutPipe.u.hPipe);
161 hStdOutPipe.u.hPipe = NIL_RTPIPE;
162
163 if (RT_SUCCESS(vrc))
164 {
165 RTPROCSTATUS ProcStatus;
166 size_t cbStdOutBuf = 0;
167 size_t offStdOutBuf = 0;
168 char *pszStdOutBuf = NULL;
169 do
170 {
171 if (hPipeR != NIL_RTPIPE)
172 {
173 char achBuf[1024];
174 size_t cbRead;
175 vrc = RTPipeReadBlocking(hPipeR, achBuf, sizeof(achBuf), &cbRead);
176 if (RT_SUCCESS(vrc))
177 {
178 /* grow the buffer? */
179 size_t cbBufReq = offStdOutBuf + cbRead + 1;
180 if ( cbBufReq > cbStdOutBuf
181 && cbBufReq < _256K)
182 {
183 size_t cbNew = RT_ALIGN_Z(cbBufReq, 16); // 1024
184 void *pvNew = RTMemRealloc(pszStdOutBuf, cbNew);
185 if (pvNew)
186 {
187 pszStdOutBuf = (char *)pvNew;
188 cbStdOutBuf = cbNew;
189 }
190 }
191
192 /* append if we've got room. */
193 if (cbBufReq <= cbStdOutBuf)
194 {
195 (void) memcpy(&pszStdOutBuf[offStdOutBuf], achBuf, cbRead);
196 offStdOutBuf = offStdOutBuf + cbRead;
197 pszStdOutBuf[offStdOutBuf] = '\0';
198 }
199 }
200 else
201 {
202 AssertLogRelMsg(vrc == VERR_BROKEN_PIPE, ("%Rrc\n", vrc));
203 RTPipeClose(hPipeR);
204 hPipeR = NIL_RTPIPE;
205 }
206 }
207
208 /*
209 * Service the process. Block if we have no pipe.
210 */
211 if (hProc != NIL_RTPROCESS)
212 {
213 vrc = RTProcWait(hProc,
214 hPipeR == NIL_RTPIPE ? RTPROCWAIT_FLAGS_BLOCK : RTPROCWAIT_FLAGS_NOBLOCK,
215 &ProcStatus);
216 if (RT_SUCCESS(vrc))
217 hProc = NIL_RTPROCESS;
218 else
219 AssertLogRelMsgStmt(vrc == VERR_PROCESS_RUNNING, ("%Rrc\n", vrc), hProc = NIL_RTPROCESS);
220 }
221 } while ( hPipeR != NIL_RTPIPE
222 || hProc != NIL_RTPROCESS);
223
224 if ( ProcStatus.enmReason == RTPROCEXITREASON_NORMAL
225 && ProcStatus.iStatus == 0) {
226 pszStdOutBuf[offStdOutBuf-1] = '\0'; // remove trailing newline
227 Utf8Str pszStdOutBufUTF8(pszStdOutBuf);
228 strPlatform.appendPrintf(" [%s]", pszStdOutBufUTF8.strip().c_str());
229 // For testing, here is some sample output:
230 //strPlatform.appendPrintf(" [Distribution: Redhat | Version: 7.6.1810 | Kernel: Linux version 3.10.0-952.27.2.el7.x86_64 (gcc version 4.8.5 20150623 (Red Hat 4.8.5-36) (GCC) ) #1 SMP Mon Jul 29 17:46:05 UTC 2019]");
231 }
232 }
233 else
234 vrc = VERR_TRY_AGAIN; /* (take the fallback path) */
235 }
236
237 if (RT_FAILURE(vrc))
238# endif /* RT_OS_LINUX */
239 {
240 /* Use RTSystemQueryOSInfo: */
241 char szTmp[256];
242
243 vrc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szTmp, sizeof(szTmp));
244 if ((RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW) && szTmp[0] != '\0')
245 strPlatform.appendPrintf(" [Product: %s", szTmp);
246
247 vrc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szTmp, sizeof(szTmp));
248 if ((RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW) && szTmp[0] != '\0')
249 strPlatform.appendPrintf(" %sRelease: %s", strlen(szTmp) == 0 ? "[" : "| ", szTmp);
250
251 vrc = RTSystemQueryOSInfo(RTSYSOSINFO_VERSION, szTmp, sizeof(szTmp));
252 if ((RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW) && szTmp[0] != '\0')
253 strPlatform.appendPrintf(" %sVersion: %s", strlen(szTmp) == 0 ? "[" : "| ", szTmp);
254
255 vrc = RTSystemQueryOSInfo(RTSYSOSINFO_SERVICE_PACK, szTmp, sizeof(szTmp));
256 if ((RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW) && szTmp[0] != '\0')
257 strPlatform.appendPrintf(" %sSP: %s]", strlen(szTmp) == 0 ? "[" : "| ", szTmp);
258
259 if (!strPlatform.endsWith("]"))
260 strPlatform.append("]");
261 }
262
263 LogRel2(("UpdateAgent: Platform is '%s'\n", strPlatform.c_str()));
264
265 return strPlatform;
266}
267
268/**
269 * Returns the proxy mode as a string.
270 *
271 * @returns Proxy mode as string.
272 * @param enmMode Proxy mode to return as string.
273 */
274/* static */
275const char *UpdateAgentBase::i_proxyModeToStr(ProxyMode_T enmMode)
276{
277 switch (enmMode)
278 {
279 case ProxyMode_System: return "System";
280 case ProxyMode_Manual: return "Manual";
281 case ProxyMode_NoProxy: return "None";
282 default: break;
283 }
284
285 AssertFailed();
286 return "<Invalid>";
287}
288
289
290/*********************************************************************************************************************************
291* Update agent class implementation *
292*********************************************************************************************************************************/
293UpdateAgent::UpdateAgent()
294{
295}
296
297UpdateAgent::~UpdateAgent()
298{
299}
300
301HRESULT UpdateAgent::FinalConstruct(void)
302{
303 return BaseFinalConstruct();
304}
305
306void UpdateAgent::FinalRelease(void)
307{
308 uninit();
309
310 BaseFinalRelease();
311}
312
313HRESULT UpdateAgent::init(VirtualBox *aVirtualBox)
314{
315 /* Weak reference to a VirtualBox object */
316 unconst(m_VirtualBox) = aVirtualBox;
317
318 HRESULT hr = unconst(m_EventSource).createObject();
319 if (SUCCEEDED(hr))
320 {
321 hr = m_EventSource->init();
322 if (SUCCEEDED(hr))
323 mData.m_fUseOwnProxy = false;
324 }
325
326 return hr;
327}
328
329void UpdateAgent::uninit(void)
330{
331 // Enclose the state transition Ready->InUninit->NotReady.
332 AutoUninitSpan autoUninitSpan(this);
333 if (autoUninitSpan.uninitDone())
334 return;
335
336 unconst(m_EventSource).setNull();
337}
338
339HRESULT UpdateAgent::checkFor(ComPtr<IProgress> &aProgress)
340{
341 RT_NOREF(aProgress);
342
343 return VBOX_E_NOT_SUPPORTED;
344}
345
346HRESULT UpdateAgent::download(ComPtr<IProgress> &aProgress)
347{
348 RT_NOREF(aProgress);
349
350 return VBOX_E_NOT_SUPPORTED;
351}
352
353HRESULT UpdateAgent::install(ComPtr<IProgress> &aProgress)
354{
355 RT_NOREF(aProgress);
356
357 return VBOX_E_NOT_SUPPORTED;
358}
359
360HRESULT UpdateAgent::rollback(void)
361{
362 return VBOX_E_NOT_SUPPORTED;
363}
364
365HRESULT UpdateAgent::getName(com::Utf8Str &aName)
366{
367 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
368
369 aName = mData.m_strName;
370
371 return S_OK;
372}
373
374HRESULT UpdateAgent::getEventSource(ComPtr<IEventSource> &aEventSource)
375{
376 LogFlowThisFuncEnter();
377
378 /* No need to lock - lifetime constant. */
379 m_EventSource.queryInterfaceTo(aEventSource.asOutParam());
380
381 LogFlowFuncLeaveRC(S_OK);
382 return S_OK;
383}
384
385HRESULT UpdateAgent::getOrder(ULONG *aOrder)
386{
387 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
388
389 *aOrder = 0; /* 0 means no order / disabled. */
390
391 return S_OK;
392}
393
394HRESULT UpdateAgent::getDependsOn(std::vector<com::Utf8Str> &aDeps)
395{
396 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
397
398 aDeps.resize(0); /* No dependencies by default. */
399
400 return S_OK;
401}
402
403HRESULT UpdateAgent::getVersion(com::Utf8Str &aVer)
404{
405 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
406
407 aVer = mData.m_lastResult.strVer;
408
409 return S_OK;
410}
411
412HRESULT UpdateAgent::getDownloadUrl(com::Utf8Str &aUrl)
413{
414 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
415
416 aUrl = mData.m_lastResult.strDownloadUrl;
417
418 return S_OK;
419}
420
421
422HRESULT UpdateAgent::getWebUrl(com::Utf8Str &aUrl)
423{
424 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
425
426 aUrl = mData.m_lastResult.strWebUrl;
427
428 return S_OK;
429}
430
431HRESULT UpdateAgent::getReleaseNotes(com::Utf8Str &aRelNotes)
432{
433 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
434
435 aRelNotes = mData.m_lastResult.strReleaseNotes;
436
437 return S_OK;
438}
439
440HRESULT UpdateAgent::getEnabled(BOOL *aEnabled)
441{
442 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
443
444 *aEnabled = m->fEnabled;
445
446 return S_OK;
447}
448
449HRESULT UpdateAgent::setEnabled(const BOOL aEnabled)
450{
451 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
452
453 m->fEnabled = aEnabled;
454
455 return i_commitSettings(alock);
456}
457
458
459HRESULT UpdateAgent::getHidden(BOOL *aHidden)
460{
461 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
462
463 *aHidden = mData.m_fHidden;
464
465 return S_OK;
466}
467
468HRESULT UpdateAgent::getState(UpdateState_T *aState)
469{
470 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
471
472 *aState = mData.m_enmState;
473
474 return S_OK;
475}
476
477HRESULT UpdateAgent::getCheckFrequency(ULONG *aFreqSeconds)
478{
479 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
480
481 *aFreqSeconds = m->uCheckFreqSeconds;
482
483 return S_OK;
484}
485
486HRESULT UpdateAgent::setCheckFrequency(ULONG aFreqSeconds)
487{
488 if (aFreqSeconds < RT_SEC_1DAY) /* Don't allow more frequent checks for now. */
489 return setError(E_INVALIDARG, tr("Frequency too small; one day is the minimum"));
490
491 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
492
493 m->uCheckFreqSeconds = aFreqSeconds;
494
495 return i_commitSettings(alock);
496}
497
498HRESULT UpdateAgent::getChannel(UpdateChannel_T *aChannel)
499{
500 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
501
502 *aChannel = m->enmChannel;
503
504 return S_OK;
505}
506
507HRESULT UpdateAgent::setChannel(UpdateChannel_T aChannel)
508{
509 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
510
511 m->enmChannel = aChannel;
512
513 return i_commitSettings(alock);
514}
515
516HRESULT UpdateAgent::getCheckCount(ULONG *aCount)
517{
518 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
519
520 *aCount = m->uCheckCount;
521
522 return S_OK;
523}
524
525HRESULT UpdateAgent::getRepositoryURL(com::Utf8Str &aRepo)
526{
527 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
528
529 aRepo = m->strRepoUrl;
530
531 return S_OK;
532}
533
534HRESULT UpdateAgent::setRepositoryURL(const com::Utf8Str &aRepo)
535{
536 if (!aRepo.startsWith("https://", com::Utf8Str::CaseInsensitive))
537 return setError(E_INVALIDARG, tr("Invalid URL scheme specified; only https:// is supported."));
538
539 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
540
541 m->strRepoUrl = aRepo;
542
543 return i_commitSettings(alock);
544}
545
546HRESULT UpdateAgent::getProxyMode(ProxyMode_T *aMode)
547{
548 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
549
550 return i_getProxyMode(aMode);
551}
552
553HRESULT UpdateAgent::setProxyMode(ProxyMode_T aMode)
554{
555 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
556
557 m->enmProxyMode = aMode;
558 mData.m_fUseOwnProxy = true;
559
560 return i_commitSettings(alock);
561}
562
563HRESULT UpdateAgent::getProxyURL(com::Utf8Str &aAddress)
564{
565 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
566
567 return i_getProxyURL(aAddress);
568}
569
570HRESULT UpdateAgent::setProxyURL(const com::Utf8Str &aAddress)
571{
572 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
573
574 m->strProxyUrl = aAddress;
575 mData.m_fUseOwnProxy = true;
576
577 return i_commitSettings(alock);
578}
579
580HRESULT UpdateAgent::getLastCheckDate(com::Utf8Str &aDate)
581{
582 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
583
584 aDate = m->strLastCheckDate;
585
586 return S_OK;
587}
588
589HRESULT UpdateAgent::getIsCheckNeeded(BOOL *aCheckNeeded)
590{
591 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
592
593 /*
594 * Is update checking enabled at all?
595 */
596 if (!m->fEnabled)
597 {
598 *aCheckNeeded = FALSE;
599 return S_OK;
600 }
601
602 /*
603 * When was the last update?
604 */
605 if (m->strLastCheckDate.isEmpty()) /* No prior update check performed -- do so now. */
606 {
607 *aCheckNeeded = TRUE;
608 return S_OK;
609 }
610
611 RTTIMESPEC LastCheckTime;
612 if (!RTTimeSpecFromString(&LastCheckTime, Utf8Str(m->strLastCheckDate).c_str()))
613 {
614 *aCheckNeeded = TRUE; /* Invalid date set or error? Perform check. */
615 return S_OK;
616 }
617
618 /*
619 * Compare last update with how often we are supposed to check for updates.
620 */
621 if ( !m->uCheckFreqSeconds /* Paranoia */
622 || m->uCheckFreqSeconds < RT_SEC_1DAY) /* This is the minimum we currently allow. */
623 {
624 /* Consider config (enable, 0 day interval) as checking once but never again.
625 We've already check since we've got a date. */
626 *aCheckNeeded = FALSE;
627 return S_OK;
628 }
629
630 uint64_t const cCheckFreqDays = m->uCheckFreqSeconds / RT_SEC_1DAY_64;
631
632 RTTIMESPEC TimeDiff;
633 RTTimeSpecSub(RTTimeNow(&TimeDiff), &LastCheckTime);
634
635 int64_t const diffLastCheckSecs = RTTimeSpecGetSeconds(&TimeDiff);
636 int64_t const diffLastCheckDays = diffLastCheckSecs / (int64_t)RT_SEC_1DAY_64;
637
638 /* Be as accurate as possible. */
639 *aCheckNeeded = diffLastCheckSecs >= (int64_t)m->uCheckFreqSeconds ? TRUE : FALSE;
640
641 LogRel2(("Update agent (%s): Last update %RI64 days (%RI64 seconds) ago, check frequency is every %RU64 days (%RU64 seconds) -> Check %s\n",
642 mData.m_strName.c_str(), diffLastCheckDays, diffLastCheckSecs, cCheckFreqDays, m->uCheckFreqSeconds,
643 *aCheckNeeded ? "needed" : "not needed"));
644
645 return S_OK;
646}
647
648HRESULT UpdateAgent::getSupportedChannels(std::vector<UpdateChannel_T> &aSupportedChannels)
649{
650 /* No need to take the read lock, as m_enmChannels is const. */
651
652 aSupportedChannels = mData.m_enmChannels;
653
654 return S_OK;
655}
656
657
658/*********************************************************************************************************************************
659* Internal helper methods of update agent class *
660*********************************************************************************************************************************/
661
662/**
663 * Loads the settings of the update agent base class.
664 *
665 * @returns HRESULT
666 * @param data Where to load the settings from.
667 */
668HRESULT UpdateAgent::i_loadSettings(const settings::UpdateAgent &data)
669{
670 AutoCaller autoCaller(this);
671 if (FAILED(autoCaller.rc())) return autoCaller.rc();
672
673 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
674
675 m->fEnabled = data.fEnabled;
676 m->enmChannel = data.enmChannel;
677 m->uCheckFreqSeconds = data.uCheckFreqSeconds;
678 if (data.strRepoUrl.isNotEmpty()) /* Prevent overwriting the agent's default URL when XML settings are empty. */
679 m->strRepoUrl = data.strRepoUrl;
680 m->enmProxyMode = data.enmProxyMode;
681 m->strProxyUrl = data.strProxyUrl;
682 m->strLastCheckDate = data.strLastCheckDate;
683 m->uCheckCount = data.uCheckCount;
684
685 return S_OK;
686}
687
688/**
689 * Saves the settings of the update agent base class.
690 *
691 * @returns HRESULT
692 * @param data Where to save the settings to.
693 */
694HRESULT UpdateAgent::i_saveSettings(settings::UpdateAgent &data)
695{
696 AutoCaller autoCaller(this);
697 if (FAILED(autoCaller.rc())) return autoCaller.rc();
698
699 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
700
701 data = *m;
702
703 /* Cancel out eventually set proxy settings if those were not explicitly set.
704 * This way the ISystemProperties proxy settings will be used then. */
705 if (!mData.m_fUseOwnProxy)
706 {
707 data.strProxyUrl = "";
708 data.enmProxyMode = ProxyMode_System;
709 }
710
711 return S_OK;
712}
713
714/**
715 * Sets the update check count.
716 *
717 * @returns HRESULT
718 * @param aCount Update check count to set.
719 */
720HRESULT UpdateAgent::i_setCheckCount(ULONG aCount)
721{
722 AutoCaller autoCaller(this);
723 if (FAILED(autoCaller.rc())) return autoCaller.rc();
724
725 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
726
727 m->uCheckCount = aCount;
728
729 return i_commitSettings(alock);
730}
731
732/**
733 * Sets the last update check date.
734 *
735 * @returns HRESULT
736 * @param aDate Last update check date to set.
737 * Must be in ISO 8601 format (e.g. 2020-05-11T21:13:39.348416000Z).
738 */
739HRESULT UpdateAgent::i_setLastCheckDate(const com::Utf8Str &aDate)
740{
741 AutoCaller autoCaller(this);
742 if (FAILED(autoCaller.rc())) return autoCaller.rc();
743
744 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
745
746 m->strLastCheckDate = aDate;
747
748 return i_commitSettings(alock);
749}
750
751/**
752 * Internal helper function to commit modified settings.
753 *
754 * @returns HRESULT
755 * @param aLock Write lock to release before committing settings.
756 */
757HRESULT UpdateAgent::i_commitSettings(AutoWriteLock &aLock)
758{
759 aLock.release();
760
761 m_VirtualBox->i_onUpdateAgentSettingsChanged(this, "" /** @todo Include attribute hints */);
762
763 AutoWriteLock vboxLock(m_VirtualBox COMMA_LOCKVAL_SRC_POS);
764 return m_VirtualBox->i_saveSettings();
765}
766
767/**
768 * Returns the proxy mode to use.
769 *
770 * @returns HRESULT
771 * @param aMode Where to return the proxy mode.
772 */
773HRESULT UpdateAgent::i_getProxyMode(ProxyMode_T *aMode)
774{
775 HRESULT hrc;
776
777 if (!mData.m_fUseOwnProxy) /* If not explicitly set, use the ISystemProperties proxy settings. */
778 {
779 ComPtr<ISystemProperties> pSystemProperties;
780 hrc = m_VirtualBox->COMGETTER(SystemProperties)(pSystemProperties.asOutParam());
781 if (SUCCEEDED(hrc))
782 hrc = pSystemProperties->COMGETTER(ProxyMode)(aMode);
783 }
784 else
785 {
786 *aMode = m->enmProxyMode;
787 hrc = S_OK;
788 }
789
790 return hrc;
791}
792
793/**
794 * Returns the proxy URL to use.
795 *
796 * @returns HRESULT
797 * @param aUrl Where to return the proxy URL to use.
798 */
799HRESULT UpdateAgent::i_getProxyURL(com::Utf8Str &aUrl)
800{
801 HRESULT hrc;
802
803 if (!mData.m_fUseOwnProxy) /* If not explicitly set, use the ISystemProperties proxy settings. */
804 {
805 ComPtr<ISystemProperties> pSystemProperties;
806 hrc = m_VirtualBox->COMGETTER(SystemProperties)(pSystemProperties.asOutParam());
807 if (SUCCEEDED(hrc))
808 {
809 com::Bstr bstrVal;
810 hrc = pSystemProperties->COMGETTER(ProxyURL)(bstrVal.asOutParam());
811 if (SUCCEEDED(hrc))
812 aUrl = bstrVal;
813 }
814 }
815 else
816 {
817 aUrl = m->strProxyUrl;
818 hrc = S_OK;
819 }
820
821 return hrc;
822}
823
824/**
825 * Configures a HTTP client's proxy.
826 *
827 * @returns HRESULT
828 * @param hHttp HTTP client to configure proxy for.
829 */
830HRESULT UpdateAgent::i_configureProxy(RTHTTP hHttp)
831{
832 HRESULT rc;
833
834 ProxyMode_T enmProxyMode;
835 rc = i_getProxyMode(&enmProxyMode);
836 ComAssertComRCRetRC(rc);
837 Utf8Str strProxyUrl;
838 rc = i_getProxyURL(strProxyUrl);
839 ComAssertComRCRetRC(rc);
840
841 if (enmProxyMode == ProxyMode_Manual)
842 {
843 int vrc = RTHttpSetProxyByUrl(hHttp, strProxyUrl.c_str());
844 if (RT_FAILURE(vrc))
845 return i_reportError(vrc, tr("RTHttpSetProxyByUrl() failed: %Rrc"), vrc);
846 }
847 else if (enmProxyMode == ProxyMode_System)
848 {
849 int vrc = RTHttpUseSystemProxySettings(hHttp);
850 if (RT_FAILURE(vrc))
851 return i_reportError(vrc, tr("RTHttpUseSystemProxySettings() failed: %Rrc"), vrc);
852 }
853 else
854 Assert(enmProxyMode == ProxyMode_NoProxy);
855
856 LogRel2(("Update agent (%s): Using proxy mode = '%s', URL = '%s'\n",
857 mData.m_strName.c_str(), UpdateAgentBase::i_proxyModeToStr(enmProxyMode), strProxyUrl.c_str()));
858
859 return S_OK;
860}
861
862/**
863 * Reports an error by setting the error info and also informs subscribed listeners.
864 *
865 * @returns HRESULT
866 * @param vrc Result code (IPRT-style) to report.
867 * @param pcszMsgFmt Error message to report.
868 * @param ... Format string for \a pcszMsgFmt.
869 */
870HRESULT UpdateAgent::i_reportError(int vrc, const char *pcszMsgFmt, ...)
871{
872 AssertReturn(pcszMsgFmt && *pcszMsgFmt != '\0', E_INVALIDARG);
873
874 va_list va;
875 va_start(va, pcszMsgFmt);
876
877 Utf8Str strMsg;
878 int const vrc2 = strMsg.printfVNoThrow(pcszMsgFmt, va);
879 if (RT_FAILURE(vrc2))
880 {
881 va_end(va);
882 return setErrorBoth(VBOX_E_IPRT_ERROR, vrc2, tr("Failed to format update agent error string (%Rrc)"), vrc2);
883 }
884
885 va_end(va);
886
887 LogRel(("Update agent (%s): %s\n", mData.m_strName.c_str(), strMsg.c_str()));
888
889 m_VirtualBox->i_onUpdateAgentError(this, strMsg.c_str(), vrc);
890
891 return setErrorBoth(VBOX_E_IPRT_ERROR, vrc, strMsg.c_str());
892}
893
894
895/*********************************************************************************************************************************
896* Host update implementation *
897*********************************************************************************************************************************/
898
899HostUpdateAgent::HostUpdateAgent(void)
900{
901}
902
903HostUpdateAgent::~HostUpdateAgent(void)
904{
905}
906
907
908HRESULT HostUpdateAgent::FinalConstruct(void)
909{
910 return BaseFinalConstruct();
911}
912
913void HostUpdateAgent::FinalRelease(void)
914{
915 uninit();
916
917 BaseFinalRelease();
918}
919
920HRESULT HostUpdateAgent::init(VirtualBox *aVirtualBox)
921{
922 // Enclose the state transition NotReady->InInit->Ready.
923 AutoInitSpan autoInitSpan(this);
924 AssertReturn(autoInitSpan.isOk(), E_FAIL);
925
926 /* Initialize the bare minimum to get things going.
927 ** @todo Add more stuff later here. */
928 mData.m_strName = "VirtualBox";
929 mData.m_fHidden = false;
930
931 const UpdateChannel_T aChannels[] =
932 {
933 UpdateChannel_Stable,
934 UpdateChannel_All,
935 UpdateChannel_WithBetas
936 /** @todo Add UpdateChannel_WithTesting once it's implemented on the backend. */
937 };
938 unconst(mData.m_enmChannels).assign(aChannels, aChannels + RT_ELEMENTS(aChannels));
939
940 /* Set default repository. */
941 m->strRepoUrl = "https://update.virtualbox.org";
942
943 HRESULT hr = UpdateAgent::init(aVirtualBox);
944 if (SUCCEEDED(hr))
945 autoInitSpan.setSucceeded();
946
947 return hr;
948}
949
950void HostUpdateAgent::uninit(void)
951{
952 // Enclose the state transition Ready->InUninit->NotReady.
953 AutoUninitSpan autoUninitSpan(this);
954 if (autoUninitSpan.uninitDone())
955 return;
956}
957
958HRESULT HostUpdateAgent::checkFor(ComPtr<IProgress> &aProgress)
959{
960 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
961
962 ComObjPtr<Progress> pProgress;
963 HRESULT rc = pProgress.createObject();
964 if (FAILED(rc))
965 return rc;
966
967 rc = pProgress->init(m_VirtualBox,
968 static_cast<IUpdateAgent*>(this),
969 tr("Checking for update for %s ...", this->mData.m_strName.c_str()),
970 TRUE /* aCancelable */);
971 if (FAILED(rc))
972 return rc;
973
974 /* initialize the worker task */
975 UpdateAgentTask *pTask = new UpdateAgentTask(this, pProgress);
976 rc = pTask->createThread();
977 pTask = NULL;
978 if (FAILED(rc))
979 return rc;
980
981 return pProgress.queryInterfaceTo(aProgress.asOutParam());
982}
983
984
985/*********************************************************************************************************************************
986* Host update internal functions *
987*********************************************************************************************************************************/
988
989/**
990 * Task callback to perform an update check for the VirtualBox host (core).
991 *
992 * @returns HRESULT
993 * @param pTask Associated update agent task to use.
994 */
995DECLCALLBACK(HRESULT) HostUpdateAgent::i_checkForUpdateTask(UpdateAgentTask *pTask)
996{
997 RT_NOREF(pTask);
998
999 AssertReturn(m->strRepoUrl.isNotEmpty(), E_INVALIDARG);
1000
1001 // Following the sequence of steps in UIUpdateStepVirtualBox::sltStartStep()
1002 // Build up our query URL starting with the configured repository.
1003 Utf8Str strUrl;
1004 strUrl.appendPrintf("%s/query.php/?", m->strRepoUrl.c_str());
1005
1006 // Add platform ID.
1007 Bstr platform;
1008 HRESULT rc = m_VirtualBox->COMGETTER(PackageType)(platform.asOutParam());
1009 AssertComRCReturn(rc, rc);
1010 strUrl.appendPrintf("platform=%ls", platform.raw()); // e.g. SOLARIS_64BITS_GENERIC
1011
1012 // Get the complete current version string for the query URL
1013 Bstr versionNormalized;
1014 rc = m_VirtualBox->COMGETTER(VersionNormalized)(versionNormalized.asOutParam());
1015 AssertComRCReturn(rc, rc);
1016 strUrl.appendPrintf("&version=%ls", versionNormalized.raw()); // e.g. 6.1.1
1017#ifdef DEBUG // Comment out previous line and uncomment this one for testing.
1018// strUrl.appendPrintf("&version=6.0.12");
1019#endif
1020
1021 ULONG revision = 0;
1022 rc = m_VirtualBox->COMGETTER(Revision)(&revision);
1023 AssertComRCReturn(rc, rc);
1024 strUrl.appendPrintf("_%u", revision); // e.g. 135618
1025
1026 // Update the last update check timestamp.
1027 RTTIME Time;
1028 RTTIMESPEC TimeNow;
1029 char szTimeStr[RTTIME_STR_LEN];
1030 RTTimeToString(RTTimeExplode(&Time, RTTimeNow(&TimeNow)), szTimeStr, sizeof(szTimeStr));
1031 LogRel2(("Update agent (%s): Setting last update check timestamp to '%s'\n", mData.m_strName.c_str(), szTimeStr));
1032
1033 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1034
1035 m->strLastCheckDate = szTimeStr;
1036 m->uCheckCount++;
1037
1038 rc = i_commitSettings(alock);
1039 AssertComRCReturn(rc, rc);
1040
1041 strUrl.appendPrintf("&count=%RU32", m->uCheckCount);
1042
1043 // Update the query URL (if necessary) with the 'channel' information.
1044 switch (m->enmChannel)
1045 {
1046 case UpdateChannel_All:
1047 strUrl.appendPrintf("&branch=allrelease"); // query.php expects 'allrelease' and not 'allreleases'
1048 break;
1049 case UpdateChannel_WithBetas:
1050 strUrl.appendPrintf("&branch=withbetas");
1051 break;
1052 /** @todo Handle UpdateChannel_WithTesting once implemented on the backend. */
1053 case UpdateChannel_Stable:
1054 RT_FALL_THROUGH();
1055 default:
1056 strUrl.appendPrintf("&branch=stable");
1057 break;
1058 }
1059
1060 LogRel2(("Update agent (%s): Using URL '%s'\n", mData.m_strName.c_str(), strUrl.c_str()));
1061
1062 /*
1063 * Compose the User-Agent header for the GET request.
1064 */
1065 Bstr version;
1066 rc = m_VirtualBox->COMGETTER(Version)(version.asOutParam()); // e.g. 6.1.0_RC1
1067 AssertComRCReturn(rc, rc);
1068
1069 Utf8StrFmt const strUserAgent("VirtualBox %ls <%s>", version.raw(), UpdateAgent::i_getPlatformInfo().c_str());
1070 LogRel2(("Update agent (%s): Using user agent '%s'\n", mData.m_strName.c_str(), strUserAgent.c_str()));
1071
1072 /*
1073 * Create the HTTP client instance and pass it to a inner worker method to
1074 * ensure proper cleanup.
1075 */
1076 RTHTTP hHttp = NIL_RTHTTP;
1077 int vrc = RTHttpCreate(&hHttp);
1078 if (RT_SUCCESS(vrc))
1079 {
1080 try
1081 {
1082 rc = i_checkForUpdateInner(hHttp, strUrl, strUserAgent);
1083 }
1084 catch (...)
1085 {
1086 AssertFailed();
1087 rc = E_UNEXPECTED;
1088 }
1089 RTHttpDestroy(hHttp);
1090 }
1091 else
1092 rc = i_reportError(vrc, tr("RTHttpCreate() failed: %Rrc"), vrc);
1093
1094 return rc;
1095}
1096
1097/**
1098 * Inner function of the actual update checking mechanism.
1099 *
1100 * @returns HRESULT
1101 * @param hHttp HTTP client instance to use for checking.
1102 * @param strUrl URL of repository to check.
1103 * @param strUserAgent HTTP user agent to use for checking.
1104 */
1105HRESULT HostUpdateAgent::i_checkForUpdateInner(RTHTTP hHttp, Utf8Str const &strUrl, Utf8Str const &strUserAgent)
1106{
1107 /*
1108 * Configure the proxy (if any).
1109 */
1110 HRESULT rc = i_configureProxy(hHttp);
1111 if (FAILED(rc))
1112 return rc;
1113
1114 /** @todo Are there any other headers needed to be added first via RTHttpSetHeaders()? */
1115 int vrc = RTHttpAddHeader(hHttp, "User-Agent", strUserAgent.c_str(), strUserAgent.length(), RTHTTPADDHDR_F_BACK);
1116 if (RT_FAILURE(vrc))
1117 return i_reportError(vrc, tr("RTHttpAddHeader() failed: %Rrc (user agent)"), vrc);
1118
1119 /*
1120 * Perform the GET request, returning raw binary stuff.
1121 */
1122 void *pvResponse = NULL;
1123 size_t cbResponse = 0;
1124 vrc = RTHttpGetBinary(hHttp, strUrl.c_str(), &pvResponse, &cbResponse);
1125 if (RT_FAILURE(vrc))
1126 return i_reportError(vrc, tr("RTHttpGetBinary() failed: %Rrc"), vrc);
1127
1128 /* Note! We can do nothing that might throw exceptions till we call RTHttpFreeResponse! */
1129
1130 /*
1131 * If url is platform=DARWIN_64BITS_GENERIC&version=6.0.12&branch=stable for example, the reply is:
1132 * 6.0.14<SPACE>https://download.virtualbox.org/virtualbox/6.0.14/VirtualBox-6.0.14-133895-OSX.dmg
1133 * If no update required, 'UPTODATE' is returned.
1134 */
1135 /* Parse out the two first words of the response, ignoring whatever follows: */
1136 const char *pchResponse = (const char *)pvResponse;
1137 while (cbResponse > 0 && *pchResponse == ' ')
1138 cbResponse--, pchResponse++;
1139
1140 char ch;
1141 const char *pchWord0 = pchResponse;
1142 while (cbResponse > 0 && (ch = *pchResponse) != ' ' && ch != '\0')
1143 cbResponse--, pchResponse++;
1144 size_t const cchWord0 = (size_t)(pchResponse - pchWord0);
1145
1146 while (cbResponse > 0 && *pchResponse == ' ')
1147 cbResponse--, pchResponse++;
1148 const char *pchWord1 = pchResponse;
1149 while (cbResponse > 0 && (ch = *pchResponse) != ' ' && ch != '\0')
1150 cbResponse--, pchResponse++;
1151 size_t const cchWord1 = (size_t)(pchResponse - pchWord1);
1152
1153 /* Decode the two word: */
1154 static char const s_szUpToDate[] = "UPTODATE";
1155 if ( cchWord0 == sizeof(s_szUpToDate) - 1
1156 && memcmp(pchWord0, s_szUpToDate, sizeof(s_szUpToDate) - 1) == 0)
1157 {
1158 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1159
1160 mData.m_enmState = UpdateState_NotAvailable;
1161 rc = S_OK;
1162
1163 alock.release(); /* Release lock before firing off event. */
1164
1165 m_VirtualBox->i_onUpdateAgentStateChanged(this, UpdateState_NotAvailable);
1166 }
1167 else
1168 {
1169 mData.m_enmState = UpdateState_Error; /* Play safe by default. */
1170
1171 vrc = RTStrValidateEncodingEx(pchWord0, cchWord0, 0 /*fFlags*/);
1172 if (RT_SUCCESS(vrc))
1173 vrc = RTStrValidateEncodingEx(pchWord1, cchWord1, 0 /*fFlags*/);
1174 if (RT_SUCCESS(vrc))
1175 {
1176 /** @todo Any additional sanity checks we could perform here? */
1177 rc = mData.m_lastResult.strVer.assignEx(pchWord0, cchWord0);
1178 if (SUCCEEDED(rc))
1179 rc = mData.m_lastResult.strDownloadUrl.assignEx(pchWord1, cchWord1);
1180
1181 if (SUCCEEDED(rc))
1182 {
1183 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1184
1185 /** @todo Implement this on the backend first.
1186 * We also could do some guessing based on the installed version vs. reported update version? */
1187 mData.m_lastResult.enmSeverity = UpdateSeverity_Invalid;
1188 mData.m_enmState = UpdateState_Available;
1189
1190 alock.release(); /* Release lock before firing off events. */
1191
1192 m_VirtualBox->i_onUpdateAgentStateChanged(this, UpdateState_Available);
1193 m_VirtualBox->i_onUpdateAgentAvailable(this, mData.m_lastResult.strVer, m->enmChannel,
1194 mData.m_lastResult.enmSeverity, mData.m_lastResult.strDownloadUrl,
1195 mData.m_lastResult.strWebUrl, mData.m_lastResult.strReleaseNotes);
1196 }
1197 else
1198 rc = i_reportError(VERR_GENERAL_FAILURE /** @todo Use a better rc */,
1199 tr("Invalid server response [1]: %Rhrc (%.*Rhxs -- %.*Rhxs)"),
1200 rc, cchWord0, pchWord0, cchWord1, pchWord1);
1201
1202 LogRel2(("Update agent (%s): HTTP server replied: %.*s %.*s\n",
1203 mData.m_strName.c_str(), cchWord0, pchWord0, cchWord1, pchWord1));
1204 }
1205 else
1206 rc = i_reportError(vrc, tr("Invalid server response [2]: %Rrc (%.*Rhxs -- %.*Rhxs)"),
1207 vrc, cchWord0, pchWord0, cchWord1, pchWord1);
1208 }
1209
1210 RTHttpFreeResponse(pvResponse);
1211
1212 return rc;
1213}
1214
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette