VirtualBox

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

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

Main/Update check: Docs, renaming. ​​bugref:7983

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

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