VirtualBox

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

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

Main/Update check: Settings fixes, added @todos [SCM fix]. ​​bugref:7983

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 29.1 KB
Line 
1/* $Id: UpdateAgentImpl.cpp 94686 2022-04-22 10:23:07Z 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
376/* static */
377Utf8Str UpdateAgentBase::i_getPlatformInfo(void)
378{
379 /* Prepare platform report: */
380 Utf8Str strPlatform;
381
382# if defined (RT_OS_WINDOWS)
383 strPlatform = "win";
384# elif defined (RT_OS_LINUX)
385 strPlatform = "linux";
386# elif defined (RT_OS_DARWIN)
387 strPlatform = "macosx";
388# elif defined (RT_OS_OS2)
389 strPlatform = "os2";
390# elif defined (RT_OS_FREEBSD)
391 strPlatform = "freebsd";
392# elif defined (RT_OS_SOLARIS)
393 strPlatform = "solaris";
394# else
395 strPlatform = "unknown";
396# endif
397
398 /* The format is <system>.<bitness>: */
399 strPlatform.appendPrintf(".%lu", ARCH_BITS);
400
401 /* Add more system information: */
402 int vrc;
403# ifdef RT_OS_LINUX
404 // WORKAROUND:
405 // On Linux we try to generate information using script first of all..
406
407 /* Get script path: */
408 char szAppPrivPath[RTPATH_MAX];
409 vrc = RTPathAppPrivateNoArch(szAppPrivPath, sizeof(szAppPrivPath));
410 AssertRC(vrc);
411 if (RT_SUCCESS(vrc))
412 vrc = RTPathAppend(szAppPrivPath, sizeof(szAppPrivPath), "/VBoxSysInfo.sh");
413 AssertRC(vrc);
414 if (RT_SUCCESS(vrc))
415 {
416 RTPIPE hPipeR;
417 RTHANDLE hStdOutPipe;
418 hStdOutPipe.enmType = RTHANDLETYPE_PIPE;
419 vrc = RTPipeCreate(&hPipeR, &hStdOutPipe.u.hPipe, RTPIPE_C_INHERIT_WRITE);
420 AssertLogRelRC(vrc);
421
422 char const *szAppPrivArgs[2];
423 szAppPrivArgs[0] = szAppPrivPath;
424 szAppPrivArgs[1] = NULL;
425 RTPROCESS hProc = NIL_RTPROCESS;
426
427 /* Run script: */
428 vrc = RTProcCreateEx(szAppPrivPath, szAppPrivArgs, RTENV_DEFAULT, 0 /*fFlags*/, NULL /*phStdin*/, &hStdOutPipe,
429 NULL /*phStderr*/, NULL /*pszAsUser*/, NULL /*pszPassword*/, NULL /*pvExtraData*/, &hProc);
430
431 (void) RTPipeClose(hStdOutPipe.u.hPipe);
432 hStdOutPipe.u.hPipe = NIL_RTPIPE;
433
434 if (RT_SUCCESS(vrc))
435 {
436 RTPROCSTATUS ProcStatus;
437 size_t cbStdOutBuf = 0;
438 size_t offStdOutBuf = 0;
439 char *pszStdOutBuf = NULL;
440 do
441 {
442 if (hPipeR != NIL_RTPIPE)
443 {
444 char achBuf[1024];
445 size_t cbRead;
446 vrc = RTPipeReadBlocking(hPipeR, achBuf, sizeof(achBuf), &cbRead);
447 if (RT_SUCCESS(vrc))
448 {
449 /* grow the buffer? */
450 size_t cbBufReq = offStdOutBuf + cbRead + 1;
451 if ( cbBufReq > cbStdOutBuf
452 && cbBufReq < _256K)
453 {
454 size_t cbNew = RT_ALIGN_Z(cbBufReq, 16); // 1024
455 void *pvNew = RTMemRealloc(pszStdOutBuf, cbNew);
456 if (pvNew)
457 {
458 pszStdOutBuf = (char *)pvNew;
459 cbStdOutBuf = cbNew;
460 }
461 }
462
463 /* append if we've got room. */
464 if (cbBufReq <= cbStdOutBuf)
465 {
466 (void) memcpy(&pszStdOutBuf[offStdOutBuf], achBuf, cbRead);
467 offStdOutBuf = offStdOutBuf + cbRead;
468 pszStdOutBuf[offStdOutBuf] = '\0';
469 }
470 }
471 else
472 {
473 AssertLogRelMsg(vrc == VERR_BROKEN_PIPE, ("%Rrc\n", vrc));
474 RTPipeClose(hPipeR);
475 hPipeR = NIL_RTPIPE;
476 }
477 }
478
479 /*
480 * Service the process. Block if we have no pipe.
481 */
482 if (hProc != NIL_RTPROCESS)
483 {
484 vrc = RTProcWait(hProc,
485 hPipeR == NIL_RTPIPE ? RTPROCWAIT_FLAGS_BLOCK : RTPROCWAIT_FLAGS_NOBLOCK,
486 &ProcStatus);
487 if (RT_SUCCESS(vrc))
488 hProc = NIL_RTPROCESS;
489 else
490 AssertLogRelMsgStmt(vrc == VERR_PROCESS_RUNNING, ("%Rrc\n", vrc), hProc = NIL_RTPROCESS);
491 }
492 } while ( hPipeR != NIL_RTPIPE
493 || hProc != NIL_RTPROCESS);
494
495 if ( ProcStatus.enmReason == RTPROCEXITREASON_NORMAL
496 && ProcStatus.iStatus == 0) {
497 pszStdOutBuf[offStdOutBuf-1] = '\0'; // remove trailing newline
498 Utf8Str pszStdOutBufUTF8(pszStdOutBuf);
499 strPlatform.appendPrintf(" [%s]", pszStdOutBufUTF8.strip().c_str());
500 // For testing, here is some sample output:
501 //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]");
502 }
503 }
504 else
505 vrc = VERR_TRY_AGAIN; /* (take the fallback path) */
506 }
507
508 LogRelFunc(("strPlatform (Linux) = %s\n", strPlatform.c_str()));
509
510 if (RT_FAILURE(vrc))
511# endif /* RT_OS_LINUX */
512 {
513 /* Use RTSystemQueryOSInfo: */
514 char szTmp[256];
515
516 vrc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szTmp, sizeof(szTmp));
517 if ((RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW) && szTmp[0] != '\0')
518 strPlatform.appendPrintf(" [Product: %s", szTmp);
519
520 vrc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szTmp, sizeof(szTmp));
521 if ((RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW) && szTmp[0] != '\0')
522 strPlatform.appendPrintf(" %sRelease: %s", strlen(szTmp) == 0 ? "[" : "| ", szTmp);
523
524 vrc = RTSystemQueryOSInfo(RTSYSOSINFO_VERSION, szTmp, sizeof(szTmp));
525 if ((RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW) && szTmp[0] != '\0')
526 strPlatform.appendPrintf(" %sVersion: %s", strlen(szTmp) == 0 ? "[" : "| ", szTmp);
527
528 vrc = RTSystemQueryOSInfo(RTSYSOSINFO_SERVICE_PACK, szTmp, sizeof(szTmp));
529 if ((RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW) && szTmp[0] != '\0')
530 strPlatform.appendPrintf(" %sSP: %s]", strlen(szTmp) == 0 ? "[" : "| ", szTmp);
531
532 if (!strPlatform.endsWith("]"))
533 strPlatform.append("]");
534
535 LogRelFunc(("strPlatform = %s\n", strPlatform.c_str()));
536 }
537
538 return strPlatform;
539}
540
541HRESULT UpdateAgent::i_loadSettings(const settings::UpdateAgent &data)
542{
543 AutoCaller autoCaller(this);
544 if (FAILED(autoCaller.rc())) return autoCaller.rc();
545
546 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
547
548 m->fEnabled = data.fEnabled;
549 m->enmChannel = data.enmChannel;
550 m->uCheckFreqSeconds = data.uCheckFreqSeconds;
551 m->strRepoUrl = data.strRepoUrl;
552 m->enmProxyMode = data.enmProxyMode;
553 m->strProxyUrl = data.strProxyUrl;
554 m->strLastCheckDate = data.strLastCheckDate;
555 m->uCheckCount = data.uCheckCount;
556
557 return S_OK;
558}
559
560HRESULT UpdateAgent::i_saveSettings(settings::UpdateAgent &data)
561{
562 AutoCaller autoCaller(this);
563 if (FAILED(autoCaller.rc())) return autoCaller.rc();
564
565 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
566
567 data = *m;
568
569 return S_OK;
570}
571
572HRESULT UpdateAgent::i_setCheckCount(ULONG aCount)
573{
574 AutoCaller autoCaller(this);
575 if (FAILED(autoCaller.rc())) return autoCaller.rc();
576
577 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
578
579 m->uCheckCount = aCount;
580
581 return i_commitSettings(alock);
582}
583
584HRESULT UpdateAgent::i_setLastCheckDate(const com::Utf8Str &aDate)
585{
586 AutoCaller autoCaller(this);
587 if (FAILED(autoCaller.rc())) return autoCaller.rc();
588
589 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
590
591 m->strLastCheckDate = aDate;
592
593 return i_commitSettings(alock);
594}
595
596
597/*********************************************************************************************************************************
598* Internal helper methods *
599*********************************************************************************************************************************/
600
601/**
602 * Internal helper function to commit modified settings.
603 *
604 * @returns HRESULT
605 * @param aLock Write lock to release before committing settings.
606 */
607HRESULT UpdateAgent::i_commitSettings(AutoWriteLock &aLock)
608{
609 aLock.release();
610
611 AutoWriteLock vboxLock(m_VirtualBox COMMA_LOCKVAL_SRC_POS);
612 return m_VirtualBox->i_saveSettings();
613}
614
615#if 0
616HRESULT UpdateAgent::getUpdateCheckNeeded(BOOL *aUpdateCheckNeeded)
617{
618 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
619
620 HRESULT rc;
621 ComPtr<ISystemProperties> pSystemProperties;
622 rc = m_VirtualBox->COMGETTER(SystemProperties)(pSystemProperties.asOutParam());
623 if (FAILED(rc))
624 return rc;
625
626 /*
627 * Is update checking enabled?
628 */
629 BOOL fVBoxUpdateEnabled;
630 rc = pSystemProperties->COMGETTER(VBoxUpdateEnabled)(&fVBoxUpdateEnabled);
631 if (FAILED(rc))
632 return rc;
633
634 if (!fVBoxUpdateEnabled)
635 {
636 *aUpdateCheckNeeded = false;
637 return S_OK;
638 }
639
640 /*
641 * When was the last update?
642 */
643 Bstr strVBoxUpdateLastCheckDate;
644 rc = pSystemProperties->COMGETTER(VBoxUpdateLastCheckDate)(strVBoxUpdateLastCheckDate.asOutParam());
645 if (FAILED(rc))
646 return rc;
647
648 // No prior update check performed so do so now
649 if (strVBoxUpdateLastCheckDate.isEmpty())
650 {
651 *aUpdateCheckNeeded = true;
652 return S_OK;
653 }
654
655 // convert stored timestamp to time spec
656 RTTIMESPEC LastCheckTime;
657 if (!RTTimeSpecFromString(&LastCheckTime, Utf8Str(strVBoxUpdateLastCheckDate).c_str()))
658 {
659 *aUpdateCheckNeeded = true;
660 return S_OK;
661 }
662
663 /*
664 * Compare last update with how often we are supposed to check for updates.
665 */
666 ULONG uVBoxUpdateFrequency = 0; // value in days
667 rc = pSystemProperties->COMGETTER(VBoxUpdateFrequency)(&uVBoxUpdateFrequency);
668 if (FAILED(rc))
669 return rc;
670
671 if (!uVBoxUpdateFrequency)
672 {
673 /* Consider config (enable, 0 day interval) as checking once but never again.
674 We've already check since we've got a date. */
675 *aUpdateCheckNeeded = false;
676 return S_OK;
677 }
678 uint64_t const cSecsInXDays = uVBoxUpdateFrequency * RT_SEC_1DAY_64;
679
680 RTTIMESPEC TimeDiff;
681 RTTimeSpecSub(RTTimeNow(&TimeDiff), &LastCheckTime);
682
683 LogRelFunc(("Checking if seconds since last check (%lld) >= Number of seconds in %lu day%s (%lld)\n",
684 RTTimeSpecGetSeconds(&TimeDiff), uVBoxUpdateFrequency, uVBoxUpdateFrequency > 1 ? "s" : "", cSecsInXDays));
685
686 if (RTTimeSpecGetSeconds(&TimeDiff) >= (int64_t)cSecsInXDays)
687 *aUpdateCheckNeeded = true;
688
689 return S_OK;
690}
691#endif
692
693
694/*********************************************************************************************************************************
695* Host update implementation *
696*********************************************************************************************************************************/
697
698HostUpdateAgent::HostUpdateAgent(void)
699{
700}
701
702HostUpdateAgent::~HostUpdateAgent(void)
703{
704}
705
706
707HRESULT HostUpdateAgent::FinalConstruct(void)
708{
709 return BaseFinalConstruct();
710}
711
712void HostUpdateAgent::FinalRelease(void)
713{
714 uninit();
715
716 BaseFinalRelease();
717}
718
719HRESULT HostUpdateAgent::init(VirtualBox *aVirtualBox)
720{
721 // Enclose the state transition NotReady->InInit->Ready.
722 AutoInitSpan autoInitSpan(this);
723 AssertReturn(autoInitSpan.isOk(), E_FAIL);
724
725 /* Weak reference to a VirtualBox object */
726 unconst(m_VirtualBox) = aVirtualBox;
727
728 /* Initialize the bare minimum to get things going.
729 ** @todo Add more stuff later here. */
730 mData.m_strName = "VirtualBox";
731 mData.m_fHidden = false;
732
733 /* Set default repository. */
734 m->strRepoUrl = "https://update.virtualbox.org";
735
736 autoInitSpan.setSucceeded();
737 return S_OK;
738}
739
740void HostUpdateAgent::uninit()
741{
742 // Enclose the state transition Ready->InUninit->NotReady.
743 AutoUninitSpan autoUninitSpan(this);
744 if (autoUninitSpan.uninitDone())
745 return;
746}
747
748HRESULT HostUpdateAgent::checkFor(ComPtr<IProgress> &aProgress)
749{
750 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
751
752 ComObjPtr<Progress> pProgress;
753 HRESULT rc = pProgress.createObject();
754 if (FAILED(rc))
755 return rc;
756
757 rc = pProgress->init(m_VirtualBox,
758 static_cast<IUpdateAgent*>(this),
759 tr("Checking for update for %s ...", this->mData.m_strName.c_str()),
760 TRUE /* aCancelable */);
761 if (FAILED(rc))
762 return rc;
763
764 /* initialize the worker task */
765 UpdateAgentTask *pTask = new UpdateAgentTask(this, pProgress);
766 rc = pTask->createThread();
767 pTask = NULL;
768 if (FAILED(rc))
769 return rc;
770
771 return pProgress.queryInterfaceTo(aProgress.asOutParam());
772}
773
774
775/*********************************************************************************************************************************
776* Host update internal functions *
777*********************************************************************************************************************************/
778
779DECLCALLBACK(HRESULT) HostUpdateAgent::i_updateTask(UpdateAgentTask *pTask)
780{
781 RT_NOREF(pTask);
782
783 // Following the sequence of steps in UIUpdateStepVirtualBox::sltStartStep()
784 // Build up our query URL starting with the configured repository.
785 Utf8Str strUrl;
786 strUrl.appendPrintf("%s/query.php/?", m->strRepoUrl.c_str());
787
788 // Add platform ID.
789 Bstr platform;
790 HRESULT rc = m_VirtualBox->COMGETTER(PackageType)(platform.asOutParam());
791 AssertComRCReturn(rc, rc);
792 strUrl.appendPrintf("platform=%ls", platform.raw()); // e.g. SOLARIS_64BITS_GENERIC
793
794 // Get the complete current version string for the query URL
795 Bstr versionNormalized;
796 rc = m_VirtualBox->COMGETTER(VersionNormalized)(versionNormalized.asOutParam());
797 AssertComRCReturn(rc, rc);
798 strUrl.appendPrintf("&version=%ls", versionNormalized.raw()); // e.g. 6.1.1
799#ifdef DEBUG // Comment out previous line and uncomment this one for testing.
800// strUrl.appendPrintf("&version=6.0.12");
801#endif
802
803 ULONG revision = 0;
804 rc = m_VirtualBox->COMGETTER(Revision)(&revision);
805 AssertComRCReturn(rc, rc);
806 strUrl.appendPrintf("_%u", revision); // e.g. 135618
807
808 // Update the last update check timestamp.
809 RTTIME Time;
810 RTTIMESPEC TimeNow;
811 char szTimeStr[RTTIME_STR_LEN];
812 RTTimeToString(RTTimeExplode(&Time, RTTimeNow(&TimeNow)), szTimeStr, sizeof(szTimeStr));
813 LogRel2(("Update agent (%s): Setting last update check timestamp to '%s'\n", mData.m_strName.c_str(), szTimeStr));
814
815 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
816
817 m->strLastCheckDate = szTimeStr;
818 m->uCheckCount++;
819
820 rc = i_commitSettings(alock);
821 AssertComRCReturn(rc, rc);
822
823 strUrl.appendPrintf("&count=%RU32", m->uCheckCount);
824
825 // Update the query URL (if necessary) with the 'channel' information.
826 switch (m->enmChannel)
827 {
828 case UpdateChannel_All:
829 strUrl.appendPrintf("&branch=allrelease"); // query.php expects 'allrelease' and not 'allreleases'
830 break;
831 case UpdateChannel_WithBetas:
832 strUrl.appendPrintf("&branch=withbetas");
833 break;
834 /** @todo Handle UpdateChannel_WithTesting once implemented on the backend. */
835 case UpdateChannel_Stable:
836 RT_FALL_THROUGH();
837 default:
838 strUrl.appendPrintf("&branch=stable");
839 break;
840 }
841
842 LogRel2(("Update agent (%s): Using URL '%s'\n", mData.m_strName.c_str(), strUrl.c_str()));
843
844 /*
845 * Compose the User-Agent header for the GET request.
846 */
847 Bstr version;
848 rc = m_VirtualBox->COMGETTER(Version)(version.asOutParam()); // e.g. 6.1.0_RC1
849 AssertComRCReturn(rc, rc);
850
851 Utf8StrFmt const strUserAgent("VirtualBox %ls <%s>", version.raw(), UpdateAgent::i_getPlatformInfo().c_str());
852 LogRel2(("Update agent (%s): Using user agent '%s'\n", mData.m_strName.c_str(), strUserAgent.c_str()));
853
854 /*
855 * Create the HTTP client instance and pass it to a inner worker method to
856 * ensure proper cleanup.
857 */
858 RTHTTP hHttp = NIL_RTHTTP;
859 int vrc = RTHttpCreate(&hHttp);
860 if (RT_SUCCESS(vrc))
861 {
862 try
863 {
864 rc = i_checkForUpdateInner(hHttp, strUrl, strUserAgent);
865 }
866 catch (...)
867 {
868 AssertFailed();
869 rc = E_UNEXPECTED;
870 }
871 RTHttpDestroy(hHttp);
872 }
873 else
874 rc = setErrorVrc(vrc, tr("Update agent (%s): RTHttpCreate() failed: %Rrc"), mData.m_strName.c_str(), vrc);
875
876 return rc;
877}
878
879HRESULT HostUpdateAgent::i_checkForUpdateInner(RTHTTP hHttp, Utf8Str const &strUrl, Utf8Str const &strUserAgent)
880{
881 /** @todo Are there any other headers needed to be added first via RTHttpSetHeaders()? */
882 int vrc = RTHttpAddHeader(hHttp, "User-Agent", strUserAgent.c_str(), strUserAgent.length(), RTHTTPADDHDR_F_BACK);
883 if (RT_FAILURE(vrc))
884 return setErrorVrc(vrc, tr("Update agent (%s): RTHttpAddHeader() failed: %Rrc (on User-Agent)"),
885 mData.m_strName.c_str(), vrc);
886
887 /*
888 * Configure proxying.
889 */
890 if (m->enmProxyMode == ProxyMode_Manual)
891 {
892 vrc = RTHttpSetProxyByUrl(hHttp, m->strProxyUrl.c_str());
893 if (RT_FAILURE(vrc))
894 return setErrorVrc(vrc, tr("Update agent (%s): RTHttpSetProxyByUrl() failed: %Rrc"), mData.m_strName.c_str(), vrc);
895 }
896 else if (m->enmProxyMode == ProxyMode_System)
897 {
898 vrc = RTHttpUseSystemProxySettings(hHttp);
899 if (RT_FAILURE(vrc))
900 return setErrorVrc(vrc, tr("Update agent (%s): RTHttpUseSystemProxySettings() failed: %Rrc"),
901 mData.m_strName.c_str(), vrc);
902 }
903 else
904 Assert(m->enmProxyMode == ProxyMode_NoProxy);
905
906 /*
907 * Perform the GET request, returning raw binary stuff.
908 */
909 void *pvResponse = NULL;
910 size_t cbResponse = 0;
911 vrc = RTHttpGetBinary(hHttp, strUrl.c_str(), &pvResponse, &cbResponse);
912 if (RT_FAILURE(vrc))
913 return setErrorVrc(vrc, tr("Update agent (%s): RTHttpGetBinary() failed: %Rrc"), mData.m_strName.c_str(), vrc);
914
915 /* Note! We can do nothing that might throw exceptions till we call RTHttpFreeResponse! */
916
917 /*
918 * If url is platform=DARWIN_64BITS_GENERIC&version=6.0.12&branch=stable for example, the reply is:
919 * 6.0.14<SPACE>https://download.virtualbox.org/virtualbox/6.0.14/VirtualBox-6.0.14-133895-OSX.dmg
920 * If no update required, 'UPTODATE' is returned.
921 */
922 /* Parse out the two first words of the response, ignoring whatever follows: */
923 const char *pchResponse = (const char *)pvResponse;
924 while (cbResponse > 0 && *pchResponse == ' ')
925 cbResponse--, pchResponse++;
926
927 char ch;
928 const char *pchWord0 = pchResponse;
929 while (cbResponse > 0 && (ch = *pchResponse) != ' ' && ch != '\0')
930 cbResponse--, pchResponse++;
931 size_t const cchWord0 = (size_t)(pchResponse - pchWord0);
932
933 while (cbResponse > 0 && *pchResponse == ' ')
934 cbResponse--, pchResponse++;
935 const char *pchWord1 = pchResponse;
936 while (cbResponse > 0 && (ch = *pchResponse) != ' ' && ch != '\0')
937 cbResponse--, pchResponse++;
938 size_t const cchWord1 = (size_t)(pchResponse - pchWord1);
939
940 HRESULT rc;
941
942 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
943
944 /* Decode the two word: */
945 static char const s_szUpToDate[] = "UPTODATE";
946 if ( cchWord0 == sizeof(s_szUpToDate) - 1
947 && memcmp(pchWord0, s_szUpToDate, sizeof(s_szUpToDate) - 1) == 0)
948 {
949 mData.m_enmState = UpdateState_NotAvailable;
950 rc = S_OK;
951 }
952 else
953 {
954 mData.m_enmState = UpdateState_Error; /* Play safe by default. */
955
956 vrc = RTStrValidateEncodingEx(pchWord0, cchWord0, 0 /*fFlags*/);
957 if (RT_SUCCESS(vrc))
958 vrc = RTStrValidateEncodingEx(pchWord1, cchWord1, 0 /*fFlags*/);
959 if (RT_SUCCESS(vrc))
960 {
961 /** @todo Any additional sanity checks we could perform here? */
962 rc = mData.m_lastResult.strVer.assignEx(pchWord0, cchWord0);
963 if (SUCCEEDED(rc))
964 rc = mData.m_lastResult.strDownloadUrl.assignEx(pchWord1, cchWord1);
965
966 if (RT_SUCCESS(vrc))
967 {
968 /** @todo Implement this on the backend first.
969 * We also could do some guessing based on the installed version vs. reported update version? */
970 mData.m_lastResult.enmSeverity = UpdateSeverity_Invalid;
971 mData.m_enmState = UpdateState_Available;
972 }
973
974 LogRel(("Update agent (%s): HTTP server replied: %.*s %.*s\n",
975 mData.m_strName.c_str(), cchWord0, pchWord0, cchWord1, pchWord1));
976 }
977 else
978 rc = setErrorVrc(vrc, tr("Update agent (%s): Invalid server response: %Rrc (%.*Rhxs -- %.*Rhxs)"),
979 mData.m_strName.c_str(), vrc, cchWord0, pchWord0, cchWord1, pchWord1);
980 }
981
982 RTHttpFreeResponse(pvResponse);
983
984 return rc;
985}
986
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