1 /*
2  * Copyright (C) Tildeslash Ltd. All rights reserved.
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU Affero General Public License version 3.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU Affero General Public License
13  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
14  *
15  * In addition, as a special exception, the copyright holders give
16  * permission to link the code of portions of this program with the
17  * OpenSSL library under certain conditions as described in each
18  * individual source file, and distribute linked combinations
19  * including the two.
20  *
21  * You must obey the GNU Affero General Public License in all respects
22  * for all of the code used other than OpenSSL.
23  */
24 
25 /**
26  *  System dependent filesystem methods.
27  *
28  *  @file
29  */
30 
31 #include "config.h"
32 
33 #ifdef HAVE_STDIO_H
34 #include <stdio.h>
35 #endif
36 
37 #ifdef HAVE_ERRNO_H
38 #include <errno.h>
39 #endif
40 
41 #ifdef HAVE_STRING_H
42 #include <string.h>
43 #endif
44 
45 #ifdef HAVE_SYS_PARAM_H
46 #include <sys/param.h>
47 #endif
48 
49 #ifdef HAVE_CTYPE_H
50 #include <ctype.h>
51 #endif
52 
53 #ifdef HAVE_SYS_SYSCTL_H
54 #include <sys/sysctl.h>
55 #endif
56 
57 #ifdef HAVE_SYS_MOUNT_H
58 #include <sys/mount.h>
59 #endif
60 
61 #ifdef HAVE_SYS_TYPES_H
62 #include <sys/types.h>
63 #endif
64 
65 #ifdef HAVE_SYS_STATVFS_H
66 #include <sys/statvfs.h>
67 #endif
68 
69 #ifdef HAVE_SYS_IOSTAT_H
70 #include <sys/iostat.h>
71 #endif
72 
73 
74 #include "monit.h"
75 #include "device.h"
76 
77 // libmonit
78 #include "system/Time.h"
79 #include "io/File.h"
80 
81 
82 /* ------------------------------------------------------------- Definitions */
83 
84 
85 static struct {
86         unsigned long long timestamp;
87         size_t diskCount;
88         size_t diskLength;
89         struct io_sysctl *disk;
90 } _statistics = {};
91 
92 
93 /* ------------------------------------------------------- Static destructor */
94 
95 
_destructor()96 static void __attribute__ ((destructor)) _destructor() {
97         FREE(_statistics.disk);
98 }
99 
100 
101 /* ----------------------------------------------------------------- Private */
102 
103 
104 // Parse the device path like /dev/sd0a -> sd0
_parseDevice(const char * path,Device_T device)105 static bool _parseDevice(const char *path, Device_T device) {
106         const unsigned char *base = File_basename(path);
107         for (int len = strlen(base), i = len - 1; i >= 0; i--) {
108                 if (isdigit(*(base + i))) {
109                         unsigned index = i + 1;
110                         strncpy(device->key, base, index < sizeof(device->key) ? index : sizeof(device->key) - 1);
111                         return true;
112                 }
113         }
114         return false;
115 }
116 
117 
_getStatistics(unsigned long long now)118 static bool _getStatistics(unsigned long long now) {
119         // Refresh only if the statistics are older then 1 second (handle also backward time jumps)
120         if (now > _statistics.timestamp + 1000 || now < _statistics.timestamp - 1000) {
121                 size_t len = 0;
122                 int mib[3] = {CTL_HW, HW_IOSTATS, sizeof(struct io_sysctl)};
123                 if (sysctl(mib, 3, NULL, &len, NULL, 0) == -1) {
124                         Log_error("filesystem statistic error -- cannot get HW_IOSTATS size: %s\n", STRERROR);
125                         return false;
126                 }
127                 if (_statistics.diskLength != len) {
128                         _statistics.diskCount = len / mib[2];
129                         _statistics.diskLength = len;
130                         RESIZE(_statistics.disk, len);
131                 }
132                 if (sysctl(mib, 3, _statistics.disk, &(_statistics.diskLength), NULL, 0) == -1) {
133                         Log_error("filesystem statistic error -- cannot get HW_IOSTATS: %s\n", STRERROR);
134                         return false;
135                 }
136                 _statistics.timestamp = now;
137         }
138         return true;
139 }
140 
141 
_getDummyDiskActivity(void * _inf)142 static bool _getDummyDiskActivity(__attribute__ ((unused)) void *_inf) {
143         return true;
144 }
145 
146 
_getBlockDiskActivity(void * _inf)147 static bool _getBlockDiskActivity(void *_inf) {
148         Info_T inf = _inf;
149         unsigned long long now = Time_milli();
150         bool rv = _getStatistics(now);
151         if (rv) {
152                 for (unsigned i = 0; i < _statistics.diskCount; i++)     {
153                         if (Str_isEqual(inf->filesystem->object.key, _statistics.disk[i].name)) {
154                                 Statistics_update(&(inf->filesystem->read.bytes), now, _statistics.disk[i].rbytes);
155                                 Statistics_update(&(inf->filesystem->write.bytes), now, _statistics.disk[i].wbytes);
156                                 Statistics_update(&(inf->filesystem->read.operations),  now, _statistics.disk[i].rxfer);
157                                 Statistics_update(&(inf->filesystem->write.operations), now, _statistics.disk[i].wxfer);
158                                 Statistics_update(&(inf->filesystem->time.run), now, _statistics.disk[i].time_sec * 1000. + _statistics.disk[i].time_usec / 1000.);
159                                 break;
160                         }
161                 }
162         }
163         return rv;
164 }
165 
166 
_getDiskUsage(void * _inf)167 static bool _getDiskUsage(void *_inf) {
168         Info_T inf = _inf;
169         struct statvfs usage;
170         if (statvfs(inf->filesystem->object.mountpoint, &usage) != 0) {
171                 Log_error("Error getting usage statistics for filesystem '%s' -- %s\n", inf->filesystem->object.mountpoint, STRERROR);
172                 return false;
173         }
174         inf->filesystem->f_bsize = usage.f_frsize;
175         inf->filesystem->f_blocks = usage.f_blocks;
176         inf->filesystem->f_blocksfree = usage.f_bavail;
177         inf->filesystem->f_blocksfreetotal = usage.f_bfree;
178         inf->filesystem->f_files = usage.f_files;
179         inf->filesystem->f_filesfree = usage.f_ffree;
180         return true;
181 }
182 
183 
_compareMountpoint(const char * mountpoint,struct statvfs * mnt)184 static bool _compareMountpoint(const char *mountpoint, struct statvfs *mnt) {
185         return IS(mountpoint, mnt->f_mntonname);
186 }
187 
188 
_compareDevice(const char * device,struct statvfs * mnt)189 static bool _compareDevice(const char *device, struct statvfs *mnt) {
190         return IS(device, mnt->f_mntfromname);
191 }
192 
193 
_filesystemFlagsToString(Info_T inf,unsigned long long flags)194 static void _filesystemFlagsToString(Info_T inf, unsigned long long flags) {
195         struct mystable {
196                 unsigned long long flag;
197                 char *description;
198         } t[]= {
199 #ifdef MNT_DISCARD
200                 {MNT_DISCARD, "discard"},
201 #endif
202                 {MNT_RDONLY, "ro"},
203                 {MNT_SYNCHRONOUS, "synchronous"},
204                 {MNT_NOEXEC, "noexec"},
205                 {MNT_NOSUID, "nosuid"},
206                 {MNT_NODEV, "nodev"},
207                 {MNT_NODEVMTIME, "nodevmtime"},
208                 {MNT_EXTATTR, "extattr"},
209                 {MNT_IGNORE, "hidden"},
210                 {MNT_LOG, "log"},
211                 {MNT_RELATIME, "relatime"},
212                 {MNT_NOCOREDUMP, "nocoredump"},
213                 {MNT_ASYNC, "asynchronous"},
214                 {MNT_NOATIME, "noatime"},
215                 {MNT_EXRDONLY, "exported read only"},
216                 {MNT_EXPORTED, "exported"},
217                 {MNT_DEFEXPORTED, "exported to the world"},
218                 {MNT_EXPORTANON, "anon uid mapping"},
219                 {MNT_EXKERB, "exported with kerberos"},
220                 {MNT_EXPUBLIC, "public export"},
221                 {MNT_EXNORESPORT, "no reserved ports enforcement"},
222                 {MNT_LOCAL, "local"},
223                 {MNT_QUOTA, "quota"},
224                 {MNT_ROOTFS, "rootfs"},
225                 {MNT_SOFTDEP, "soft dependencies"},
226                 {MNT_SYMPERM, "symperm"},
227                 {MNT_UNION, "union"}
228         };
229         Util_swapFilesystemFlags(&(inf->filesystem->flags));
230         for (unsigned i = 0, count = 0; i < sizeof(t) / sizeof(t[0]); i++) {
231                 if (flags & t[i].flag) {
232                         snprintf(inf->filesystem->flags.current + strlen(inf->filesystem->flags.current), sizeof(inf->filesystem->flags.value[0]) - strlen(inf->filesystem->flags.current) - 1, "%s%s", count++ ? ", " : "", t[i].description);
233                 }
234         }
235 }
236 
237 
_setDevice(Info_T inf,const char * path,bool (* compare)(const char * path,struct statvfs * mnt))238 static bool _setDevice(Info_T inf, const char *path, bool (*compare)(const char *path, struct statvfs *mnt)) {
239         int countfs = getvfsstat(NULL, 0, MNT_NOWAIT);
240         if (countfs != -1) {
241                 struct statvfs *mnt = CALLOC(countfs, sizeof(struct statvfs));
242                 if ((countfs = getvfsstat(mnt, countfs * sizeof(struct statvfs), MNT_NOWAIT)) != -1) {
243                         for (int i = 0; i < countfs; i++) {
244                                 struct statvfs *mntItem = mnt + i;
245                                 if (compare(path, mntItem)) {
246                                         if (IS(mntItem->f_fstypename, "ffs")) {
247                                                 if (_parseDevice(mntItem->f_mntfromname, &(inf->filesystem->object))) {
248                                                         inf->filesystem->object.getDiskActivity = _getBlockDiskActivity;
249                                                 } else {
250                                                         inf->filesystem->object.getDiskActivity = _getDummyDiskActivity;
251                                                         DEBUG("I/O monitoring for filesystem '%s' skipped - unable to parse the device %s", path, mntItem->f_mntfromname);
252                                                 }
253                                         } else {
254                                                 //FIXME: NetBSD kernel has NFS statistics as well, but there is no clear mapping between the kernel label ("nfsX" style) and the NFS mount => we don't support NFS currently
255                                                 inf->filesystem->object.getDiskActivity = _getDummyDiskActivity;
256                                         }
257                                         inf->filesystem->object.flags = mntItem->f_flag & MNT_VISFLAGMASK;
258                                         _filesystemFlagsToString(inf, inf->filesystem->object.flags);
259                                         strncpy(inf->filesystem->object.device, mntItem->f_mntfromname, sizeof(inf->filesystem->object.device) - 1);
260                                         strncpy(inf->filesystem->object.mountpoint, mntItem->f_mntonname, sizeof(inf->filesystem->object.mountpoint) - 1);
261                                         strncpy(inf->filesystem->object.type, mntItem->f_fstypename, sizeof(inf->filesystem->object.type) - 1);
262                                         inf->filesystem->object.getDiskUsage = _getDiskUsage;
263                                         inf->filesystem->object.mounted = true;
264                                         FREE(mnt);
265                                         return true;
266                                 }
267                         }
268                 }
269                 FREE(mnt);
270         }
271         Log_error("Lookup for '%s' filesystem failed\n", path);
272 error:
273         inf->filesystem->object.mounted = false;
274         return false;
275 }
276 
277 
_getDevice(Info_T inf,const char * path,bool (* compare)(const char * path,struct statvfs * mnt))278 static bool _getDevice(Info_T inf, const char *path, bool (*compare)(const char *path, struct statvfs *mnt)) {
279         if (_setDevice(inf, path, compare)) {
280                 return (inf->filesystem->object.getDiskUsage(inf) && inf->filesystem->object.getDiskActivity(inf));
281         }
282         return false;
283 }
284 
285 
286 /* ------------------------------------------------------------------ Public */
287 
288 
Filesystem_getByMountpoint(Info_T inf,const char * path)289 bool Filesystem_getByMountpoint(Info_T inf, const char *path) {
290         ASSERT(inf);
291         ASSERT(path);
292         return _getDevice(inf, path, _compareMountpoint);
293 }
294 
295 
Filesystem_getByDevice(Info_T inf,const char * path)296 bool Filesystem_getByDevice(Info_T inf, const char *path) {
297         ASSERT(inf);
298         ASSERT(path);
299         return _getDevice(inf, path, _compareDevice);
300 }
301 
302