VirtualBox

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

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

Main/Update check: Big overhaul of the API and functionality.

  • Now uses VBOX_WITH_UPDATE_AGENT to entirely disable the feature (enabled by default).
  • Main: Uses new (more abstract) API as proposed in the latest UML docs.
  • Main: Added support for several events.
  • Main: Added support for update severities, order and dependencies (all optional).
  • Settings/XML: Now has own "Updates" branch to also cover other updatable components (later); not part of the system properties anymore.
  • Prepared for GuestAdditions and ExtPack updates.
  • FE/Qt: Adapted to new API.
  • FE/VBoxManage: Adapted to new API; uses more uniform (common) synopsis "modify" and "list" for modifying and listing (showing) update settings.
  • Docs: Fixed various typos, extended documentation.

Work in progress. bugref:7983

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