VirtualBox

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

Last change on this file was 98293, checked in by vboxsync, 16 months ago

Main/src-server: hr -> hrc. bugref:10223

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

© 2023 Oracle
ContactPrivacy policyTerms of Use