VirtualBox

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

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

Main/Update check: Also expose IUpdateAgent::isCheckNeeded() in base class (as a convenience function). ​​bugref:7983

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