VirtualBox

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

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

Main/Update check: Big overhaul of the API and functionality [build fix]. bugref:7983

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