VirtualBox

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

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

Main/Update check: Take #2: Boilerplate code for update agent event handling in FE/Qt, along with an example -- this time the events are being emitted directly through Main / IVirtualBox. See @todos. bugref:7983

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