1 //
2 // Copyright (C) 2018 The Android Open Source Project
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16
17 #include "update_engine/update_manager/staging_utils.h"
18
19 #include <utility>
20 #include <vector>
21
22 #include <base/logging.h>
23 #include <base/rand_util.h>
24 #include <base/time/time.h>
25 #include <policy/device_policy.h>
26
27 #include "update_engine/common/constants.h"
28 #include "update_engine/common/hardware_interface.h"
29 #include "update_engine/common/system_state.h"
30
31 using base::TimeDelta;
32 using chromeos_update_engine::kPrefsWallClockStagingWaitPeriod;
33 using chromeos_update_engine::SystemState;
34 using policy::DevicePolicy;
35
36 namespace chromeos_update_manager {
37
GetStagingSchedule(const DevicePolicy * device_policy,StagingSchedule * staging_schedule_out)38 int GetStagingSchedule(const DevicePolicy* device_policy,
39 StagingSchedule* staging_schedule_out) {
40 StagingSchedule staging_schedule;
41 if (!device_policy->GetDeviceUpdateStagingSchedule(&staging_schedule) ||
42 staging_schedule.empty()) {
43 return 0;
44 }
45
46 // Last percentage of the schedule should be 100.
47 if (staging_schedule.back().percentage != 100) {
48 LOG(ERROR) << "Last percentage of the schedule is not 100, it's: "
49 << staging_schedule.back().percentage;
50 return 0;
51 }
52
53 int previous_days = 0;
54 int previous_percentage = -1;
55 // Ensure that the schedule has a monotonically increasing set of percentages
56 // and that days are also monotonically increasing.
57 for (const auto& staging_pair : staging_schedule) {
58 int days = staging_pair.days;
59 if (previous_days >= days) {
60 LOG(ERROR) << "Days in staging schedule are not monotonically "
61 << "increasing. Previous value: " << previous_days
62 << " Current value: " << days;
63 return 0;
64 }
65 previous_days = days;
66 int percentage = staging_pair.percentage;
67 if (previous_percentage >= percentage) {
68 LOG(ERROR) << "Percentages in staging schedule are not monotonically "
69 << "increasing. Previous value: " << previous_percentage
70 << " Current value: " << percentage;
71 return 0;
72 }
73 previous_percentage = percentage;
74 }
75 // Modify staging schedule only if the schedule in the device policy is valid.
76 if (staging_schedule_out)
77 *staging_schedule_out = std::move(staging_schedule);
78
79 return previous_days;
80 }
81
CalculateWaitTimeInDaysFromSchedule(const StagingSchedule & staging_schedule)82 int CalculateWaitTimeInDaysFromSchedule(
83 const StagingSchedule& staging_schedule) {
84 int prev_days = 0;
85 int percentage_position = base::RandInt(1, 100);
86 for (const auto& staging_pair : staging_schedule) {
87 int days = staging_pair.days;
88 if (percentage_position <= staging_pair.percentage) {
89 // Scatter between the start of the range and the end.
90 return prev_days + base::RandInt(1, days - prev_days);
91 }
92 prev_days = days;
93 }
94 // Something went wrong.
95 NOTREACHED();
96 return 0;
97 }
98
CalculateStagingCase(const DevicePolicy * device_policy,TimeDelta * staging_wait_time,StagingSchedule * staging_schedule)99 StagingCase CalculateStagingCase(const DevicePolicy* device_policy,
100 TimeDelta* staging_wait_time,
101 StagingSchedule* staging_schedule) {
102 // Check that the schedule in the device policy is correct.
103 StagingSchedule new_staging_schedule;
104 int max_days = GetStagingSchedule(device_policy, &new_staging_schedule);
105 if (max_days == 0)
106 return StagingCase::kOff;
107
108 // Calculate the new wait time.
109 TimeDelta new_staging_wait_time = TimeDelta::FromDays(
110 CalculateWaitTimeInDaysFromSchedule(new_staging_schedule));
111 DCHECK_GT(new_staging_wait_time.InSeconds(), 0);
112 if (staging_wait_time->InSeconds() > 0) {
113 // If there hasn't been any changes to the schedule and there is a value
114 // set, don't change the waiting time.
115 if (new_staging_schedule == *staging_schedule) {
116 return StagingCase::kNoAction;
117 }
118 // Otherwise, update the schedule and wait time.
119 *staging_wait_time = new_staging_wait_time;
120 *staging_schedule = std::move(new_staging_schedule);
121 return StagingCase::kNoSavedValue;
122 }
123 // Getting this means the schedule changed, update the old schedule.
124 *staging_schedule = std::move(new_staging_schedule);
125
126 int64_t wait_period_in_days;
127 // There exists a persisted value that is valid. That is, it's smaller than
128 // the maximum amount of days of staging set by the user.
129 if (SystemState::Get()->prefs()->GetInt64(kPrefsWallClockStagingWaitPeriod,
130 &wait_period_in_days) &&
131 wait_period_in_days > 0 && wait_period_in_days <= max_days) {
132 *staging_wait_time = TimeDelta::FromDays(wait_period_in_days);
133 return StagingCase::kSetStagingFromPref;
134 }
135
136 *staging_wait_time = new_staging_wait_time;
137 return StagingCase::kNoSavedValue;
138 }
139
140 } // namespace chromeos_update_manager
141