VirtualBox

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

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

Main/Update check Removed update agent-specific proxy settings. ​bugref:7983

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 35.3 KB
Line 
1/* $Id: UpdateAgentImpl.cpp 94981 2022-05-10 14:48:36Z 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 * Returns whether a given URL's scheme is supported or not.
291 *
292 * @returns \c true if scheme is supported, or \c false if not.
293 * @param strUrl URL to check scheme for.
294 *
295 * @note Empty URL are considered as being supported for convenience.
296 */
297bool UpdateAgentBase::i_urlSchemeIsSupported(const Utf8Str &strUrl) const
298{
299 if (strUrl.isEmpty())
300 return true;
301 return strUrl.startsWith("https://", com::Utf8Str::CaseInsensitive);
302}
303
304
305/*********************************************************************************************************************************
306* Update agent class implementation *
307*********************************************************************************************************************************/
308UpdateAgent::UpdateAgent()
309{
310}
311
312UpdateAgent::~UpdateAgent()
313{
314}
315
316HRESULT UpdateAgent::FinalConstruct(void)
317{
318 return BaseFinalConstruct();
319}
320
321void UpdateAgent::FinalRelease(void)
322{
323 uninit();
324
325 BaseFinalRelease();
326}
327
328HRESULT UpdateAgent::init(VirtualBox *aVirtualBox)
329{
330 /* Weak reference to a VirtualBox object */
331 unconst(m_VirtualBox) = aVirtualBox;
332
333 HRESULT hr = unconst(m_EventSource).createObject();
334 if (SUCCEEDED(hr))
335 hr = m_EventSource->init();
336
337 return hr;
338}
339
340void UpdateAgent::uninit(void)
341{
342 // Enclose the state transition Ready->InUninit->NotReady.
343 AutoUninitSpan autoUninitSpan(this);
344 if (autoUninitSpan.uninitDone())
345 return;
346
347 unconst(m_EventSource).setNull();
348}
349
350HRESULT UpdateAgent::checkFor(ComPtr<IProgress> &aProgress)
351{
352 RT_NOREF(aProgress);
353
354 return VBOX_E_NOT_SUPPORTED;
355}
356
357HRESULT UpdateAgent::download(ComPtr<IProgress> &aProgress)
358{
359 RT_NOREF(aProgress);
360
361 return VBOX_E_NOT_SUPPORTED;
362}
363
364HRESULT UpdateAgent::install(ComPtr<IProgress> &aProgress)
365{
366 RT_NOREF(aProgress);
367
368 return VBOX_E_NOT_SUPPORTED;
369}
370
371HRESULT UpdateAgent::rollback(void)
372{
373 return VBOX_E_NOT_SUPPORTED;
374}
375
376HRESULT UpdateAgent::getName(com::Utf8Str &aName)
377{
378 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
379
380 aName = mData.m_strName;
381
382 return S_OK;
383}
384
385HRESULT UpdateAgent::getEventSource(ComPtr<IEventSource> &aEventSource)
386{
387 LogFlowThisFuncEnter();
388
389 /* No need to lock - lifetime constant. */
390 m_EventSource.queryInterfaceTo(aEventSource.asOutParam());
391
392 LogFlowFuncLeaveRC(S_OK);
393 return S_OK;
394}
395
396HRESULT UpdateAgent::getOrder(ULONG *aOrder)
397{
398 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
399
400 *aOrder = 0; /* 0 means no order / disabled. */
401
402 return S_OK;
403}
404
405HRESULT UpdateAgent::getDependsOn(std::vector<com::Utf8Str> &aDeps)
406{
407 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
408
409 aDeps.resize(0); /* No dependencies by default. */
410
411 return S_OK;
412}
413
414HRESULT UpdateAgent::getVersion(com::Utf8Str &aVer)
415{
416 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
417
418 aVer = mData.m_lastResult.strVer;
419
420 return S_OK;
421}
422
423HRESULT UpdateAgent::getDownloadUrl(com::Utf8Str &aUrl)
424{
425 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
426
427 aUrl = mData.m_lastResult.strDownloadUrl;
428
429 return S_OK;
430}
431
432
433HRESULT UpdateAgent::getWebUrl(com::Utf8Str &aUrl)
434{
435 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
436
437 aUrl = mData.m_lastResult.strWebUrl;
438
439 return S_OK;
440}
441
442HRESULT UpdateAgent::getReleaseNotes(com::Utf8Str &aRelNotes)
443{
444 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
445
446 aRelNotes = mData.m_lastResult.strReleaseNotes;
447
448 return S_OK;
449}
450
451HRESULT UpdateAgent::getEnabled(BOOL *aEnabled)
452{
453 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
454
455 *aEnabled = m->fEnabled;
456
457 return S_OK;
458}
459
460HRESULT UpdateAgent::setEnabled(const BOOL aEnabled)
461{
462 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
463
464 m->fEnabled = aEnabled;
465
466 return i_commitSettings(alock);
467}
468
469
470HRESULT UpdateAgent::getHidden(BOOL *aHidden)
471{
472 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
473
474 *aHidden = mData.m_fHidden;
475
476 return S_OK;
477}
478
479HRESULT UpdateAgent::getState(UpdateState_T *aState)
480{
481 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
482
483 *aState = mData.m_enmState;
484
485 return S_OK;
486}
487
488HRESULT UpdateAgent::getCheckFrequency(ULONG *aFreqSeconds)
489{
490 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
491
492 *aFreqSeconds = m->uCheckFreqSeconds;
493
494 return S_OK;
495}
496
497HRESULT UpdateAgent::setCheckFrequency(ULONG aFreqSeconds)
498{
499 if (aFreqSeconds < RT_SEC_1DAY) /* Don't allow more frequent checks for now. */
500 return setError(E_INVALIDARG, tr("Frequency too small; one day is the minimum"));
501
502 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
503
504 m->uCheckFreqSeconds = aFreqSeconds;
505
506 return i_commitSettings(alock);
507}
508
509HRESULT UpdateAgent::getChannel(UpdateChannel_T *aChannel)
510{
511 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
512
513 *aChannel = m->enmChannel;
514
515 return S_OK;
516}
517
518HRESULT UpdateAgent::setChannel(UpdateChannel_T aChannel)
519{
520 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
521
522 m->enmChannel = aChannel;
523
524 return i_commitSettings(alock);
525}
526
527HRESULT UpdateAgent::getCheckCount(ULONG *aCount)
528{
529 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
530
531 *aCount = m->uCheckCount;
532
533 return S_OK;
534}
535
536HRESULT UpdateAgent::getRepositoryURL(com::Utf8Str &aRepo)
537{
538 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
539
540 aRepo = m->strRepoUrl;
541
542 return S_OK;
543}
544
545HRESULT UpdateAgent::setRepositoryURL(const com::Utf8Str &aRepo)
546{
547 if (!i_urlSchemeIsSupported(aRepo))
548 return setError(E_INVALIDARG, tr("Invalid URL scheme specified!"));
549
550 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
551
552 m->strRepoUrl = aRepo;
553
554 return i_commitSettings(alock);
555}
556
557HRESULT UpdateAgent::getLastCheckDate(com::Utf8Str &aDate)
558{
559 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
560
561 aDate = m->strLastCheckDate;
562
563 return S_OK;
564}
565
566HRESULT UpdateAgent::getIsCheckNeeded(BOOL *aCheckNeeded)
567{
568 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
569
570 /*
571 * Is update checking enabled at all?
572 */
573 if (!m->fEnabled)
574 {
575 *aCheckNeeded = FALSE;
576 return S_OK;
577 }
578
579 /*
580 * When was the last update?
581 */
582 if (m->strLastCheckDate.isEmpty()) /* No prior update check performed -- do so now. */
583 {
584 *aCheckNeeded = TRUE;
585 return S_OK;
586 }
587
588 RTTIMESPEC LastCheckTime;
589 if (!RTTimeSpecFromString(&LastCheckTime, Utf8Str(m->strLastCheckDate).c_str()))
590 {
591 *aCheckNeeded = TRUE; /* Invalid date set or error? Perform check. */
592 return S_OK;
593 }
594
595 /*
596 * Compare last update with how often we are supposed to check for updates.
597 */
598 if ( !m->uCheckFreqSeconds /* Paranoia */
599 || m->uCheckFreqSeconds < RT_SEC_1DAY) /* This is the minimum we currently allow. */
600 {
601 /* Consider config (enable, 0 day interval) as checking once but never again.
602 We've already check since we've got a date. */
603 *aCheckNeeded = FALSE;
604 return S_OK;
605 }
606
607 uint64_t const cCheckFreqDays = m->uCheckFreqSeconds / RT_SEC_1DAY_64;
608
609 RTTIMESPEC TimeDiff;
610 RTTimeSpecSub(RTTimeNow(&TimeDiff), &LastCheckTime);
611
612 int64_t const diffLastCheckSecs = RTTimeSpecGetSeconds(&TimeDiff);
613 int64_t const diffLastCheckDays = diffLastCheckSecs / (int64_t)RT_SEC_1DAY_64;
614
615 /* Be as accurate as possible. */
616 *aCheckNeeded = diffLastCheckSecs >= (int64_t)m->uCheckFreqSeconds ? TRUE : FALSE;
617
618 LogRel2(("Update agent (%s): Last update %RI64 days (%RI64 seconds) ago, check frequency is every %RU64 days (%RU64 seconds) -> Check %s\n",
619 mData.m_strName.c_str(), diffLastCheckDays, diffLastCheckSecs, cCheckFreqDays, m->uCheckFreqSeconds,
620 *aCheckNeeded ? "needed" : "not needed"));
621
622 return S_OK;
623}
624
625HRESULT UpdateAgent::getSupportedChannels(std::vector<UpdateChannel_T> &aSupportedChannels)
626{
627 /* No need to take the read lock, as m_enmChannels is const. */
628
629 aSupportedChannels = mData.m_enmChannels;
630
631 return S_OK;
632}
633
634
635/*********************************************************************************************************************************
636* Internal helper methods of update agent class *
637*********************************************************************************************************************************/
638
639/**
640 * Loads the settings of the update agent base class.
641 *
642 * @returns HRESULT
643 * @retval E_INVALIDARG if to-load settings are invalid / not supported.
644 * @param data Where to load the settings from.
645 */
646HRESULT UpdateAgent::i_loadSettings(const settings::UpdateAgent &data)
647{
648 AutoCaller autoCaller(this);
649 if (FAILED(autoCaller.rc())) return autoCaller.rc();
650
651 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
652
653 m->fEnabled = data.fEnabled;
654 m->enmChannel = data.enmChannel;
655 m->uCheckFreqSeconds = data.uCheckFreqSeconds;
656 if (data.strRepoUrl.isNotEmpty()) /* Prevent overwriting the agent's default URL when XML settings are empty. */
657 m->strRepoUrl = data.strRepoUrl;
658 m->strLastCheckDate = data.strLastCheckDate;
659 m->uCheckCount = data.uCheckCount;
660
661 /* Sanity checks. */
662 if (!i_urlSchemeIsSupported(data.strRepoUrl))
663 return setError(E_INVALIDARG, tr("Invalid URL scheme specified!"));
664
665 return S_OK;
666}
667
668/**
669 * Saves the settings of the update agent base class.
670 *
671 * @returns HRESULT
672 * @param data Where to save the settings to.
673 */
674HRESULT UpdateAgent::i_saveSettings(settings::UpdateAgent &data)
675{
676 AutoCaller autoCaller(this);
677 if (FAILED(autoCaller.rc())) return autoCaller.rc();
678
679 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
680
681 data = *m;
682
683 return S_OK;
684}
685
686/**
687 * Sets the update check count.
688 *
689 * @returns HRESULT
690 * @param aCount Update check count to set.
691 */
692HRESULT UpdateAgent::i_setCheckCount(ULONG aCount)
693{
694 AutoCaller autoCaller(this);
695 if (FAILED(autoCaller.rc())) return autoCaller.rc();
696
697 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
698
699 m->uCheckCount = aCount;
700
701 return i_commitSettings(alock);
702}
703
704/**
705 * Sets the last update check date.
706 *
707 * @returns HRESULT
708 * @param aDate Last update check date to set.
709 * Must be in ISO 8601 format (e.g. 2020-05-11T21:13:39.348416000Z).
710 */
711HRESULT UpdateAgent::i_setLastCheckDate(const com::Utf8Str &aDate)
712{
713 AutoCaller autoCaller(this);
714 if (FAILED(autoCaller.rc())) return autoCaller.rc();
715
716 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
717
718 m->strLastCheckDate = aDate;
719
720 return i_commitSettings(alock);
721}
722
723/**
724 * Internal helper function to commit modified settings.
725 *
726 * @returns HRESULT
727 * @param aLock Write lock to release before committing settings.
728 */
729HRESULT UpdateAgent::i_commitSettings(AutoWriteLock &aLock)
730{
731 aLock.release();
732
733 m_VirtualBox->i_onUpdateAgentSettingsChanged(this, "" /** @todo Include attribute hints */);
734
735 AutoWriteLock vboxLock(m_VirtualBox COMMA_LOCKVAL_SRC_POS);
736 return m_VirtualBox->i_saveSettings();
737}
738
739/**
740 * Returns the proxy mode to use.
741 *
742 * @returns HRESULT
743 * @param aMode Where to return the proxy mode.
744 */
745HRESULT UpdateAgent::i_getProxyMode(ProxyMode_T *aMode)
746{
747 ComPtr<ISystemProperties> pSystemProperties;
748 HRESULT hrc = m_VirtualBox->COMGETTER(SystemProperties)(pSystemProperties.asOutParam());
749 if (SUCCEEDED(hrc))
750 hrc = pSystemProperties->COMGETTER(ProxyMode)(aMode);
751
752 return hrc;
753}
754
755/**
756 * Returns the proxy URL to use.
757 *
758 * @returns HRESULT
759 * @param aUrl Where to return the proxy URL to use.
760 */
761HRESULT UpdateAgent::i_getProxyURL(com::Utf8Str &aUrl)
762{
763 ComPtr<ISystemProperties> pSystemProperties;
764 HRESULT hrc = m_VirtualBox->COMGETTER(SystemProperties)(pSystemProperties.asOutParam());
765 if (SUCCEEDED(hrc))
766 {
767 com::Bstr bstrVal;
768 hrc = pSystemProperties->COMGETTER(ProxyURL)(bstrVal.asOutParam());
769 if (SUCCEEDED(hrc))
770 aUrl = bstrVal;
771 }
772
773 return hrc;
774}
775
776/**
777 * Configures a HTTP client's proxy.
778 *
779 * @returns HRESULT
780 * @param hHttp HTTP client to configure proxy for.
781 */
782HRESULT UpdateAgent::i_configureProxy(RTHTTP hHttp)
783{
784 HRESULT rc;
785
786 ProxyMode_T enmProxyMode;
787 rc = i_getProxyMode(&enmProxyMode);
788 ComAssertComRCRetRC(rc);
789 Utf8Str strProxyUrl;
790 rc = i_getProxyURL(strProxyUrl);
791 ComAssertComRCRetRC(rc);
792
793 if (enmProxyMode == ProxyMode_Manual)
794 {
795 int vrc = RTHttpSetProxyByUrl(hHttp, strProxyUrl.c_str());
796 if (RT_FAILURE(vrc))
797 return i_reportError(vrc, tr("RTHttpSetProxyByUrl() failed: %Rrc"), vrc);
798 }
799 else if (enmProxyMode == ProxyMode_System)
800 {
801 int vrc = RTHttpUseSystemProxySettings(hHttp);
802 if (RT_FAILURE(vrc))
803 return i_reportError(vrc, tr("RTHttpUseSystemProxySettings() failed: %Rrc"), vrc);
804 }
805 else
806 Assert(enmProxyMode == ProxyMode_NoProxy);
807
808 LogRel2(("Update agent (%s): Using proxy mode = '%s', URL = '%s'\n",
809 mData.m_strName.c_str(), UpdateAgentBase::i_proxyModeToStr(enmProxyMode), strProxyUrl.c_str()));
810
811 return S_OK;
812}
813
814/**
815 * Reports an error by setting the error info and also informs subscribed listeners.
816 *
817 * @returns HRESULT
818 * @param vrc Result code (IPRT-style) to report.
819 * @param pcszMsgFmt Error message to report.
820 * @param ... Format string for \a pcszMsgFmt.
821 */
822HRESULT UpdateAgent::i_reportError(int vrc, const char *pcszMsgFmt, ...)
823{
824 AssertReturn(pcszMsgFmt && *pcszMsgFmt != '\0', E_INVALIDARG);
825
826 va_list va;
827 va_start(va, pcszMsgFmt);
828
829 Utf8Str strMsg;
830 int const vrc2 = strMsg.printfVNoThrow(pcszMsgFmt, va);
831 if (RT_FAILURE(vrc2))
832 {
833 va_end(va);
834 return setErrorBoth(VBOX_E_IPRT_ERROR, vrc2, tr("Failed to format update agent error string (%Rrc)"), vrc2);
835 }
836
837 va_end(va);
838
839 LogRel(("Update agent (%s): %s\n", mData.m_strName.c_str(), strMsg.c_str()));
840
841 m_VirtualBox->i_onUpdateAgentError(this, strMsg.c_str(), vrc);
842
843 return setErrorBoth(VBOX_E_IPRT_ERROR, vrc, strMsg.c_str());
844}
845
846
847/*********************************************************************************************************************************
848* Host update implementation *
849*********************************************************************************************************************************/
850
851HostUpdateAgent::HostUpdateAgent(void)
852{
853}
854
855HostUpdateAgent::~HostUpdateAgent(void)
856{
857}
858
859
860HRESULT HostUpdateAgent::FinalConstruct(void)
861{
862 return BaseFinalConstruct();
863}
864
865void HostUpdateAgent::FinalRelease(void)
866{
867 uninit();
868
869 BaseFinalRelease();
870}
871
872HRESULT HostUpdateAgent::init(VirtualBox *aVirtualBox)
873{
874 // Enclose the state transition NotReady->InInit->Ready.
875 AutoInitSpan autoInitSpan(this);
876 AssertReturn(autoInitSpan.isOk(), E_FAIL);
877
878 /* Initialize the bare minimum to get things going.
879 ** @todo Add more stuff later here. */
880 mData.m_strName = "VirtualBox";
881 mData.m_fHidden = false;
882
883 const UpdateChannel_T aChannels[] =
884 {
885 UpdateChannel_Stable,
886 UpdateChannel_All,
887 UpdateChannel_WithBetas
888 /** @todo Add UpdateChannel_WithTesting once it's implemented on the backend. */
889 };
890 unconst(mData.m_enmChannels).assign(aChannels, aChannels + RT_ELEMENTS(aChannels));
891
892 /* Set default repository. */
893 m->strRepoUrl = "https://update.virtualbox.org";
894
895 HRESULT hr = UpdateAgent::init(aVirtualBox);
896 if (SUCCEEDED(hr))
897 autoInitSpan.setSucceeded();
898
899 return hr;
900}
901
902void HostUpdateAgent::uninit(void)
903{
904 // Enclose the state transition Ready->InUninit->NotReady.
905 AutoUninitSpan autoUninitSpan(this);
906 if (autoUninitSpan.uninitDone())
907 return;
908}
909
910HRESULT HostUpdateAgent::checkFor(ComPtr<IProgress> &aProgress)
911{
912 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
913
914 ComObjPtr<Progress> pProgress;
915 HRESULT rc = pProgress.createObject();
916 if (FAILED(rc))
917 return rc;
918
919 rc = pProgress->init(m_VirtualBox,
920 static_cast<IUpdateAgent*>(this),
921 tr("Checking for update for %s ...", this->mData.m_strName.c_str()),
922 TRUE /* aCancelable */);
923 if (FAILED(rc))
924 return rc;
925
926 /* initialize the worker task */
927 UpdateAgentTask *pTask = new UpdateAgentTask(this, pProgress);
928 rc = pTask->createThread();
929 pTask = NULL;
930 if (FAILED(rc))
931 return rc;
932
933 return pProgress.queryInterfaceTo(aProgress.asOutParam());
934}
935
936
937/*********************************************************************************************************************************
938* Host update internal functions *
939*********************************************************************************************************************************/
940
941/**
942 * Task callback to perform an update check for the VirtualBox host (core).
943 *
944 * @returns HRESULT
945 * @param pTask Associated update agent task to use.
946 */
947DECLCALLBACK(HRESULT) HostUpdateAgent::i_checkForUpdateTask(UpdateAgentTask *pTask)
948{
949 RT_NOREF(pTask);
950
951 AssertReturn(m->strRepoUrl.isNotEmpty(), E_INVALIDARG);
952
953 // Following the sequence of steps in UIUpdateStepVirtualBox::sltStartStep()
954 // Build up our query URL starting with the configured repository.
955 Utf8Str strUrl;
956 strUrl.appendPrintf("%s/query.php/?", m->strRepoUrl.c_str());
957
958 // Add platform ID.
959 Bstr platform;
960 HRESULT rc = m_VirtualBox->COMGETTER(PackageType)(platform.asOutParam());
961 AssertComRCReturn(rc, rc);
962 strUrl.appendPrintf("platform=%ls", platform.raw()); // e.g. SOLARIS_64BITS_GENERIC
963
964 // Get the complete current version string for the query URL
965 Bstr versionNormalized;
966 rc = m_VirtualBox->COMGETTER(VersionNormalized)(versionNormalized.asOutParam());
967 AssertComRCReturn(rc, rc);
968 strUrl.appendPrintf("&version=%ls", versionNormalized.raw()); // e.g. 6.1.1
969#ifdef DEBUG // Comment out previous line and uncomment this one for testing.
970// strUrl.appendPrintf("&version=6.0.12");
971#endif
972
973 ULONG revision = 0;
974 rc = m_VirtualBox->COMGETTER(Revision)(&revision);
975 AssertComRCReturn(rc, rc);
976 strUrl.appendPrintf("_%u", revision); // e.g. 135618
977
978 // Update the last update check timestamp.
979 RTTIME Time;
980 RTTIMESPEC TimeNow;
981 char szTimeStr[RTTIME_STR_LEN];
982 RTTimeToString(RTTimeExplode(&Time, RTTimeNow(&TimeNow)), szTimeStr, sizeof(szTimeStr));
983 LogRel2(("Update agent (%s): Setting last update check timestamp to '%s'\n", mData.m_strName.c_str(), szTimeStr));
984
985 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
986
987 m->strLastCheckDate = szTimeStr;
988 m->uCheckCount++;
989
990 rc = i_commitSettings(alock);
991 AssertComRCReturn(rc, rc);
992
993 strUrl.appendPrintf("&count=%RU32", m->uCheckCount);
994
995 // Update the query URL (if necessary) with the 'channel' information.
996 switch (m->enmChannel)
997 {
998 case UpdateChannel_All:
999 strUrl.appendPrintf("&branch=allrelease"); // query.php expects 'allrelease' and not 'allreleases'
1000 break;
1001 case UpdateChannel_WithBetas:
1002 strUrl.appendPrintf("&branch=withbetas");
1003 break;
1004 /** @todo Handle UpdateChannel_WithTesting once implemented on the backend. */
1005 case UpdateChannel_Stable:
1006 RT_FALL_THROUGH();
1007 default:
1008 strUrl.appendPrintf("&branch=stable");
1009 break;
1010 }
1011
1012 LogRel2(("Update agent (%s): Using URL '%s'\n", mData.m_strName.c_str(), strUrl.c_str()));
1013
1014 /*
1015 * Compose the User-Agent header for the GET request.
1016 */
1017 Bstr version;
1018 rc = m_VirtualBox->COMGETTER(Version)(version.asOutParam()); // e.g. 6.1.0_RC1
1019 AssertComRCReturn(rc, rc);
1020
1021 Utf8StrFmt const strUserAgent("VirtualBox %ls <%s>", version.raw(), UpdateAgent::i_getPlatformInfo().c_str());
1022 LogRel2(("Update agent (%s): Using user agent '%s'\n", mData.m_strName.c_str(), strUserAgent.c_str()));
1023
1024 /*
1025 * Create the HTTP client instance and pass it to a inner worker method to
1026 * ensure proper cleanup.
1027 */
1028 RTHTTP hHttp = NIL_RTHTTP;
1029 int vrc = RTHttpCreate(&hHttp);
1030 if (RT_SUCCESS(vrc))
1031 {
1032 try
1033 {
1034 rc = i_checkForUpdateInner(hHttp, strUrl, strUserAgent);
1035 }
1036 catch (...)
1037 {
1038 AssertFailed();
1039 rc = E_UNEXPECTED;
1040 }
1041 RTHttpDestroy(hHttp);
1042 }
1043 else
1044 rc = i_reportError(vrc, tr("RTHttpCreate() failed: %Rrc"), vrc);
1045
1046 return rc;
1047}
1048
1049/**
1050 * Inner function of the actual update checking mechanism.
1051 *
1052 * @returns HRESULT
1053 * @param hHttp HTTP client instance to use for checking.
1054 * @param strUrl URL of repository to check.
1055 * @param strUserAgent HTTP user agent to use for checking.
1056 */
1057HRESULT HostUpdateAgent::i_checkForUpdateInner(RTHTTP hHttp, Utf8Str const &strUrl, Utf8Str const &strUserAgent)
1058{
1059 /*
1060 * Configure the proxy (if any).
1061 */
1062 HRESULT rc = i_configureProxy(hHttp);
1063 if (FAILED(rc))
1064 return rc;
1065
1066 /** @todo Are there any other headers needed to be added first via RTHttpSetHeaders()? */
1067 int vrc = RTHttpAddHeader(hHttp, "User-Agent", strUserAgent.c_str(), strUserAgent.length(), RTHTTPADDHDR_F_BACK);
1068 if (RT_FAILURE(vrc))
1069 return i_reportError(vrc, tr("RTHttpAddHeader() failed: %Rrc (user agent)"), vrc);
1070
1071 /*
1072 * Perform the GET request, returning raw binary stuff.
1073 */
1074 void *pvResponse = NULL;
1075 size_t cbResponse = 0;
1076 vrc = RTHttpGetBinary(hHttp, strUrl.c_str(), &pvResponse, &cbResponse);
1077 if (RT_FAILURE(vrc))
1078 return i_reportError(vrc, tr("RTHttpGetBinary() failed: %Rrc"), vrc);
1079
1080 /* Note! We can do nothing that might throw exceptions till we call RTHttpFreeResponse! */
1081
1082 /*
1083 * If url is platform=DARWIN_64BITS_GENERIC&version=6.0.12&branch=stable for example, the reply is:
1084 * 6.0.14<SPACE>https://download.virtualbox.org/virtualbox/6.0.14/VirtualBox-6.0.14-133895-OSX.dmg
1085 * If no update required, 'UPTODATE' is returned.
1086 */
1087 /* Parse out the two first words of the response, ignoring whatever follows: */
1088 const char *pchResponse = (const char *)pvResponse;
1089 while (cbResponse > 0 && *pchResponse == ' ')
1090 cbResponse--, pchResponse++;
1091
1092 char ch;
1093 const char *pchWord0 = pchResponse;
1094 while (cbResponse > 0 && (ch = *pchResponse) != ' ' && ch != '\0')
1095 cbResponse--, pchResponse++;
1096 size_t const cchWord0 = (size_t)(pchResponse - pchWord0);
1097
1098 while (cbResponse > 0 && *pchResponse == ' ')
1099 cbResponse--, pchResponse++;
1100 const char *pchWord1 = pchResponse;
1101 while (cbResponse > 0 && (ch = *pchResponse) != ' ' && ch != '\0')
1102 cbResponse--, pchResponse++;
1103 size_t const cchWord1 = (size_t)(pchResponse - pchWord1);
1104
1105 /* Decode the two word: */
1106 static char const s_szUpToDate[] = "UPTODATE";
1107 if ( cchWord0 == sizeof(s_szUpToDate) - 1
1108 && memcmp(pchWord0, s_szUpToDate, sizeof(s_szUpToDate) - 1) == 0)
1109 {
1110 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1111
1112 mData.m_enmState = UpdateState_NotAvailable;
1113 rc = S_OK;
1114
1115 alock.release(); /* Release lock before firing off event. */
1116
1117 m_VirtualBox->i_onUpdateAgentStateChanged(this, UpdateState_NotAvailable);
1118 }
1119 else
1120 {
1121 mData.m_enmState = UpdateState_Error; /* Play safe by default. */
1122
1123 vrc = RTStrValidateEncodingEx(pchWord0, cchWord0, 0 /*fFlags*/);
1124 if (RT_SUCCESS(vrc))
1125 vrc = RTStrValidateEncodingEx(pchWord1, cchWord1, 0 /*fFlags*/);
1126 if (RT_SUCCESS(vrc))
1127 {
1128 /** @todo Any additional sanity checks we could perform here? */
1129 rc = mData.m_lastResult.strVer.assignEx(pchWord0, cchWord0);
1130 if (SUCCEEDED(rc))
1131 rc = mData.m_lastResult.strDownloadUrl.assignEx(pchWord1, cchWord1);
1132
1133 if (SUCCEEDED(rc))
1134 {
1135 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1136
1137 /** @todo Implement this on the backend first.
1138 * We also could do some guessing based on the installed version vs. reported update version? */
1139 mData.m_lastResult.enmSeverity = UpdateSeverity_Invalid;
1140 mData.m_enmState = UpdateState_Available;
1141
1142 alock.release(); /* Release lock before firing off events. */
1143
1144 m_VirtualBox->i_onUpdateAgentStateChanged(this, UpdateState_Available);
1145 m_VirtualBox->i_onUpdateAgentAvailable(this, mData.m_lastResult.strVer, m->enmChannel,
1146 mData.m_lastResult.enmSeverity, mData.m_lastResult.strDownloadUrl,
1147 mData.m_lastResult.strWebUrl, mData.m_lastResult.strReleaseNotes);
1148 }
1149 else
1150 rc = i_reportError(VERR_GENERAL_FAILURE /** @todo Use a better rc */,
1151 tr("Invalid server response [1]: %Rhrc (%.*Rhxs -- %.*Rhxs)"),
1152 rc, cchWord0, pchWord0, cchWord1, pchWord1);
1153
1154 LogRel2(("Update agent (%s): HTTP server replied: %.*s %.*s\n",
1155 mData.m_strName.c_str(), cchWord0, pchWord0, cchWord1, pchWord1));
1156 }
1157 else
1158 rc = i_reportError(vrc, tr("Invalid server response [2]: %Rrc (%.*Rhxs -- %.*Rhxs)"),
1159 vrc, cchWord0, pchWord0, cchWord1, pchWord1);
1160 }
1161
1162 RTHttpFreeResponse(pvResponse);
1163
1164 return rc;
1165}
1166
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