]> git.ipfire.org Git - collecty.git/blob - src/_collecty/blockdev.c
disk: Catch error for SMART devices w/o tmp sensors
[collecty.git] / src / _collecty / blockdev.c
1 /*
2 * collecty
3 * Copyright (C) 2015 IPFire Team (www.ipfire.org)
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include <Python.h>
20
21 #include <atasmart.h>
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <mntent.h>
25 #include <stdbool.h>
26 #include <string.h>
27 #include <sys/ioctl.h>
28
29 #include "_collectymodule.h"
30
31 static PyGetSetDef BlockDevice_getsetters[] = {
32 {"path", (getter)BlockDevice_get_path, NULL, NULL, NULL},
33 {"model", (getter)BlockDevice_get_model, NULL, NULL, NULL},
34 {"serial", (getter)BlockDevice_get_serial, NULL, NULL, NULL},
35 };
36
37 static PyMethodDef BlockDevice_methods[] = {
38 {"get_bad_sectors", (PyCFunction)BlockDevice_get_bad_sectors, METH_NOARGS, NULL},
39 {"get_temperature", (PyCFunction)BlockDevice_get_temperature, METH_NOARGS, NULL},
40 {"is_smart_supported", (PyCFunction)BlockDevice_is_smart_supported, METH_NOARGS, NULL},
41 {"is_awake", (PyCFunction)BlockDevice_is_awake, METH_NOARGS, NULL},
42 {NULL}
43 };
44
45 PyTypeObject BlockDeviceType = {
46 PyVarObject_HEAD_INIT(NULL, 0)
47 "_collecty.BlockDevice", /*tp_name*/
48 sizeof(BlockDevice), /*tp_basicsize*/
49 0, /*tp_itemsize*/
50 (destructor)BlockDevice_dealloc, /*tp_dealloc*/
51 0, /*tp_print*/
52 0, /*tp_getattr*/
53 0, /*tp_setattr*/
54 0, /*tp_compare*/
55 0, /*tp_repr*/
56 0, /*tp_as_number*/
57 0, /*tp_as_sequence*/
58 0, /*tp_as_mapping*/
59 0, /*tp_hash */
60 0, /*tp_call*/
61 0, /*tp_str*/
62 0, /*tp_getattro*/
63 0, /*tp_setattro*/
64 0, /*tp_as_buffer*/
65 Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/
66 "BlockDevice objects", /* tp_doc */
67 0, /* tp_traverse */
68 0, /* tp_clear */
69 0, /* tp_richcompare */
70 0, /* tp_weaklistoffset */
71 0, /* tp_iter */
72 0, /* tp_iternext */
73 BlockDevice_methods, /* tp_methods */
74 0, /* tp_members */
75 BlockDevice_getsetters, /* tp_getset */
76 0, /* tp_base */
77 0, /* tp_dict */
78 0, /* tp_descr_get */
79 0, /* tp_descr_set */
80 0, /* tp_dictoffset */
81 (initproc)BlockDevice_init, /* tp_init */
82 0, /* tp_alloc */
83 BlockDevice_new, /* tp_new */
84 };
85
86 void BlockDevice_dealloc(BlockDevice* self) {
87 if (self->disk)
88 sk_disk_free(self->disk);
89
90 if (self->path)
91 free(self->path);
92
93 Py_TYPE(self)->tp_free((PyObject*)self);
94 }
95
96 int BlockDevice_get_identity(BlockDevice* device) {
97 int fd;
98
99 if ((fd = open(device->path, O_RDONLY | O_NONBLOCK)) < 0) {
100 return 1;
101 }
102
103 int r = ioctl(fd, HDIO_GET_IDENTITY, &device->identity);
104 close(fd);
105
106 if (r)
107 return 1;
108
109 return 0;
110 }
111
112 int BlockDevice_smart_is_available(BlockDevice* device) {
113 SkBool available = FALSE;
114
115 int r = sk_disk_smart_is_available(device->disk, &available);
116 if (r)
117 return -1;
118
119 if (available)
120 return 0;
121
122 return 1;
123 }
124
125 int BlockDevice_check_sleep_mode(BlockDevice* device) {
126 SkBool awake = FALSE;
127
128 int r = sk_disk_check_sleep_mode(device->disk, &awake);
129 if (r)
130 return -1;
131
132 if (awake)
133 return 0;
134
135 return 1;
136 }
137
138 PyObject * BlockDevice_new(PyTypeObject* type, PyObject* args, PyObject* kwds) {
139 BlockDevice* self = (BlockDevice*)type->tp_alloc(type, 0);
140
141 if (self) {
142 self->path = NULL;
143
144 // libatasmart
145 self->disk = NULL;
146 }
147
148 return (PyObject *)self;
149 }
150
151 int BlockDevice_init(BlockDevice* self, PyObject* args, PyObject* kwds) {
152 const char* path = NULL;
153
154 if (!PyArg_ParseTuple(args, "s", &path))
155 return -1;
156
157 self->path = strdup(path);
158
159 int r = BlockDevice_get_identity(self);
160 if (r) {
161 PyErr_Format(PyExc_OSError, "Could not open block device: %s", path);
162 return -1;
163 }
164
165 r = sk_disk_open(path, &self->disk);
166 if (r == 0) {
167 if (BlockDevice_smart_is_available(self) == 0) {
168 if (BlockDevice_check_sleep_mode(self) == 0) {
169 r = sk_disk_smart_read_data(self->disk);
170 if (r) {
171 PyErr_Format(PyExc_OSError, "Could not open block device %s: %s", path,
172 strerror(errno));
173 return -1;
174 }
175 }
176 }
177 } else {
178 PyErr_Format(PyExc_OSError, "Could not open block device %s: %s", path,
179 strerror(errno));
180 return -1;
181 }
182
183 //sk_disk_identify_is_available
184
185 return 0;
186 }
187
188 PyObject* BlockDevice_get_path(PyObject* self) {
189 BlockDevice* device = (BlockDevice*)self;
190
191 return PyUnicode_FromString(device->path);
192 }
193
194 static void clean_string(char *s) {
195 for (char* e = s; *e; e++) {
196 if (*e < ' ' || *e >= 127)
197 *e = ' ';
198 }
199 }
200
201 static void drop_spaces(char *s) {
202 char *d = s;
203 bool prev_space = false;
204
205 s += strspn(s, " ");
206
207 for (; *s; s++) {
208 if (prev_space) {
209 if (*s != ' ') {
210 prev_space = false;
211 *(d++) = ' ';
212 *(d++) = *s;
213 }
214 } else {
215 if (*s == ' ')
216 prev_space = true;
217 else
218 *(d++) = *s;
219 }
220 }
221
222 *d = 0;
223 }
224
225 static void copy_string(char* d, const char* s, size_t n) {
226 // Copy the source buffer to the destination buffer up to n
227 memcpy(d, s, n);
228
229 // Terminate the destination buffer with NULL
230 d[n] = '\0';
231
232 // Clean up the string from non-printable characters
233 clean_string(d);
234 drop_spaces(d);
235 }
236
237 PyObject* BlockDevice_get_model(PyObject* self) {
238 BlockDevice* device = (BlockDevice*)self;
239
240 char model[MODEL_SIZE + 1];
241 copy_string(model, device->identity.model, sizeof(model));
242
243 return PyUnicode_FromString(model);
244 }
245
246 PyObject* BlockDevice_get_serial(PyObject* self) {
247 BlockDevice* device = (BlockDevice*)self;
248
249 char serial[SERIAL_SIZE + 1];
250 copy_string(serial, device->identity.serial_no, sizeof(serial));
251
252 return PyUnicode_FromString(serial);
253 }
254
255 PyObject* BlockDevice_is_smart_supported(PyObject* self) {
256 BlockDevice* device = (BlockDevice*)self;
257
258 if (BlockDevice_smart_is_available(device) == 0)
259 Py_RETURN_TRUE;
260
261 Py_RETURN_FALSE;
262 }
263
264 PyObject* BlockDevice_is_awake(PyObject* self) {
265 BlockDevice* device = (BlockDevice*)self;
266
267 if (BlockDevice_check_sleep_mode(device) == 0)
268 Py_RETURN_TRUE;
269
270 Py_RETURN_FALSE;
271 }
272
273 PyObject* BlockDevice_get_bad_sectors(PyObject* self) {
274 BlockDevice* device = (BlockDevice*)self;
275
276 if (BlockDevice_smart_is_available(device)) {
277 PyErr_Format(PyExc_OSError, "Device does not support SMART");
278 return NULL;
279 }
280
281 uint64_t bad_sectors;
282 int r = sk_disk_smart_get_bad(device->disk, &bad_sectors);
283 if (r)
284 return NULL;
285
286 return PyLong_FromUnsignedLongLong((unsigned long long)bad_sectors);
287 }
288
289 PyObject* BlockDevice_get_temperature(PyObject* self) {
290 BlockDevice* device = (BlockDevice*)self;
291
292 if (BlockDevice_smart_is_available(device)) {
293 PyErr_Format(PyExc_OSError, "Device does not support SMART");
294 return NULL;
295 }
296
297 uint64_t mkelvin;
298 int r = sk_disk_smart_get_temperature(device->disk, &mkelvin);
299 if (r) {
300 // Temperature not available but SMART is supported
301 if (errno == ENOENT) {
302 PyErr_Format(PyExc_OSError, "Device does not have a temperature");
303 }
304
305 return NULL;
306 }
307
308 // Convert the temperature to Kelvin
309 return PyFloat_FromDouble((double)mkelvin / 1000.0);
310 }