1 /**************************************************************************
2 Copyright:
3 (C) 2008 - 2012 Alexander Shaduri <ashaduri 'at' gmail.com>
4 License: See LICENSE_gsmartcontrol.txt
5 ***************************************************************************/
6 /// \file
7 /// \author Alexander Shaduri
8 /// \ingroup applib
9 /// \weakgroup applib
10 /// @{
11
12 #include <algorithm> // std::max, std::min
13 #include <cmath> // std::floor
14
15 #include "app_pcrecpp.h"
16 #include "storage_property.h"
17 #include "smartctl_parser.h"
18 #include "selftest.h"
19
20
21
22 // Returns estimated time of completion for the test. returns -1 if n/a or unknown. 0 is a valid value.
get_remaining_seconds() const23 int64_t SelfTest::get_remaining_seconds() const
24 {
25 int64_t total = get_min_duration_seconds();
26 if (total <= 0)
27 return -1; // unknown
28
29 double gran = (double(total) / 9.); // seconds per 10%
30 // since remaining_percent_ may be manually set to 100, we limit from the above.
31 double rem_seconds_at_last_change = std::min(double(total), gran * remaining_percent_ / 10.);
32 double rem = rem_seconds_at_last_change - timer_.elapsed();
33 return std::max(int64_t(0), int64_t(std::floor(rem + 0.5))); // aka round. don't return negative values.
34 }
35
36
37
38 // a drive reports a constant "test duration during idle" capability.
get_min_duration_seconds() const39 int64_t SelfTest::get_min_duration_seconds() const
40 {
41 if (!drive_)
42 return -1; // n/a
43
44 if (total_duration_ != -1) // cache
45 return total_duration_;
46
47 std::string prop_name;
48 switch(type_) {
49 case type_ioffline: prop_name = "iodc_total_time_length"; break;
50 case type_short: prop_name = "short_total_time_length"; break;
51 case type_long: prop_name = "long_total_time_length"; break;
52 case type_conveyance: prop_name = "conveyance_total_time_length"; break;
53 }
54
55 StorageProperty p = drive_->lookup_property(prop_name,
56 StorageProperty::section_data, StorageProperty::subsection_capabilities);
57
58 // p stores it as uint64_t
59 return (total_duration_ = (p.empty() ? 0 : static_cast<int64_t>(p.value_time_length)));
60 }
61
62
63
is_supported() const64 bool SelfTest::is_supported() const
65 {
66 if (!drive_)
67 return false;
68
69 if (type_ == type_ioffline) // disable this for now - it's unsupported.
70 return false;
71
72 std::string prop_name;
73 switch(type_) {
74 case type_ioffline: prop_name = "iodc_support"; break;
75 case type_short: prop_name = "selftest_support"; break;
76 case type_long: prop_name = "selftest_support"; break; // same for short and long
77 case type_conveyance: prop_name = "conveyance_support"; break;
78 }
79
80 StorageProperty p = drive_->lookup_property(prop_name, StorageProperty::section_internal);
81 return (!p.empty() && p.value_bool);
82 }
83
84
85
86
87 // start the test
start(hz::intrusive_ptr<CmdexSync> smartctl_ex)88 std::string SelfTest::start(hz::intrusive_ptr<CmdexSync> smartctl_ex)
89 {
90 this->clear(); // clear previous results
91
92 if (!drive_)
93 return "Invalid drive given.";
94 if (drive_->get_test_is_active())
95 return "A test is already running on this drive.";
96 if (!this->is_supported())
97 return get_test_name(type_) + " is unsupported by this drive.";
98
99 std::string test_param;
100 switch(type_) {
101 case type_ioffline: test_param = "offline"; break;
102 case type_short: test_param = "short"; break;
103 case type_long: test_param = "long"; break;
104 case type_conveyance: test_param = "conveyance"; break;
105 // no default - this way we get warned by compiler if we're not listing all of them.
106 }
107 if (test_param.empty())
108 return "Invalid test specified";
109
110 std::string output;
111 std::string error_msg = drive_->execute_device_smartctl("--test=" + test_param, smartctl_ex, output);
112
113 if (!error_msg.empty()) // checks for empty output too
114 return error_msg;
115
116 if (!app_pcre_match("/^Drive command .* successful\\.\\nTesting has begun\\.$/mi", output)) {
117 return "Sending command failed.";
118 }
119
120
121 // update our members
122 // error_msg = this->update(smartctl_ex);
123 // if (!error_msg.empty()) // update can error out too.
124 // return error_msg;
125
126 // Don't update here - the logs may not be updated this fast.
127 // Better to wait several seconds and then call it manually.
128
129 // Set up everything so that the caller won't have to.
130
131 status_ = StorageSelftestEntry::status_in_progress;
132
133 remaining_percent_ = 100;
134 // set to 90 to avoid the 100->90 timer reset. this way we won't be looking at
135 // "remaining 60sec" on 60sec test twice (5 seconds apart). Since the test starts
136 // at 90% anyway, it's a good thing.
137 last_seen_percent_ = 90;
138 poll_in_seconds_ = 5; // first update() in 5 seconds
139 timer_.start();
140
141 drive_->set_test_is_active(true);
142
143
144 return std::string(); // everything ok
145 }
146
147
148
149 // abort test.
force_stop(hz::intrusive_ptr<CmdexSync> smartctl_ex)150 std::string SelfTest::force_stop(hz::intrusive_ptr<CmdexSync> smartctl_ex)
151 {
152 if (!drive_)
153 return "Invalid drive given.";
154 if (!drive_->get_test_is_active())
155 return "No test is currently running on this drive.";
156
157 // To abort immediate offline test, the device MUST have
158 // "Abort Offline collection upon new command" capability,
159 // any command (e.g. "--abort") will abort it. If it has "Suspend Offline...",
160 // there's no way to abort such test.
161 if (type_ == type_ioffline) {
162 StorageProperty p = drive_->lookup_property("iodc_command_suspends", StorageProperty::section_internal);
163 if (!p.empty() && p.value_bool) { // if empty, give a chance to abort anyway.
164 return "Aborting this test is unsupported by the drive.";
165 }
166 // else, proceed as any other test
167 }
168
169 // To abort non-captive short, long and conveyance tests, use "--abort".
170 std::string output;
171 std::string error_msg = drive_->execute_device_smartctl("--abort", smartctl_ex, output);
172
173 if (!error_msg.empty()) // checks for empty output too
174 return error_msg;
175
176 // this command prints success even if no test was running.
177 if (!app_pcre_match("/^Self-testing aborted!$/mi", output)) {
178 return "Sending command failed.";
179 }
180
181 // update our members
182 error_msg = this->update(smartctl_ex);
183
184 // the thing is, update() may fail to actually update the statuses, so
185 // do it manually.
186 if (status_ == StorageSelftestEntry::status_in_progress) { // update() couldn't do its job
187 status_ = StorageSelftestEntry::status_aborted_by_host;
188 remaining_percent_ = -1;
189 last_seen_percent_ = -1;
190 poll_in_seconds_ = -1;
191 timer_.stop();
192 drive_->set_test_is_active(false);
193 }
194
195 if (!error_msg.empty()) // update can error out too.
196 return error_msg;
197 return std::string(); // everything ok
198 }
199
200
201
202 // update status variables. note: the returned error is an error in logic,
203 // not an hw defect error.
update(hz::intrusive_ptr<CmdexSync> smartctl_ex)204 std::string SelfTest::update(hz::intrusive_ptr<CmdexSync> smartctl_ex)
205 {
206 if (!drive_)
207 return "Invalid drive given.";
208
209 std::string output;
210 // std::string error_msg = drive_->execute_device_smartctl("--log=selftest", smartctl_ex, output);
211 std::string error_msg = drive_->execute_device_smartctl("--capabilities", smartctl_ex, output);
212
213 if (!error_msg.empty()) // checks for empty output too
214 return error_msg;
215
216 StorageAttribute::DiskType disk_type = drive_->get_is_hdd() ? StorageAttribute::DiskHDD : StorageAttribute::DiskSSD;
217 SmartctlParser ps;
218 if (!ps.parse_full(output, disk_type)) { // try to parse it
219 return ps.get_error_msg();
220 }
221
222 SmartctlParser::prop_list_t props = ps.get_properties();
223
224 // Note: Since the self-test log is sometimes late
225 // and in undetermined order (sorting by hours is too rough),
226 // we use the "self-test status" capability.
227 StorageProperty p;
228 for (SmartctlParser::prop_list_t::const_iterator iter = props.begin(); iter != props.end(); ++iter) {
229 // if (iter->section != StorageProperty::section_data || iter->subsection != StorageProperty::subsection_selftest_log
230 if (iter->section != StorageProperty::section_internal
231 || iter->value_type != StorageProperty::value_type_selftest_entry || iter->value_selftest_entry.test_num != 0
232 || iter->generic_name != "last_selftest_status")
233 continue;
234 p = *iter;
235 }
236
237 if (p.empty())
238 return "The drive doesn't report the test status.";
239
240 status_ = p.value_selftest_entry.status;
241 bool active = (status_ == StorageSelftestEntry::status_in_progress);
242
243
244 // Note that the test needs 90% to complete, not 100. It starts at 90%
245 // and reaches 00% on completion. That's 9 pieces.
246 if (active) {
247
248 remaining_percent_ = p.value_selftest_entry.remaining_percent;
249 if (remaining_percent_ != last_seen_percent_) {
250 last_seen_percent_ = remaining_percent_;
251 timer_.start(); // restart the timer
252 }
253
254 int64_t total = get_min_duration_seconds();
255
256 if (total <= 0) { // unknown
257 poll_in_seconds_ = 30; // just a guess
258
259 } else {
260 // seconds per 10%. use double, because e.g. 60sec test gives silly values with int.
261 double gran = (double(total) / 9.);
262
263 // Add 1/10 for disk load delays, etc... . Limit to 15sec, in case of very quick tests.
264 poll_in_seconds_ = std::max(int64_t(15), int64_t(gran / 3. + (gran / 10.)));
265
266 // for long tests we don't want to make the user wait too much, so
267 // we need to poll more frequently by the end, in case it's completed.
268 if (type_ == type_long && remaining_percent_ == 10)
269 poll_in_seconds_ = std::max(int64_t(1*60), int64_t(gran / 10.)); // that's 2 min for 180min extended test
270
271 debug_out_dump("app", DBG_FUNC_MSG << "total: " << total << ", gran: " << gran
272 << ", poll in: " << poll_in_seconds_ << ", remaining secs: " << get_remaining_seconds()
273 << ", remaining %: " << int(remaining_percent_) << ", last seen %: " << int(last_seen_percent_) << ".\n");
274 }
275
276 } else {
277 remaining_percent_ = -1;
278 last_seen_percent_ = -1;
279 poll_in_seconds_ = -1;
280 timer_.stop();
281 }
282
283 drive_->set_test_is_active(active);
284
285 return std::string(); // everything ok
286 }
287
288
289
290
291
292
293 /// @}
294