procfs: improve parsing of stat file
[apps/native/ttsd-worker-task.git] / src / procfs.c
1 /*
2  * Copyright (c) 2018 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Flora License, Version 1.1 (the License);
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://floralicense.org/license/
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an AS IS BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include <stdio.h>
18 #include <string.h>
19 #include <dirent.h>
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <ctype.h>
23
24 #include "procfs.h"
25 #include "log.h"
26 #include "err-check.h"
27
28 #define LOADAVG_FILEPATH "/proc/loadavg"
29 #define UPTIME_FILEPATH  "/proc/uptime"
30 #define POSSIBLE_CPUS_FILEPATH  "/sys/devices/system/cpu/possible"
31 #define MEMINFO_FILEPATH "/proc/meminfo"
32 #define STAT_FILEPATH "/proc/stat"
33 #define PIDSTAT_FILEPATH "/proc/%d/stat"
34 #define SMAPS_FILEPATH "/proc/%d/smaps"
35 #define PROC_DIR_PATH "/proc/"
36 #define PROC_PID_EXE_PATH "/proc/%d/exe"
37 #define PROC_PID_CMDLINE_PATH "/proc/%d/cmdline"
38
39 int procfs_read_loadavg(struct procfs_loadavg *info)
40 {
41         float a1, a5, a15;
42
43         ON_NULL_RETURN_VAL(info, -1);
44
45         FILE *loadavg_fp = fopen(LOADAVG_FILEPATH, "r");
46         if (!loadavg_fp) {
47                 ERR("failed to open " LOADAVG_FILEPATH);
48                 return -1;
49         }
50
51         if (fscanf(loadavg_fp, "%f %f %f", &a1, &a5, &a15) != 3) {
52                 ERR("failed to read " LOADAVG_FILEPATH);
53                 fclose(loadavg_fp);
54                 return -1;
55         }
56
57         info->one_min_avg = a1;
58         info->five_min_avg = a5;
59         info->fifteen_min_avg = a15;
60
61         fclose(loadavg_fp);
62
63         return 0;
64 }
65
66 int procfs_read_stat(struct procfs_stat *usage)
67 {
68         ON_NULL_RETURN_VAL(usage, -1);
69
70         char line[256];
71         struct procfs_stat tmp;
72         const char *prefix = "cpu ";
73
74         FILE *stat_fp = fopen(STAT_FILEPATH, "r");
75         if (!stat_fp) {
76                 ERR("failed to open " STAT_FILEPATH);
77                 return -1;
78         }
79
80         while (fgets(line, sizeof(line), stat_fp)) {
81                 if (!strncmp(line, prefix, strlen(prefix)) &&
82                     sscanf(line + 4, "%llu %llu %llu %llu %llu %llu %llu",
83                                 &tmp.user, &tmp.nice, &tmp.system, &tmp.idle,
84                                 &tmp.iowait, &tmp.irq, &tmp.softirq) == 7) {
85                         *usage = tmp;
86                         fclose(stat_fp);
87                         return 0;
88                 }
89         }
90
91         fclose(stat_fp);
92
93         return -1;
94 }
95
96 int procfs_read_meminfo(struct procfs_meminfo *usage)
97 {
98         ON_NULL_RETURN_VAL(usage, -1);
99
100         char line[128];
101         unsigned long value;
102         unsigned long mem_total = 0;
103         unsigned long mem_free = 0;
104         unsigned long cached = 0;
105         unsigned long mem_available = 0;
106         unsigned long swap_total = 0;
107         unsigned long swap_free = 0;
108
109         FILE *meminfo_fp = fopen(MEMINFO_FILEPATH, "r");
110         if (!meminfo_fp) {
111                 ERR("failed to open " MEMINFO_FILEPATH);
112                 return -1;
113         }
114
115         while (fgets(line, sizeof(line), meminfo_fp)) {
116                 if (sscanf(line, "MemTotal: %lu", &mem_total) == 1)
117                         usage->total = mem_total;
118                 else if (sscanf(line, "MemFree: %lu", &mem_free) == 1)
119                         usage->free = mem_free;
120                 else if (sscanf(line, "Cached: %lu", &cached) == 1)
121                         usage->cache = cached;
122                 else if (sscanf(line, "MemAvailable: %lu", &value) == 1)
123                         mem_available = value;
124                 else if (sscanf(line, "SwapTotal: %lu", &value) == 1)
125                         swap_total = value;
126                 else if (sscanf(line, "SwapFree: %lu", &value) == 1)
127                         swap_free = value;
128         }
129
130         // Calculate used memory
131         // MemAvailable is exposed since Linux 3.14, so handle also previous
132         // kernel versions
133         usage->used = 0;
134         if (mem_available > 0) {
135                 if (mem_total > mem_available)
136                         usage->used = mem_total - mem_available;
137         } else {
138                 if ((mem_total > mem_free) && ((mem_total - mem_free) > cached))
139                         usage->used = mem_total - mem_free - cached;
140         }
141
142         usage->swap = (swap_total > swap_free) ? (swap_total - swap_free) : 0;
143
144         fclose(meminfo_fp);
145
146         return 0;
147 }
148
149 int procfs_read_process_smaps(int pid, struct procfs_process_smaps *usage)
150 {
151         ON_NULL_RETURN_VAL(usage, -1);
152         ON_TRUE_RETURN_VAL(pid <= 0, -1);
153
154         char smapspath[64], line[256];
155         unsigned long value;
156
157         snprintf(smapspath, sizeof(smapspath), SMAPS_FILEPATH, pid);
158
159         FILE *smaps_fp = fopen(smapspath, "r");
160         if (!smaps_fp) {
161                  ERR("failed to open path: %s", smapspath);
162                  return -1;
163         }
164
165         memset(usage, 0x0, sizeof(struct procfs_process_smaps));
166
167         while (fgets(line, sizeof(line), smaps_fp)) {
168                 if (sscanf(line, "Size: %lu", &value) == 1)
169                         usage->vsz += value;
170                 else if (sscanf(line, "Rss: %lu", &value) == 1)
171                         usage->rss += value;
172                 else if (sscanf(line, "Pss: %lu", &value) == 1)
173                         usage->pss += value;
174                 else if (sscanf(line, "Shared_Clean: %lu", &value) == 1)
175                         usage->shared_clean += value;
176                 else if (sscanf(line, "Shared_Dirty: %lu", &value) == 1)
177                         usage->shared_dirty += value;
178                 else if (sscanf(line, "Private_Clean: %lu", &value) == 1)
179                         usage->private_clean += value;
180                 else if (sscanf(line, "Private_Dirty: %lu", &value) == 1)
181                         usage->private_dirty += value;
182         }
183
184         fclose(smaps_fp);
185
186         return 0;
187 }
188
189 static int read_file(const char *filename, char *buf, size_t buf_size)
190 {
191         FILE *fp = fopen(filename, "r");
192         if (!fp) return -1;
193
194         size_t read = fread(buf, 1, buf_size, fp);
195         if (read <= 0) {
196                 fclose(fp);
197                 return -1;
198         }
199         buf[read-1] = '\0';
200         fclose(fp);
201         return read;
202 }
203
204 int procfs_read_process_stat(int pid, struct procfs_process_stat *usage)
205 {
206         ON_NULL_RETURN_VAL(usage, -1);
207         ON_TRUE_RETURN_VAL(pid <= 0, -1);
208
209         char statpath[64];
210         char buf[1024];
211         unsigned long long utime, stime;
212         unsigned long rss;
213         int read;
214
215         snprintf(statpath, sizeof(statpath), PIDSTAT_FILEPATH, pid);
216
217         if ((read = read_file(statpath, buf, sizeof(buf))) <= 0) {
218                 return -1;
219         }
220
221         if (read == sizeof(buf)) {
222                 return -1;
223         }
224
225         char *name_end = strrchr(buf, ')');
226         if (!name_end) return -1;
227
228         name_end += 1;
229
230         if (sscanf(name_end,
231                                 "%*c "
232                                 "%*s "
233                                 "%*s "
234                                 "%*s "
235                                 "%*s "
236                                 "%*s "
237                                 "%*s "
238                                 "%*s "
239                                 "%*s "
240                                 "%*s "
241                                 "%*s "
242                                 "%*s "
243                                 "%llu "
244                                 "%llu "
245                                 "%*s "
246                                 "%*s "
247                                 "%*s "
248                                 "%*s "
249                                 "%*s "
250                                 "%*s "
251                                 "%*s "
252                                 "%*s "
253                                 "%ld"
254                                 , &utime, &stime, &rss) != 3) {
255                 ERR("reading pid stat file failed");
256                 return -1;
257         }
258
259         usage->utime = utime;
260         usage->stime = stime;
261         usage->rss = rss;
262
263         return 0;
264 }
265
266 int procfs_read_uptime(unsigned long *uptime)
267 {
268         ON_NULL_RETURN_VAL(uptime, -1);
269
270         double duptime;
271
272         FILE *uptime_fp = fopen(UPTIME_FILEPATH, "r");
273         if (!uptime_fp) {
274                 ERR("failed to open " UPTIME_FILEPATH);
275                 return -1;
276         }
277
278         if (fscanf(uptime_fp, "%lf ", &duptime) != 1) {
279                 ERR("failed to read " UPTIME_FILEPATH);
280                 fclose(uptime_fp);
281                 return -1;
282         }
283
284         fclose(uptime_fp);
285         *uptime = (unsigned long)duptime;
286
287         return 0;
288 }
289
290 int procfs_read_cpu_possible(int *cpu_count)
291 {
292         ON_NULL_RETURN_VAL(cpu_count, -1);
293
294         int cpus, dummy;
295
296         FILE *possible_fp = fopen(POSSIBLE_CPUS_FILEPATH, "r");
297         if (!possible_fp) {
298                 ERR("failed to open " POSSIBLE_CPUS_FILEPATH);
299                 return -1;
300         }
301
302         if (fscanf(possible_fp, "%d-%d", &dummy, &cpus) != 2) {
303                 ERR("failed to read " POSSIBLE_CPUS_FILEPATH);
304                 fclose(possible_fp);
305                 return -1;
306         }
307
308         *cpu_count = cpus + 1;
309
310         fclose(possible_fp);
311
312         return 0;
313 }
314
315 bool _procfs_dirname_parse_pid(const char *dirname, int *pid)
316 {
317         int parsed_pid;
318
319         if (!isdigit(*dirname))
320                 return false;
321
322         if (sscanf(dirname, "%d", &parsed_pid) != 1) {
323                 return false;
324         }
325         *pid = parsed_pid;
326         return true;
327 }
328
329 int procfs_iterate_pids(procfs_pid_iterator_cb iterator, void *user_data)
330 {
331         ON_NULL_RETURN_VAL(iterator, -1);
332
333         struct dirent *entry;
334         int pid;
335
336         DIR *dir = opendir(PROC_DIR_PATH);
337         if (!dir) {
338                 ERR("opendir failed.");
339                 return -1;
340         }
341
342         // According to POSIX docs readdir is not-thread safe.
343         // however in glib recent implementations readdir
344         // is thread safe, so we can avoid using locks here.
345         while ((entry = readdir(dir)) != NULL) {
346                 if (!_procfs_dirname_parse_pid(entry->d_name, &pid))
347                         continue;
348
349                 if (iterator(pid, user_data))
350                         continue;
351                 else
352                         break;;
353         }
354
355         closedir(dir);
356         return 0;
357 }
358
359 int procfs_read_process_exe(int pid, char **exe)
360 {
361         ON_TRUE_RETURN_VAL(pid < 0, -1);
362         ON_NULL_RETURN_VAL(exe, -1);
363
364         char buf[256];
365         size_t size = 32;
366         char *buffer = NULL;
367         int ret;
368
369         snprintf(buf, sizeof(buf), PROC_PID_EXE_PATH, pid);
370
371         do
372         {
373                 size *= 2;
374                 char *tmp = realloc(buffer, size);
375                 if (!tmp) {
376                         free(buffer);
377                         return -1;
378                 }
379                 buffer = tmp;
380                 ret = readlink(buf, buffer, size);
381                 if (ret == -1) {
382                         free(buffer);
383                         return -1;
384                 }
385                 buffer[ret] = '\0';
386         } while (ret >= size);
387
388         *exe = buffer;
389         return 0;
390 }
391
392 int procfs_read_process_cmdline(int pid, char **cmdline)
393 {
394         ON_TRUE_RETURN_VAL(pid < 0, -1);
395         ON_NULL_RETURN_VAL(cmdline, -1);
396
397         char buf[64];
398         int read, read_total = 0;
399         char *cmd = NULL;
400
401         snprintf(buf, sizeof(buf), PROC_PID_CMDLINE_PATH, pid);
402
403         FILE *cmdline_fp = fopen(buf, "r");
404         if (!cmdline_fp) {
405                 ERR("failed to open %s", buf);
406                 return -1;
407         }
408
409         while ((read = fread(buf, 1, sizeof(buf), cmdline_fp)) != 0) {
410                 char *tmp = realloc(cmd, read_total + read + 1);
411                 if (!tmp) {
412                         fclose(cmdline_fp);
413                         free(cmd);
414                         return -1;
415                 }
416                 cmd = tmp;
417                 memcpy(cmd + read_total, buf, read);
418                 read_total += read;
419         }
420
421         if (read_total == 0) {
422                 fclose(cmdline_fp);
423                 *cmdline = NULL;
424                 return 0;
425         }
426
427         // clear string from end-of-string characters
428         for (int i = 0; i < read_total; ++i) {
429                 if (cmd[i] == '\0')
430                         cmd[i] = ' ';
431         }
432         cmd[read_total] = '\0';
433
434         fclose(cmdline_fp);
435         *cmdline = cmd;
436         return 0;
437 }