Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

updater.php 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. <?php
  2. /**
  3. * @author Arthur Schiwon <blizzz@owncloud.com>
  4. * @author Bart Visscher <bartv@thisnet.nl>
  5. * @author Björn Schießle <schiessle@owncloud.com>
  6. * @author Frank Karlitschek <frank@owncloud.org>
  7. * @author Lukas Reschke <lukas@owncloud.com>
  8. * @author Morris Jobke <hey@morrisjobke.de>
  9. * @author Robin Appelman <icewind@owncloud.com>
  10. * @author Robin McCorkell <rmccorkell@karoshi.org.uk>
  11. * @author Steffen Lindner <mail@steffen-lindner.de>
  12. * @author Thomas Müller <thomas.mueller@tmit.eu>
  13. * @author Victor Dubiniuk <dubiniuk@owncloud.com>
  14. * @author Vincent Petry <pvince81@owncloud.com>
  15. *
  16. * @copyright Copyright (c) 2015, ownCloud, Inc.
  17. * @license AGPL-3.0
  18. *
  19. * This code is free software: you can redistribute it and/or modify
  20. * it under the terms of the GNU Affero General Public License, version 3,
  21. * as published by the Free Software Foundation.
  22. *
  23. * This program is distributed in the hope that it will be useful,
  24. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  25. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  26. * GNU Affero General Public License for more details.
  27. *
  28. * You should have received a copy of the GNU Affero General Public License, version 3,
  29. * along with this program. If not, see <http://www.gnu.org/licenses/>
  30. *
  31. */
  32. namespace OC;
  33. use OC\Hooks\BasicEmitter;
  34. use OC_App;
  35. use OC_Installer;
  36. use OC_Util;
  37. use OCP\IConfig;
  38. use OC\Setup;
  39. use OCP\ILogger;
  40. /**
  41. * Class that handles autoupdating of ownCloud
  42. *
  43. * Hooks provided in scope \OC\Updater
  44. * - maintenanceStart()
  45. * - maintenanceEnd()
  46. * - dbUpgrade()
  47. * - failure(string $message)
  48. */
  49. class Updater extends BasicEmitter {
  50. /** @var ILogger $log */
  51. private $log;
  52. /** @var \OC\HTTPHelper $helper */
  53. private $httpHelper;
  54. /** @var IConfig */
  55. private $config;
  56. /** @var bool */
  57. private $simulateStepEnabled;
  58. /** @var bool */
  59. private $updateStepEnabled;
  60. /** @var bool */
  61. private $skip3rdPartyAppsDisable;
  62. /**
  63. * @param HTTPHelper $httpHelper
  64. * @param IConfig $config
  65. * @param ILogger $log
  66. */
  67. public function __construct(HTTPHelper $httpHelper,
  68. IConfig $config,
  69. ILogger $log = null) {
  70. $this->httpHelper = $httpHelper;
  71. $this->log = $log;
  72. $this->config = $config;
  73. $this->simulateStepEnabled = true;
  74. $this->updateStepEnabled = true;
  75. }
  76. /**
  77. * Sets whether the database migration simulation must
  78. * be enabled.
  79. * This can be set to false to skip this test.
  80. *
  81. * @param bool $flag true to enable simulation, false otherwise
  82. */
  83. public function setSimulateStepEnabled($flag) {
  84. $this->simulateStepEnabled = $flag;
  85. }
  86. /**
  87. * Sets whether the update must be performed.
  88. * This can be set to false to skip the actual update.
  89. *
  90. * @param bool $flag true to enable update, false otherwise
  91. */
  92. public function setUpdateStepEnabled($flag) {
  93. $this->updateStepEnabled = $flag;
  94. }
  95. /**
  96. * Sets whether the update disables 3rd party apps.
  97. * This can be set to true to skip the disable.
  98. *
  99. * @param bool $flag false to not disable, true otherwise
  100. */
  101. public function setSkip3rdPartyAppsDisable($flag) {
  102. $this->skip3rdPartyAppsDisable = $flag;
  103. }
  104. /**
  105. * Check if a new version is available
  106. *
  107. * @param string $updaterUrl the url to check, i.e. 'http://apps.owncloud.com/updater.php'
  108. * @return array|bool
  109. */
  110. public function check($updaterUrl = null) {
  111. // Look up the cache - it is invalidated all 30 minutes
  112. if (((int)$this->config->getAppValue('core', 'lastupdatedat') + 1800) > time()) {
  113. return json_decode($this->config->getAppValue('core', 'lastupdateResult'), true);
  114. }
  115. if (is_null($updaterUrl)) {
  116. $updaterUrl = 'https://updates.owncloud.com/server/';
  117. }
  118. $this->config->setAppValue('core', 'lastupdatedat', time());
  119. if ($this->config->getAppValue('core', 'installedat', '') === '') {
  120. $this->config->setAppValue('core', 'installedat', microtime(true));
  121. }
  122. $version = \OC_Util::getVersion();
  123. $version['installed'] = $this->config->getAppValue('core', 'installedat');
  124. $version['updated'] = $this->config->getAppValue('core', 'lastupdatedat');
  125. $version['updatechannel'] = \OC_Util::getChannel();
  126. $version['edition'] = \OC_Util::getEditionString();
  127. $version['build'] = \OC_Util::getBuild();
  128. $versionString = implode('x', $version);
  129. //fetch xml data from updater
  130. $url = $updaterUrl . '?version=' . $versionString;
  131. $tmp = [];
  132. $xml = $this->httpHelper->getUrlContent($url);
  133. if ($xml) {
  134. $loadEntities = libxml_disable_entity_loader(true);
  135. $data = @simplexml_load_string($xml);
  136. libxml_disable_entity_loader($loadEntities);
  137. if ($data !== false) {
  138. $tmp['version'] = (string)$data->version;
  139. $tmp['versionstring'] = (string)$data->versionstring;
  140. $tmp['url'] = (string)$data->url;
  141. $tmp['web'] = (string)$data->web;
  142. }
  143. } else {
  144. $data = [];
  145. }
  146. // Cache the result
  147. $this->config->setAppValue('core', 'lastupdateResult', json_encode($data));
  148. return $tmp;
  149. }
  150. /**
  151. * runs the update actions in maintenance mode, does not upgrade the source files
  152. * except the main .htaccess file
  153. *
  154. * @return bool true if the operation succeeded, false otherwise
  155. */
  156. public function upgrade() {
  157. $wasMaintenanceModeEnabled = $this->config->getSystemValue('maintenance', false);
  158. if(!$wasMaintenanceModeEnabled) {
  159. $this->config->setSystemValue('maintenance', true);
  160. $this->emit('\OC\Updater', 'maintenanceEnabled');
  161. }
  162. $installedVersion = $this->config->getSystemValue('version', '0.0.0');
  163. $currentVersion = implode('.', \OC_Util::getVersion());
  164. if ($this->log) {
  165. $this->log->debug('starting upgrade from ' . $installedVersion . ' to ' . $currentVersion, array('app' => 'core'));
  166. }
  167. $success = true;
  168. try {
  169. $this->doUpgrade($currentVersion, $installedVersion);
  170. } catch (\Exception $exception) {
  171. \OCP\Util::logException('update', $exception);
  172. $this->emit('\OC\Updater', 'failure', array(get_class($exception) . ': ' .$exception->getMessage()));
  173. $success = false;
  174. }
  175. $this->emit('\OC\Updater', 'updateEnd', array($success));
  176. if(!$wasMaintenanceModeEnabled && $success) {
  177. $this->config->setSystemValue('maintenance', false);
  178. $this->emit('\OC\Updater', 'maintenanceDisabled');
  179. } else {
  180. $this->emit('\OC\Updater', 'maintenanceActive');
  181. }
  182. return $success;
  183. }
  184. /**
  185. * Whether an upgrade to a specified version is possible
  186. * @param string $oldVersion
  187. * @param string $newVersion
  188. * @return bool
  189. */
  190. public function isUpgradePossible($oldVersion, $newVersion) {
  191. $oldVersion = explode('.', $oldVersion);
  192. $newVersion = explode('.', $newVersion);
  193. if($newVersion[0] > ($oldVersion[0] + 1) || $oldVersion[0] > $newVersion[0]) {
  194. return false;
  195. }
  196. return true;
  197. }
  198. /**
  199. * Forward messages emitted by the repair routine
  200. *
  201. * @param Repair $repair repair routine
  202. */
  203. private function emitRepairMessages(Repair $repair) {
  204. $repair->listen('\OC\Repair', 'warning', function ($description) {
  205. $this->emit('\OC\Updater', 'repairWarning', array($description));
  206. });
  207. $repair->listen('\OC\Repair', 'error', function ($description) {
  208. $this->emit('\OC\Updater', 'repairError', array($description));
  209. });
  210. $repair->listen('\OC\Repair', 'info', function ($description) {
  211. $this->emit('\OC\Updater', 'repairInfo', array($description));
  212. });
  213. $repair->listen('\OC\Repair', 'step', function ($description) {
  214. $this->emit('\OC\Updater', 'repairStep', array($description));
  215. });
  216. }
  217. /**
  218. * runs the update actions in maintenance mode, does not upgrade the source files
  219. * except the main .htaccess file
  220. *
  221. * @param string $currentVersion current version to upgrade to
  222. * @param string $installedVersion previous version from which to upgrade from
  223. *
  224. * @throws \Exception
  225. * @return bool true if the operation succeeded, false otherwise
  226. */
  227. private function doUpgrade($currentVersion, $installedVersion) {
  228. // Stop update if the update is over several major versions
  229. if (!self::isUpgradePossible($installedVersion, $currentVersion)) {
  230. throw new \Exception('Updates between multiple major versions are unsupported.');
  231. }
  232. // Update .htaccess files
  233. try {
  234. Setup::updateHtaccess();
  235. Setup::protectDataDirectory();
  236. } catch (\Exception $e) {
  237. throw new \Exception($e->getMessage());
  238. }
  239. // FIXME: Some users do not upload the new ca-bundle.crt, let's catch this
  240. // in the update. For a newer release we shall use an integrity check after
  241. // the update.
  242. if(!file_exists(\OC::$configDir .'/ca-bundle.crt')) {
  243. throw new \Exception('Please upload the ca-bundle.crt file into the \'config\' directory.');
  244. }
  245. // create empty file in data dir, so we can later find
  246. // out that this is indeed an ownCloud data directory
  247. // (in case it didn't exist before)
  248. file_put_contents($this->config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . '/.ocdata', '');
  249. // pre-upgrade repairs
  250. $repair = new Repair(Repair::getBeforeUpgradeRepairSteps());
  251. $this->emitRepairMessages($repair);
  252. $repair->run();
  253. // simulate DB upgrade
  254. if ($this->simulateStepEnabled) {
  255. $this->checkCoreUpgrade();
  256. // simulate apps DB upgrade
  257. $this->checkAppUpgrade($currentVersion);
  258. }
  259. if ($this->updateStepEnabled) {
  260. $this->doCoreUpgrade();
  261. // update all shipped apps
  262. $disabledApps = $this->checkAppsRequirements();
  263. $this->doAppUpgrade();
  264. // upgrade appstore apps
  265. $this->upgradeAppStoreApps($disabledApps);
  266. // post-upgrade repairs
  267. $repair = new Repair(Repair::getRepairSteps());
  268. $this->emitRepairMessages($repair);
  269. $repair->run();
  270. //Invalidate update feed
  271. $this->config->setAppValue('core', 'lastupdatedat', 0);
  272. // only set the final version if everything went well
  273. $this->config->setSystemValue('version', implode('.', \OC_Util::getVersion()));
  274. }
  275. }
  276. protected function checkCoreUpgrade() {
  277. // simulate core DB upgrade
  278. \OC_DB::simulateUpdateDbFromStructure(\OC::$SERVERROOT . '/db_structure.xml');
  279. $this->emit('\OC\Updater', 'dbSimulateUpgrade');
  280. }
  281. protected function doCoreUpgrade() {
  282. // do the real upgrade
  283. \OC_DB::updateDbFromStructure(\OC::$SERVERROOT . '/db_structure.xml');
  284. $this->emit('\OC\Updater', 'dbUpgrade');
  285. }
  286. /**
  287. * @param string $version the oc version to check app compatibility with
  288. */
  289. protected function checkAppUpgrade($version) {
  290. $apps = \OC_App::getEnabledApps();
  291. foreach ($apps as $appId) {
  292. $info = \OC_App::getAppInfo($appId);
  293. $compatible = \OC_App::isAppCompatible($version, $info);
  294. $isShipped = \OC_App::isShipped($appId);
  295. if ($compatible && $isShipped && \OC_App::shouldUpgrade($appId)) {
  296. /**
  297. * FIXME: The preupdate check is performed before the database migration, otherwise database changes
  298. * are not possible anymore within it. - Consider this when touching the code.
  299. * @link https://github.com/owncloud/core/issues/10980
  300. * @see \OC_App::updateApp
  301. */
  302. if (file_exists(\OC_App::getAppPath($appId) . '/appinfo/preupdate.php')) {
  303. $this->includePreUpdate($appId);
  304. }
  305. if (file_exists(\OC_App::getAppPath($appId) . '/appinfo/database.xml')) {
  306. \OC_DB::simulateUpdateDbFromStructure(\OC_App::getAppPath($appId) . '/appinfo/database.xml');
  307. }
  308. }
  309. }
  310. $this->emit('\OC\Updater', 'appUpgradeCheck');
  311. }
  312. /**
  313. * Includes the pre-update file. Done here to prevent namespace mixups.
  314. * @param string $appId
  315. */
  316. private function includePreUpdate($appId) {
  317. include \OC_App::getAppPath($appId) . '/appinfo/preupdate.php';
  318. }
  319. /**
  320. * upgrades all apps within a major ownCloud upgrade. Also loads "priority"
  321. * (types authentication, filesystem, logging, in that order) afterwards.
  322. *
  323. * @throws NeedsUpdateException
  324. */
  325. protected function doAppUpgrade() {
  326. $apps = \OC_App::getEnabledApps();
  327. $priorityTypes = array('authentication', 'filesystem', 'logging');
  328. $pseudoOtherType = 'other';
  329. $stacks = array($pseudoOtherType => array());
  330. foreach ($apps as $appId) {
  331. $priorityType = false;
  332. foreach ($priorityTypes as $type) {
  333. if(!isset($stacks[$type])) {
  334. $stacks[$type] = array();
  335. }
  336. if (\OC_App::isType($appId, $type)) {
  337. $stacks[$type][] = $appId;
  338. $priorityType = true;
  339. break;
  340. }
  341. }
  342. if (!$priorityType) {
  343. $stacks[$pseudoOtherType][] = $appId;
  344. }
  345. }
  346. foreach ($stacks as $type => $stack) {
  347. foreach ($stack as $appId) {
  348. if (\OC_App::shouldUpgrade($appId)) {
  349. $this->emit('\OC\Updater', 'appUpgradeStarted', array($appId, \OC_App::getAppVersion($appId)));
  350. \OC_App::updateApp($appId);
  351. $this->emit('\OC\Updater', 'appUpgrade', array($appId, \OC_App::getAppVersion($appId)));
  352. }
  353. if($type !== $pseudoOtherType) {
  354. // load authentication, filesystem and logging apps after
  355. // upgrading them. Other apps my need to rely on modifying
  356. // user and/or filesystem aspects.
  357. \OC_App::loadApp($appId, false);
  358. }
  359. }
  360. }
  361. }
  362. /**
  363. * check if the current enabled apps are compatible with the current
  364. * ownCloud version. disable them if not.
  365. * This is important if you upgrade ownCloud and have non ported 3rd
  366. * party apps installed.
  367. *
  368. * @return array
  369. * @throws \Exception
  370. */
  371. private function checkAppsRequirements() {
  372. $isCoreUpgrade = $this->isCodeUpgrade();
  373. $apps = OC_App::getEnabledApps();
  374. $version = OC_Util::getVersion();
  375. $disabledApps = [];
  376. foreach ($apps as $app) {
  377. // check if the app is compatible with this version of ownCloud
  378. $info = OC_App::getAppInfo($app);
  379. if(!OC_App::isAppCompatible($version, $info)) {
  380. OC_App::disable($app);
  381. $this->emit('\OC\Updater', 'incompatibleAppDisabled', array($app));
  382. }
  383. // no need to disable any app in case this is a non-core upgrade
  384. if (!$isCoreUpgrade) {
  385. continue;
  386. }
  387. // shipped apps will remain enabled
  388. if (OC_App::isShipped($app)) {
  389. continue;
  390. }
  391. // authentication and session apps will remain enabled as well
  392. if (OC_App::isType($app, ['session', 'authentication'])) {
  393. continue;
  394. }
  395. // disable any other 3rd party apps if not overriden
  396. if(!$this->skip3rdPartyAppsDisable) {
  397. \OC_App::disable($app);
  398. $disabledApps[]= $app;
  399. $this->emit('\OC\Updater', 'thirdPartyAppDisabled', array($app));
  400. };
  401. }
  402. return $disabledApps;
  403. }
  404. /**
  405. * @return bool
  406. */
  407. private function isCodeUpgrade() {
  408. $installedVersion = $this->config->getSystemValue('version', '0.0.0');
  409. $currentVersion = implode('.', OC_Util::getVersion());
  410. if (version_compare($currentVersion, $installedVersion, '>')) {
  411. return true;
  412. }
  413. return false;
  414. }
  415. /**
  416. * @param array $disabledApps
  417. * @throws \Exception
  418. */
  419. private function upgradeAppStoreApps(array $disabledApps) {
  420. foreach($disabledApps as $app) {
  421. if (OC_Installer::isUpdateAvailable($app)) {
  422. $ocsId = \OC::$server->getConfig()->getAppValue($app, 'ocsid', '');
  423. $this->emit('\OC\Updater', 'upgradeAppStoreApp', array($app));
  424. OC_Installer::updateAppByOCSId($ocsId);
  425. }
  426. }
  427. }
  428. }