1# -*- coding: utf-8 -*-
2#
3# This file is part of Glances.
4#
5# Copyright (C) 2019 Nicolargo <nicolas@nicolargo.com>
6#
7# Glances is free software; you can redistribute it and/or modify
8# it under the terms of the GNU Lesser General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# Glances is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Lesser General Public License for more details.
16#
17# You should have received a copy of the GNU Lesser General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20"""HDD temperature plugin."""
21
22import os
23import socket
24
25from glances.compat import nativestr, range
26from glances.logger import logger
27from glances.plugins.glances_plugin import GlancesPlugin
28
29
30class Plugin(GlancesPlugin):
31    """Glances HDD temperature sensors plugin.
32
33    stats is a list
34    """
35
36    def __init__(self, args=None, config=None):
37        """Init the plugin."""
38        super(Plugin, self).__init__(args=args,
39                                     config=config,
40                                     stats_init_value=[])
41
42        # Init the sensor class
43        hddtemp_host = self.get_conf_value("host",
44                                           default=["127.0.0.1"])[0]
45        hddtemp_port = int(self.get_conf_value("port",
46                                               default="7634"))
47        self.hddtemp = GlancesGrabHDDTemp(args=args,
48                                          host=hddtemp_host,
49                                          port=hddtemp_port)
50
51        # We do not want to display the stat in a dedicated area
52        # The HDD temp is displayed within the sensors plugin
53        self.display_curse = False
54
55    @GlancesPlugin._check_decorator
56    @GlancesPlugin._log_result_decorator
57    def update(self):
58        """Update HDD stats using the input method."""
59        # Init new stats
60        stats = self.get_init_value()
61
62        if self.input_method == 'local':
63            # Update stats using the standard system lib
64            stats = self.hddtemp.get()
65
66        else:
67            # Update stats using SNMP
68            # Not available for the moment
69            pass
70
71        # Update the stats
72        self.stats = stats
73
74        return self.stats
75
76
77class GlancesGrabHDDTemp(object):
78    """Get hddtemp stats using a socket connection."""
79
80    def __init__(self, host='127.0.0.1', port=7634, args=None):
81        """Init hddtemp stats."""
82        self.args = args
83        self.host = host
84        self.port = port
85        self.cache = ""
86        self.reset()
87
88    def reset(self):
89        """Reset/init the stats."""
90        self.hddtemp_list = []
91
92    def __update__(self):
93        """Update the stats."""
94        # Reset the list
95        self.reset()
96
97        # Fetch the data
98        # data = ("|/dev/sda|WDC WD2500JS-75MHB0|44|C|"
99        #         "|/dev/sdb|WDC WD2500JS-75MHB0|35|C|"
100        #         "|/dev/sdc|WDC WD3200AAKS-75B3A0|45|C|"
101        #         "|/dev/sdd|WDC WD3200AAKS-75B3A0|45|C|"
102        #         "|/dev/sde|WDC WD3200AAKS-75B3A0|43|C|"
103        #         "|/dev/sdf|???|ERR|*|"
104        #         "|/dev/sdg|HGST HTS541010A9E680|SLP|*|"
105        #         "|/dev/sdh|HGST HTS541010A9E680|UNK|*|")
106        data = self.fetch()
107
108        # Exit if no data
109        if data == "":
110            return
111
112        # Safety check to avoid malformed data
113        # Considering the size of "|/dev/sda||0||" as the minimum
114        if len(data) < 14:
115            data = self.cache if len(self.cache) > 0 else self.fetch()
116        self.cache = data
117
118        try:
119            fields = data.split(b'|')
120        except TypeError:
121            fields = ""
122        devices = (len(fields) - 1) // 5
123        for item in range(devices):
124            offset = item * 5
125            hddtemp_current = {}
126            device = os.path.basename(nativestr(fields[offset + 1]))
127            temperature = fields[offset + 3]
128            unit = nativestr(fields[offset + 4])
129            hddtemp_current['label'] = device
130            try:
131                hddtemp_current['value'] = float(temperature)
132            except ValueError:
133                # Temperature could be 'ERR', 'SLP' or 'UNK' (see issue #824)
134                # Improper bytes/unicode in glances_hddtemp.py (see issue #887)
135                hddtemp_current['value'] = nativestr(temperature)
136            hddtemp_current['unit'] = unit
137            self.hddtemp_list.append(hddtemp_current)
138
139    def fetch(self):
140        """Fetch the data from hddtemp daemon."""
141        # Taking care of sudden deaths/stops of hddtemp daemon
142        try:
143            sck = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
144            sck.connect((self.host, self.port))
145            data = b''
146            while True:
147                received = sck.recv(4096)
148                if not received:
149                    break
150                data += received
151        except Exception as e:
152            logger.debug("Cannot connect to an HDDtemp server ({}:{} => {})".format(self.host, self.port, e))
153            logger.debug("Disable the HDDtemp module. Use the --disable-hddtemp to hide the previous message.")
154            if self.args is not None:
155                self.args.disable_hddtemp = True
156            data = ""
157        finally:
158            sck.close()
159            if data != "":
160                logger.debug("Received data from the HDDtemp server: {}".format(data))
161
162        return data
163
164    def get(self):
165        """Get HDDs list."""
166        self.__update__()
167        return self.hddtemp_list
168