VirtualBox

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

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

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