VirtualBox

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

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

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

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