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