Line data Source code
1 : /*
2 : * Copyright (C) 2014-2024 Red Hat, Inc.
3 : *
4 : * This library is free software; you can redistribute it and/or
5 : * modify it under the terms of the GNU Lesser General Public
6 : * License as published by the Free Software Foundation; either
7 : * version 2.1 of the License, or (at your option) any later version.
8 : *
9 : * This library is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 : * Lesser General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU Lesser General Public
15 : * License along with this library; if not, see <http://www.gnu.org/licenses/>.
16 : *
17 : * Author: Tomas Bzatek <tbzatek@redhat.com>
18 : */
19 :
20 : #include <glib.h>
21 : #include <string.h>
22 : #include <stdio.h>
23 : #include <unistd.h>
24 : #include <errno.h>
25 :
26 : #include <blockdev/utils.h>
27 : #include <check_deps.h>
28 :
29 : #include "smart.h"
30 : #include "smart-private.h"
31 :
32 : /**
33 : * SECTION: smart
34 : * @short_description: S.M.A.R.T. device reporting and management.
35 : * @title: SMART
36 : * @include: smart.h
37 : *
38 : * Plugin for ATA and SCSI/SAS S.M.A.R.T. device reporting and management. For NVMe
39 : * health reporting please use the native [`nvme`][libblockdev-NVMe] plugin.
40 : *
41 : * This libblockdev plugin strives to provide good enough abstraction on top of vastly
42 : * different backend implementations. Two plugin implementations are available:
43 : * `libatasmart` (default) and `smartmontools` (experimental).
44 : *
45 : * Not all plugin implementations provide full functionality and it is advised
46 : * to use standard libblockdev tech query functions for feature availability testing.
47 : * For example, the `libatasmart` plugin only provides ATA functionality and error
48 : * is returned when any SCSI function is called.
49 : *
50 : * ## libatasmart plugin #
51 : *
52 : * An implementation proven for over a decade, being essentially a port of UDisks code.
53 : * The `libatasmart` library is reasonably lightweight with minimal dependencies,
54 : * light on I/O and consuming C API directly with clearly defined data types.
55 : * However essentially no quirks or any drive database is present in the library
56 : * (apart from a couple of very old laptop drives).
57 : *
58 : * ## smartmontools plugin #
59 : *
60 : * In contrast to libatasmart, the smartmontools project is a feature-rich
61 : * implementation supporting specialties like vendor-specific data blocks. It is
62 : * a considerably heavier implementation I/O-wise due to device type detection and
63 : * retrieval of more data blocks from the drive.
64 : *
65 : * There's no C API at the moment and the plugin resorts to executing the `smartctl`
66 : * command and parsing its JSON output, that is by nature loosely-defined. This
67 : * presents challenges in data type conversions, interpretation of printed values
68 : * and volatile JSON key presence. Besides, executing external commands always brings
69 : * certain performance overhead and caution is advised when retrieving SMART data
70 : * from multiple drives in parallel.
71 : *
72 : * ## Attribute naming and value interpretation #
73 : *
74 : * Check #BDSmartATAAttribute for the struct members overview first. The plugin
75 : * public API provides both the implementation-specific attribute names/values
76 : * as well as unified ('well-known', translated) interpretation that is preferred
77 : * for general use.
78 : *
79 : * The `well_known_name` property follows the libatasmart-style naming -
80 : * e.g. `'power-on-hours'`. Unknown or untrusted attributes are either provided
81 : * in the form of `'attribute-123'` or by %NULL.
82 : *
83 : * Similarly, value of an attribute is provided in variety of interpretations,
84 : * subject to availability:
85 : * - the `value`, `worst` and `threshold` are normalized values in typical S.M.A.R.T. fashion
86 : * - the `value_raw` as a 64-bit untranslated value with no further context
87 : * of which bits are actually valid for a particular attribute
88 : * - the `pretty_value_string` as an implementation-specific string representation,
89 : * intended for end-user display
90 : * - the `pretty_value` and `pretty_value_unit` as a libatasmart-style of unified value/type pair
91 : *
92 : * Both libblockdev plugins strive for best effort of providing accurate values,
93 : * however there are often challenges ranging from string-to-number conversion,
94 : * multiple values being unpacked from a single raw number or not having enough
95 : * context provided by the underlying library for a trusted value interpretation.
96 : *
97 : * ## Attribute validation #
98 : *
99 : * It may seem obvious to use numerical attribute ID as an authoritative attribute
100 : * identifier, however in reality drive vendors tend not to stick with public
101 : * specifications. Attributes are often reused for vendor-specific values and this
102 : * differs from model to model even for a single vendor. This is more often the case
103 : * with SSD drives than traditional HDDs.
104 : *
105 : * Historically it brought confusion and false alarms on user's end and eventually
106 : * led to some form of quirk database in most projects. Maintaining such database
107 : * is a lifetime task and the only successful effort is the smartmontools' `drivedb.h`
108 : * collection. Quirks are needed for about everything - meaning of a particular
109 : * attribute (i.e. a 'well-known' name), interpretation of a raw value, all this
110 : * filtered by drive model string and firmware revision.
111 : *
112 : * However even there not everything is consistent and slight variations in
113 : * a 'well-known' name can be found. Besides, the attribute naming syntax differs
114 : * from our chosen libatasmart-style form.
115 : *
116 : * For this reason an internal libblockdev translation table has been introduced
117 : * to ensure a bit of consistency. The translation table is kept conservative,
118 : * is by no means complete and may get extended in future libblockdev releases.
119 : * As a result, some attributes may be reported as 'untrusted' or 'unknown'.
120 : *
121 : * The translation table at this point doesn't handle 'moves' where a different
122 : * attribute ID has been assigned for otherwise well defined attribute.
123 : *
124 : * An experimental `drivedb.h` parser is provided for the libatasmart plugin
125 : * as an additional tier of validation based on actual drive model + firmware match.
126 : * Being a C header file, the `drivedb.h` definitions are compiled in the plugin.
127 : * There's no support for loading external `drivedb.h` file though. This however
128 : * only serves for validation. Providing backwards mapping to libatasmart-style
129 : * of attributes is considered as a TODO.
130 : *
131 : * ## Device type detection, multipath #
132 : *
133 : * There's a big difference in how a drive is accessed. While `libatasmart` performs
134 : * only very basic device type detection based on parent subsystem as retrieved from
135 : * the udev database, `smartctl` implements logic to determine which protocol to use,
136 : * supporting variety of passthrough mechanisms and interface bridges. Such detection
137 : * is not always reliable though, having known issues with `dm-multipath` for example.
138 : *
139 : * For this case most plugin functions consume the `extra` argument allowing
140 : * callers to specify arguments such as `--device=` for device type override. This
141 : * is only supported by the smartmontools plugin and ignored by the libatasmart
142 : * plugin.
143 : *
144 : * As a well kept secret libatasmart has historically supported device type override
145 : * via the `ID_ATA_SMART_ACCESS` udev property. There's no public C API for this and
146 : * libblockdev generally tends to avoid any udev interaction, leaving the burden
147 : * to callers.
148 : *
149 : * Valid values for this property include:
150 : * - `'sat16'`: 16 Byte SCSI ATA SAT Passthru
151 : * - `'sat12'`: 12 Byte SCSI ATA SAT Passthru
152 : * - `'linux-ide'`: Native Linux IDE
153 : * - `'sunplus'`: SunPlus USB/ATA bridges
154 : * - `'jmicron'`: JMicron USB/ATA bridges
155 : * - `'none'`: No access method, avoid any I/O and ignore the device
156 : * - `'auto'`: Autodetection based on parent subsystem (default)
157 : *
158 : * A common example to override QEMU ATA device type, which often requires legacy
159 : * IDE protocol:
160 : * |[
161 : * KERNEL=="sd*", ENV{ID_VENDOR}=="ATA", ENV{ID_MODEL}=="QEMU_HARDDISK", ENV{ID_ATA}=="1", ENV{ID_ATA_SMART_ACCESS}="linux-ide"
162 : * ]|
163 : *
164 : * An example of blacklisting a USB device, in case of errors caused by reading SMART data:
165 : * |[
166 : * ATTRS{idVendor}=="152d", ATTRS{idProduct}=="2329", ENV{ID_ATA_SMART_ACCESS}="none"
167 : * ]|
168 : */
169 :
170 : /**
171 : * bd_smart_error_quark: (skip)
172 : */
173 0 : GQuark bd_smart_error_quark (void)
174 : {
175 0 : return g_quark_from_static_string ("g-bd-smart-error-quark");
176 : }
177 :
178 : /**
179 : * bd_smart_init:
180 : *
181 : * Initializes the plugin. **This function is called automatically by the
182 : * library's initialization functions.**
183 : *
184 : */
185 3 : gboolean bd_smart_init (void) {
186 : /* nothing to do here */
187 3 : return TRUE;
188 : };
189 :
190 : /**
191 : * bd_smart_close:
192 : *
193 : * Cleans up after the plugin. **This function is called automatically by the
194 : * library's functions that unload it.**
195 : *
196 : */
197 3 : void bd_smart_close (void) {
198 : /* nothing to do here */
199 3 : }
200 :
201 : /**
202 : * bd_smart_ata_attribute_free: (skip)
203 : * @attr: (nullable): %BDSmartATAAttribute to free
204 : *
205 : * Frees @attr.
206 : */
207 0 : void bd_smart_ata_attribute_free (BDSmartATAAttribute *attr) {
208 0 : if (attr == NULL)
209 0 : return;
210 0 : g_free (attr->name);
211 0 : g_free (attr->well_known_name);
212 0 : g_free (attr->pretty_value_string);
213 0 : g_free (attr);
214 : }
215 :
216 : /**
217 : * bd_smart_ata_attribute_copy: (skip)
218 : * @attr: (nullable): %BDSmartATAAttribute to copy
219 : *
220 : * Creates a new copy of @attr.
221 : */
222 0 : BDSmartATAAttribute * bd_smart_ata_attribute_copy (BDSmartATAAttribute *attr) {
223 : BDSmartATAAttribute *new_attr;
224 :
225 0 : if (attr == NULL)
226 0 : return NULL;
227 :
228 0 : new_attr = g_new0 (BDSmartATAAttribute, 1);
229 0 : memcpy (new_attr, attr, sizeof (BDSmartATAAttribute));
230 0 : new_attr->name = g_strdup (attr->name);
231 0 : new_attr->well_known_name = g_strdup (attr->well_known_name);
232 0 : new_attr->pretty_value_string = g_strdup (attr->pretty_value_string);
233 :
234 0 : return new_attr;
235 : }
236 :
237 : /**
238 : * bd_smart_ata_free: (skip)
239 : * @data: (nullable): %BDSmartATA to free
240 : *
241 : * Frees @data.
242 : */
243 0 : void bd_smart_ata_free (BDSmartATA *data) {
244 : BDSmartATAAttribute **attr;
245 :
246 0 : if (data == NULL)
247 0 : return;
248 :
249 0 : for (attr = data->attributes; attr && *attr; attr++)
250 0 : bd_smart_ata_attribute_free (*attr);
251 0 : g_free (data->attributes);
252 0 : g_free (data);
253 : }
254 :
255 : /**
256 : * bd_smart_ata_copy: (skip)
257 : * @data: (nullable): %BDSmartATA to copy
258 : *
259 : * Creates a new copy of @data.
260 : */
261 0 : BDSmartATA * bd_smart_ata_copy (BDSmartATA *data) {
262 : BDSmartATA *new_data;
263 : BDSmartATAAttribute **attr;
264 : GPtrArray *ptr_array;
265 :
266 0 : if (data == NULL)
267 0 : return NULL;
268 :
269 0 : new_data = g_new0 (BDSmartATA, 1);
270 0 : memcpy (new_data, data, sizeof (BDSmartATA));
271 :
272 0 : ptr_array = g_ptr_array_new ();
273 0 : for (attr = data->attributes; attr && *attr; attr++)
274 0 : g_ptr_array_add (ptr_array, bd_smart_ata_attribute_copy (*attr));
275 0 : g_ptr_array_add (ptr_array, NULL);
276 0 : new_data->attributes = (BDSmartATAAttribute **) g_ptr_array_free (ptr_array, FALSE);
277 :
278 0 : return new_data;
279 : }
280 :
281 : /**
282 : * bd_smart_scsi_free: (skip)
283 : * @data: (nullable): %BDSmartSCSI to free
284 : *
285 : * Frees @data.
286 : */
287 0 : void bd_smart_scsi_free (BDSmartSCSI *data) {
288 0 : if (data == NULL)
289 0 : return;
290 :
291 0 : g_free (data->scsi_ie_string);
292 0 : g_free (data);
293 : }
294 :
295 : /**
296 : * bd_smart_scsi_copy: (skip)
297 : * @data: (nullable): %BDSmartSCSI to copy
298 : *
299 : * Creates a new copy of @data.
300 : */
301 0 : BDSmartSCSI * bd_smart_scsi_copy (BDSmartSCSI *data) {
302 : BDSmartSCSI *new_data;
303 :
304 0 : if (data == NULL)
305 0 : return NULL;
306 :
307 0 : new_data = g_new0 (BDSmartSCSI, 1);
308 0 : memcpy (new_data, data, sizeof (BDSmartSCSI));
309 0 : new_data->scsi_ie_string = g_strdup (data->scsi_ie_string);
310 :
311 0 : return new_data;
312 : }
|