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/prefs_interface.h"
30 #include "update_engine/system_state.h"
31 
32 using base::TimeDelta;
33 using chromeos_update_engine::kPrefsWallClockStagingWaitPeriod;
34 using chromeos_update_engine::PrefsInterface;
35 using policy::DevicePolicy;
36 
37 namespace chromeos_update_manager {
38 
GetStagingSchedule(const DevicePolicy * device_policy,StagingSchedule * staging_schedule_out)39 int GetStagingSchedule(const DevicePolicy* device_policy,
40                        StagingSchedule* staging_schedule_out) {
41   StagingSchedule staging_schedule;
42   if (!device_policy->GetDeviceUpdateStagingSchedule(&staging_schedule) ||
43       staging_schedule.empty()) {
44     return 0;
45   }
46 
47   // Last percentage of the schedule should be 100.
48   if (staging_schedule.back().percentage != 100) {
49     LOG(ERROR) << "Last percentage of the schedule is not 100, it's: "
50                << staging_schedule.back().percentage;
51     return 0;
52   }
53 
54   int previous_days = 0;
55   int previous_percentage = -1;
56   // Ensure that the schedule has a monotonically increasing set of percentages
57   // and that days are also monotonically increasing.
58   for (const auto& staging_pair : staging_schedule) {
59     int days = staging_pair.days;
60     if (previous_days >= days) {
61       LOG(ERROR) << "Days in staging schedule are not monotonically "
62                  << "increasing. Previous value: " << previous_days
63                  << " Current value: " << days;
64       return 0;
65     }
66     previous_days = days;
67     int percentage = staging_pair.percentage;
68     if (previous_percentage >= percentage) {
69       LOG(ERROR) << "Percentages in staging schedule are not monotonically "
70                  << "increasing. Previous value: " << previous_percentage
71                  << " Current value: " << percentage;
72       return 0;
73     }
74     previous_percentage = percentage;
75   }
76   // Modify staging schedule only if the schedule in the device policy is valid.
77   if (staging_schedule_out)
78     *staging_schedule_out = std::move(staging_schedule);
79 
80   return previous_days;
81 }
82 
CalculateWaitTimeInDaysFromSchedule(const StagingSchedule & staging_schedule)83 int CalculateWaitTimeInDaysFromSchedule(
84     const StagingSchedule& staging_schedule) {
85   int prev_days = 0;
86   int percentage_position = base::RandInt(1, 100);
87   for (const auto& staging_pair : staging_schedule) {
88     int days = staging_pair.days;
89     if (percentage_position <= staging_pair.percentage) {
90       // Scatter between the start of the range and the end.
91       return prev_days + base::RandInt(1, days - prev_days);
92     }
93     prev_days = days;
94   }
95   // Something went wrong.
96   NOTREACHED();
97   return 0;
98 }
99 
CalculateStagingCase(const DevicePolicy * device_policy,PrefsInterface * prefs,TimeDelta * staging_wait_time,StagingSchedule * staging_schedule)100 StagingCase CalculateStagingCase(const DevicePolicy* device_policy,
101                                  PrefsInterface* prefs,
102                                  TimeDelta* staging_wait_time,
103                                  StagingSchedule* staging_schedule) {
104   // Check that the schedule in the device policy is correct.
105   StagingSchedule new_staging_schedule;
106   int max_days = GetStagingSchedule(device_policy, &new_staging_schedule);
107   if (max_days == 0)
108     return StagingCase::kOff;
109 
110   // Calculate the new wait time.
111   TimeDelta new_staging_wait_time = TimeDelta::FromDays(
112       CalculateWaitTimeInDaysFromSchedule(new_staging_schedule));
113   DCHECK_GT(new_staging_wait_time.InSeconds(), 0);
114   if (staging_wait_time->InSeconds() > 0) {
115     // If there hasn't been any changes to the schedule and there is a value
116     // set, don't change the waiting time.
117     if (new_staging_schedule == *staging_schedule) {
118       return StagingCase::kNoAction;
119     }
120     // Otherwise, update the schedule and wait time.
121     *staging_wait_time = new_staging_wait_time;
122     *staging_schedule = std::move(new_staging_schedule);
123     return StagingCase::kNoSavedValue;
124   }
125   // Getting this means the schedule changed, update the old schedule.
126   *staging_schedule = std::move(new_staging_schedule);
127 
128   int64_t wait_period_in_days;
129   // There exists a persisted value that is valid. That is, it's smaller than
130   // the maximum amount of days of staging set by the user.
131   if (prefs->GetInt64(kPrefsWallClockStagingWaitPeriod, &wait_period_in_days) &&
132       wait_period_in_days > 0 && wait_period_in_days <= max_days) {
133     *staging_wait_time = TimeDelta::FromDays(wait_period_in_days);
134     return StagingCase::kSetStagingFromPref;
135   }
136 
137   *staging_wait_time = new_staging_wait_time;
138   return StagingCase::kNoSavedValue;
139 }
140 
141 }  // namespace chromeos_update_manager
142