VirtualBox

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

Last change on this file since 97698 was 96407, checked in by vboxsync, 2 years ago

scm copyright and license note update

  • 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 96407 2022-08-22 17:43:14Z vboxsync $ */
2/** @file
3 * IUpdateAgent COM class implementations.
4 */
5
6/*
7 * Copyright (C) 2020-2022 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 rc = pUpdateAgent->i_checkForUpdateTask(this);
98
99 if (!m_pProgress.isNull())
100 m_pProgress->i_notifyComplete(rc);
101
102 LogFlowFunc(("rc=%Rhrc\n", rc)); RT_NOREF(rc);
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 hr = unconst(m_EventSource).createObject();
344 if (SUCCEEDED(hr))
345 hr = m_EventSource->init();
346
347 return hr;
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.rc())) return autoCaller.rc();
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.rc())) return autoCaller.rc();
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.rc())) return autoCaller.rc();
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.rc())) return autoCaller.rc();
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 HRESULT rc;
795
796 ProxyMode_T enmProxyMode;
797 rc = i_getProxyMode(&enmProxyMode);
798 ComAssertComRCRetRC(rc);
799 Utf8Str strProxyUrl;
800 rc = i_getProxyURL(strProxyUrl);
801 ComAssertComRCRetRC(rc);
802
803 if (enmProxyMode == ProxyMode_Manual)
804 {
805 int vrc = RTHttpSetProxyByUrl(hHttp, strProxyUrl.c_str());
806 if (RT_FAILURE(vrc))
807 return i_reportError(vrc, tr("RTHttpSetProxyByUrl() failed: %Rrc"), vrc);
808 }
809 else if (enmProxyMode == ProxyMode_System)
810 {
811 int vrc = RTHttpUseSystemProxySettings(hHttp);
812 if (RT_FAILURE(vrc))
813 return i_reportError(vrc, tr("RTHttpUseSystemProxySettings() failed: %Rrc"), vrc);
814 }
815 else
816 Assert(enmProxyMode == ProxyMode_NoProxy);
817
818 LogRel2(("Update agent (%s): Using proxy mode = '%s', URL = '%s'\n",
819 mData.m_strName.c_str(), UpdateAgentBase::i_proxyModeToStr(enmProxyMode), strProxyUrl.c_str()));
820
821 return S_OK;
822}
823
824/**
825 * Reports an error by setting the error info and also informs subscribed listeners.
826 *
827 * @returns HRESULT
828 * @param vrc Result code (IPRT-style) to report.
829 * @param pcszMsgFmt Error message to report.
830 * @param ... Format string for \a pcszMsgFmt.
831 */
832HRESULT UpdateAgent::i_reportError(int vrc, const char *pcszMsgFmt, ...)
833{
834 AssertReturn(pcszMsgFmt && *pcszMsgFmt != '\0', E_INVALIDARG);
835
836 va_list va;
837 va_start(va, pcszMsgFmt);
838
839 Utf8Str strMsg;
840 int const vrc2 = strMsg.printfVNoThrow(pcszMsgFmt, va);
841 if (RT_FAILURE(vrc2))
842 {
843 va_end(va);
844 return setErrorBoth(VBOX_E_IPRT_ERROR, vrc2, tr("Failed to format update agent error string (%Rrc)"), vrc2);
845 }
846
847 va_end(va);
848
849 LogRel(("Update agent (%s): %s\n", mData.m_strName.c_str(), strMsg.c_str()));
850
851 m_VirtualBox->i_onUpdateAgentError(this, strMsg.c_str(), vrc);
852
853 return setErrorBoth(VBOX_E_IPRT_ERROR, vrc, strMsg.c_str());
854}
855
856
857/*********************************************************************************************************************************
858* Host update implementation *
859*********************************************************************************************************************************/
860
861HostUpdateAgent::HostUpdateAgent(void)
862{
863}
864
865HostUpdateAgent::~HostUpdateAgent(void)
866{
867}
868
869
870HRESULT HostUpdateAgent::FinalConstruct(void)
871{
872 return BaseFinalConstruct();
873}
874
875void HostUpdateAgent::FinalRelease(void)
876{
877 uninit();
878
879 BaseFinalRelease();
880}
881
882HRESULT HostUpdateAgent::init(VirtualBox *aVirtualBox)
883{
884 // Enclose the state transition NotReady->InInit->Ready.
885 AutoInitSpan autoInitSpan(this);
886 AssertReturn(autoInitSpan.isOk(), E_FAIL);
887
888 /* Initialize the bare minimum to get things going.
889 ** @todo Add more stuff later here. */
890 mData.m_strName = "VirtualBox";
891 mData.m_fHidden = false;
892
893 const UpdateChannel_T aChannels[] =
894 {
895 UpdateChannel_Stable,
896 UpdateChannel_All,
897 UpdateChannel_WithBetas
898 /** @todo Add UpdateChannel_WithTesting once it's implemented on the backend. */
899 };
900 unconst(mData.m_enmChannels).assign(aChannels, aChannels + RT_ELEMENTS(aChannels));
901
902 /* Set default repository. */
903 m->strRepoUrl = "https://update.virtualbox.org";
904
905 HRESULT hr = UpdateAgent::init(aVirtualBox);
906 if (SUCCEEDED(hr))
907 autoInitSpan.setSucceeded();
908
909 return hr;
910}
911
912void HostUpdateAgent::uninit(void)
913{
914 // Enclose the state transition Ready->InUninit->NotReady.
915 AutoUninitSpan autoUninitSpan(this);
916 if (autoUninitSpan.uninitDone())
917 return;
918}
919
920HRESULT HostUpdateAgent::checkFor(ComPtr<IProgress> &aProgress)
921{
922 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
923
924 ComObjPtr<Progress> pProgress;
925 HRESULT rc = pProgress.createObject();
926 if (FAILED(rc))
927 return rc;
928
929 rc = pProgress->init(m_VirtualBox,
930 static_cast<IUpdateAgent*>(this),
931 tr("Checking for update for %s ...", this->mData.m_strName.c_str()),
932 TRUE /* aCancelable */);
933 if (FAILED(rc))
934 return rc;
935
936 /* initialize the worker task */
937 UpdateAgentTask *pTask = new UpdateAgentTask(this, pProgress);
938 rc = pTask->createThread();
939 pTask = NULL;
940 if (FAILED(rc))
941 return rc;
942
943 return pProgress.queryInterfaceTo(aProgress.asOutParam());
944}
945
946
947/*********************************************************************************************************************************
948* Host update internal functions *
949*********************************************************************************************************************************/
950
951/**
952 * Task callback to perform an update check for the VirtualBox host (core).
953 *
954 * @returns HRESULT
955 * @param pTask Associated update agent task to use.
956 */
957DECLCALLBACK(HRESULT) HostUpdateAgent::i_checkForUpdateTask(UpdateAgentTask *pTask)
958{
959 RT_NOREF(pTask);
960
961 AssertReturn(m->strRepoUrl.isNotEmpty(), E_INVALIDARG);
962
963 // Following the sequence of steps in UIUpdateStepVirtualBox::sltStartStep()
964 // Build up our query URL starting with the configured repository.
965 Utf8Str strUrl;
966 strUrl.appendPrintf("%s/query.php/?", m->strRepoUrl.c_str());
967
968 // Add platform ID.
969 Bstr platform;
970 HRESULT rc = m_VirtualBox->COMGETTER(PackageType)(platform.asOutParam());
971 AssertComRCReturn(rc, rc);
972 strUrl.appendPrintf("platform=%ls", platform.raw()); // e.g. SOLARIS_64BITS_GENERIC
973
974 // Get the complete current version string for the query URL
975 Bstr versionNormalized;
976 rc = m_VirtualBox->COMGETTER(VersionNormalized)(versionNormalized.asOutParam());
977 AssertComRCReturn(rc, rc);
978 strUrl.appendPrintf("&version=%ls", versionNormalized.raw()); // e.g. 6.1.1
979#ifdef DEBUG // Comment out previous line and uncomment this one for testing.
980// strUrl.appendPrintf("&version=6.0.12");
981#endif
982
983 ULONG revision = 0;
984 rc = m_VirtualBox->COMGETTER(Revision)(&revision);
985 AssertComRCReturn(rc, rc);
986 strUrl.appendPrintf("_%u", revision); // e.g. 135618
987
988 // Update the last update check timestamp.
989 RTTIME Time;
990 RTTIMESPEC TimeNow;
991 char szTimeStr[RTTIME_STR_LEN];
992 RTTimeToString(RTTimeExplode(&Time, RTTimeNow(&TimeNow)), szTimeStr, sizeof(szTimeStr));
993 LogRel2(("Update agent (%s): Setting last update check timestamp to '%s'\n", mData.m_strName.c_str(), szTimeStr));
994
995 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
996
997 m->strLastCheckDate = szTimeStr;
998 m->uCheckCount++;
999
1000 rc = i_commitSettings(alock);
1001 AssertComRCReturn(rc, rc);
1002
1003 strUrl.appendPrintf("&count=%RU32", m->uCheckCount);
1004
1005 // Update the query URL (if necessary) with the 'channel' information.
1006 switch (m->enmChannel)
1007 {
1008 case UpdateChannel_All:
1009 strUrl.appendPrintf("&branch=allrelease"); // query.php expects 'allrelease' and not 'allreleases'
1010 break;
1011 case UpdateChannel_WithBetas:
1012 strUrl.appendPrintf("&branch=withbetas");
1013 break;
1014 /** @todo Handle UpdateChannel_WithTesting once implemented on the backend. */
1015 case UpdateChannel_Stable:
1016 RT_FALL_THROUGH();
1017 default:
1018 strUrl.appendPrintf("&branch=stable");
1019 break;
1020 }
1021
1022 LogRel2(("Update agent (%s): Using URL '%s'\n", mData.m_strName.c_str(), strUrl.c_str()));
1023
1024 /*
1025 * Compose the User-Agent header for the GET request.
1026 */
1027 Bstr version;
1028 rc = m_VirtualBox->COMGETTER(Version)(version.asOutParam()); // e.g. 6.1.0_RC1
1029 AssertComRCReturn(rc, rc);
1030
1031 Utf8StrFmt const strUserAgent("VirtualBox %ls <%s>", version.raw(), UpdateAgent::i_getPlatformInfo().c_str());
1032 LogRel2(("Update agent (%s): Using user agent '%s'\n", mData.m_strName.c_str(), strUserAgent.c_str()));
1033
1034 /*
1035 * Create the HTTP client instance and pass it to a inner worker method to
1036 * ensure proper cleanup.
1037 */
1038 RTHTTP hHttp = NIL_RTHTTP;
1039 int vrc = RTHttpCreate(&hHttp);
1040 if (RT_SUCCESS(vrc))
1041 {
1042 try
1043 {
1044 rc = i_checkForUpdateInner(hHttp, strUrl, strUserAgent);
1045 }
1046 catch (...)
1047 {
1048 AssertFailed();
1049 rc = E_UNEXPECTED;
1050 }
1051 RTHttpDestroy(hHttp);
1052 }
1053 else
1054 rc = i_reportError(vrc, tr("RTHttpCreate() failed: %Rrc"), vrc);
1055
1056 return rc;
1057}
1058
1059/**
1060 * Inner function of the actual update checking mechanism.
1061 *
1062 * @returns HRESULT
1063 * @param hHttp HTTP client instance to use for checking.
1064 * @param strUrl URL of repository to check.
1065 * @param strUserAgent HTTP user agent to use for checking.
1066 */
1067HRESULT HostUpdateAgent::i_checkForUpdateInner(RTHTTP hHttp, Utf8Str const &strUrl, Utf8Str const &strUserAgent)
1068{
1069 /*
1070 * Configure the proxy (if any).
1071 */
1072 HRESULT rc = i_configureProxy(hHttp);
1073 if (FAILED(rc))
1074 return rc;
1075
1076 /** @todo Are there any other headers needed to be added first via RTHttpSetHeaders()? */
1077 int vrc = RTHttpAddHeader(hHttp, "User-Agent", strUserAgent.c_str(), strUserAgent.length(), RTHTTPADDHDR_F_BACK);
1078 if (RT_FAILURE(vrc))
1079 return i_reportError(vrc, tr("RTHttpAddHeader() failed: %Rrc (user agent)"), vrc);
1080
1081 /*
1082 * Perform the GET request, returning raw binary stuff.
1083 */
1084 void *pvResponse = NULL;
1085 size_t cbResponse = 0;
1086 vrc = RTHttpGetBinary(hHttp, strUrl.c_str(), &pvResponse, &cbResponse);
1087 if (RT_FAILURE(vrc))
1088 return i_reportError(vrc, tr("RTHttpGetBinary() failed: %Rrc"), vrc);
1089
1090 /* Note! We can do nothing that might throw exceptions till we call RTHttpFreeResponse! */
1091
1092 /*
1093 * If url is platform=DARWIN_64BITS_GENERIC&version=6.0.12&branch=stable for example, the reply is:
1094 * 6.0.14<SPACE>https://download.virtualbox.org/virtualbox/6.0.14/VirtualBox-6.0.14-133895-OSX.dmg
1095 * If no update required, 'UPTODATE' is returned.
1096 */
1097 /* Parse out the two first words of the response, ignoring whatever follows: */
1098 const char *pchResponse = (const char *)pvResponse;
1099 while (cbResponse > 0 && *pchResponse == ' ')
1100 cbResponse--, pchResponse++;
1101
1102 char ch;
1103 const char *pchWord0 = pchResponse;
1104 while (cbResponse > 0 && (ch = *pchResponse) != ' ' && ch != '\0')
1105 cbResponse--, pchResponse++;
1106 size_t const cchWord0 = (size_t)(pchResponse - pchWord0);
1107
1108 while (cbResponse > 0 && *pchResponse == ' ')
1109 cbResponse--, pchResponse++;
1110 const char *pchWord1 = pchResponse;
1111 while (cbResponse > 0 && (ch = *pchResponse) != ' ' && ch != '\0')
1112 cbResponse--, pchResponse++;
1113 size_t const cchWord1 = (size_t)(pchResponse - pchWord1);
1114
1115 /* Decode the two word: */
1116 static char const s_szUpToDate[] = "UPTODATE";
1117 if ( cchWord0 == sizeof(s_szUpToDate) - 1
1118 && memcmp(pchWord0, s_szUpToDate, sizeof(s_szUpToDate) - 1) == 0)
1119 {
1120 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1121
1122 mData.m_enmState = UpdateState_NotAvailable;
1123 rc = S_OK;
1124
1125 alock.release(); /* Release lock before firing off event. */
1126
1127 m_VirtualBox->i_onUpdateAgentStateChanged(this, UpdateState_NotAvailable);
1128 }
1129 else
1130 {
1131 mData.m_enmState = UpdateState_Error; /* Play safe by default. */
1132
1133 vrc = RTStrValidateEncodingEx(pchWord0, cchWord0, 0 /*fFlags*/);
1134 if (RT_SUCCESS(vrc))
1135 vrc = RTStrValidateEncodingEx(pchWord1, cchWord1, 0 /*fFlags*/);
1136 if (RT_SUCCESS(vrc))
1137 {
1138 /** @todo Any additional sanity checks we could perform here? */
1139 rc = mData.m_lastResult.strVer.assignEx(pchWord0, cchWord0);
1140 if (SUCCEEDED(rc))
1141 rc = mData.m_lastResult.strDownloadUrl.assignEx(pchWord1, cchWord1);
1142
1143 if (SUCCEEDED(rc))
1144 {
1145 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1146
1147 /** @todo Implement this on the backend first.
1148 * We also could do some guessing based on the installed version vs. reported update version? */
1149 mData.m_lastResult.enmSeverity = UpdateSeverity_Invalid;
1150 mData.m_enmState = UpdateState_Available;
1151
1152 alock.release(); /* Release lock before firing off events. */
1153
1154 m_VirtualBox->i_onUpdateAgentStateChanged(this, UpdateState_Available);
1155 m_VirtualBox->i_onUpdateAgentAvailable(this, mData.m_lastResult.strVer, m->enmChannel,
1156 mData.m_lastResult.enmSeverity, mData.m_lastResult.strDownloadUrl,
1157 mData.m_lastResult.strWebUrl, mData.m_lastResult.strReleaseNotes);
1158 }
1159 else
1160 rc = i_reportError(VERR_GENERAL_FAILURE /** @todo Use a better rc */,
1161 tr("Invalid server response [1]: %Rhrc (%.*Rhxs -- %.*Rhxs)"),
1162 rc, cchWord0, pchWord0, cchWord1, pchWord1);
1163
1164 LogRel2(("Update agent (%s): HTTP server replied: %.*s %.*s\n",
1165 mData.m_strName.c_str(), cchWord0, pchWord0, cchWord1, pchWord1));
1166 }
1167 else
1168 rc = i_reportError(vrc, tr("Invalid server response [2]: %Rrc (%.*Rhxs -- %.*Rhxs)"),
1169 vrc, cchWord0, pchWord0, cchWord1, pchWord1);
1170 }
1171
1172 RTHttpFreeResponse(pvResponse);
1173
1174 return rc;
1175}
1176
Note: See TracBrowser for help on using the repository browser.

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