1a81709ffb502df81d77e66652e945bf6827be74
[platform/core/system/tlm.git] / src / common / tlm-utils.c
1 /* vi: set et sw=4 ts=4 cino=t0,(0: */
2 /* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 /*
4  * This file is part of tlm (Tiny Login Manager)
5  *
6  * Copyright (C) 2013 Intel Corporation.
7  *
8  * Contact: Amarnath Valluri <amarnath.valluri@linux.intel.com>
9  *          Jussi Laako <jussi.laako@linux.intel.com>
10  *
11  * This library is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU Lesser General Public
13  * License as published by the Free Software Foundation; either
14  * version 2.1 of the License, or (at your option) any later version.
15  *
16  * This library is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19  * Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public
22  * License along with this library; if not, write to the Free Software
23  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
24  * 02110-1301 USA
25  */
26
27 #include <sys/types.h>
28 #include <pwd.h>
29 #include <sys/stat.h>
30 #include <glib/gstdio.h>
31 #include <utmp.h>
32 #include <paths.h>
33 #include <ctype.h>
34 #include <sys/types.h>
35 #include <sys/socket.h>
36 #include <sys/inotify.h>
37 #include <netdb.h>
38 #include <string.h>
39 #include <unistd.h>
40 #include <glib/gstdio.h>
41 #include <glib-unix.h>
42 #include <errno.h>
43
44 #include "tlm-utils.h"
45 #include "tlm-log.h"
46
47 #define HOST_NAME_SIZE 256
48
49 void
50 g_clear_string (gchar **str)
51 {
52     if (str && *str) {
53         g_free (*str);
54         *str = NULL;
55     }
56 }
57
58 const gchar *
59 tlm_user_get_name (uid_t user_id)
60 {
61     struct passwd *pwent;
62
63     pwent = getpwuid (user_id);
64     if (!pwent)
65         return NULL;
66
67     return pwent->pw_name;
68 }
69
70 uid_t
71 tlm_user_get_uid (const gchar *username)
72 {
73     struct passwd *pwent;
74
75     pwent = getpwnam (username);
76     if (!pwent)
77         return -1;
78
79     return pwent->pw_uid;
80 }
81
82 gid_t
83 tlm_user_get_gid (const gchar *username)
84 {
85     struct passwd *pwent;
86
87     pwent = getpwnam (username);
88     if (!pwent)
89         return -1;
90
91     return pwent->pw_gid;
92 }
93
94 const gchar *
95 tlm_user_get_home_dir (const gchar *username)
96 {
97     struct passwd *pwent;
98
99     pwent = getpwnam (username);
100     if (!pwent)
101         return NULL;
102
103     return pwent->pw_dir;
104 }
105
106 const gchar *
107 tlm_user_get_shell (const gchar *username)
108 {
109     struct passwd *pwent;
110
111     pwent = getpwnam (username);
112     if (!pwent)
113         return NULL;
114
115     return pwent->pw_shell;
116 }
117
118 gboolean
119 tlm_utils_delete_dir (
120         const gchar *dir)
121 {
122     GDir* gdir = NULL;
123     struct stat sent;
124
125     if (!dir || !(gdir = g_dir_open(dir, 0, NULL))) {
126         return FALSE;
127     }
128
129     const gchar *fname = NULL;
130     gint retval = 0;
131     gchar *filepath = NULL;
132     while ((fname = g_dir_read_name (gdir)) != NULL) {
133         if (g_strcmp0 (fname, ".") == 0 ||
134             g_strcmp0 (fname, "..") == 0) {
135             continue;
136         }
137         retval = -1;
138         filepath = g_build_filename (dir, fname, NULL);
139         if (filepath) {
140             retval = lstat(filepath, &sent);
141             if (retval == 0) {
142                 /* recurse the directory */
143                 if (S_ISDIR (sent.st_mode)) {
144                     retval = (gint)!tlm_utils_delete_dir (filepath);
145                 } else {
146                     retval = g_remove (filepath);
147                 }
148             }
149             g_free (filepath);
150         }
151         if (retval != 0) {
152             g_dir_close (gdir);
153             return FALSE;
154         }
155     }
156     g_dir_close (gdir);
157
158     if (g_remove (dir) != 0) {
159         return FALSE;
160     }
161
162     return TRUE;
163 }
164
165 static gchar *
166 _get_tty_id (
167         const gchar *tty_name)
168 {
169     gchar *id = NULL;
170     const gchar *tmp = tty_name;
171
172     while (tmp) {
173         if (isdigit (*tmp)) {
174             id = g_strdup (tmp);
175             break;
176         }
177         tmp++;
178     }
179     return id;
180 }
181
182 static gchar *
183 _get_host_address (
184         const gchar *hostname)
185 {
186     gchar *hostaddress = NULL;
187     struct addrinfo hints, *info = NULL;
188
189     if (!hostname) return NULL;
190
191     memset (&hints, 0, sizeof (hints));
192     hints.ai_flags = AI_ADDRCONFIG;
193
194     if (getaddrinfo (hostname, NULL, &hints, &info) == 0) {
195         if (info) {
196             if (info->ai_family == AF_INET) {
197                 struct sockaddr_in *sa = (struct sockaddr_in *) info->ai_addr;
198                 hostaddress = g_malloc0 (sizeof(struct in_addr));
199                 memcpy (hostaddress, &(sa->sin_addr), sizeof (struct in_addr));
200             } else if (info->ai_family == AF_INET6) {
201                 struct sockaddr_in6 *sa = (struct sockaddr_in6 *) info->ai_addr;
202                 hostaddress = g_malloc0 (sizeof(struct in6_addr));
203                 memcpy (hostaddress, &(sa->sin6_addr),
204                         sizeof (struct in6_addr));
205             }
206             freeaddrinfo (info);
207         }
208     }
209     return hostaddress;
210 }
211
212 static gboolean
213 _is_tty_same (
214         const gchar *tty1_name,
215         const gchar *tty2_name)
216 {
217     gchar *tty1 = NULL, *tty2 = NULL;
218     gboolean res = FALSE;
219
220     if (tty1_name == tty2_name) return TRUE;
221     if (!tty1_name || !tty2_name) return FALSE;
222
223     if (*tty1_name == '/') tty1 = g_strdup (tty1_name);
224     else tty1 = g_strdup_printf ("/dev/%s", tty1_name);
225     if (*tty2_name == '/') tty2 = g_strdup (tty2_name);
226     else tty2 = g_strdup_printf ("/dev/%s", tty2_name);
227
228     res = (g_strcmp0 (tty1_name, tty2_name) == 0);
229
230     g_free (tty1);
231     g_free (tty2);
232     return res;
233 }
234
235 static gchar *
236 _get_host_name ()
237 {
238     gchar *name = g_malloc0 (HOST_NAME_SIZE);
239     if (gethostname (name, HOST_NAME_SIZE) != 0) {
240         g_free (name);
241         return NULL;
242     }
243     return name;
244 }
245
246 void
247 tlm_utils_log_utmp_entry (const gchar *username)
248 {
249     struct timeval tv;
250     pid_t pid;
251     struct utmp ut_ent;
252     struct utmp *ut_tmp = NULL;
253     gchar *hostname = NULL, *hostaddress = NULL;
254     const gchar *tty_name = NULL;
255     gchar *tty_no_dev_name = NULL, *tty_id = NULL;
256
257     DBG ("Log session entry to utmp/wtmp");
258
259     hostname = _get_host_name ();
260     hostaddress = _get_host_address (hostname);
261     tty_name = ttyname (0);
262     if (tty_name) {
263         tty_no_dev_name = g_strdup (strncmp(tty_name, "/dev/", 5) == 0 ?
264             tty_name + 5 : tty_name);
265     }
266     tty_id = _get_tty_id (tty_no_dev_name);
267     pid = getpid ();
268     utmpname (_PATH_UTMP);
269
270     setutent ();
271     while ((ut_tmp = getutent())) {
272         if ( (ut_tmp->ut_pid == pid) &&
273              (ut_tmp->ut_id[0] != '\0') &&
274              (ut_tmp->ut_type == LOGIN_PROCESS ||
275                      ut_tmp->ut_type == USER_PROCESS) &&
276              (_is_tty_same (ut_tmp->ut_line, tty_name))) {
277             break;
278         }
279     }
280
281     if (ut_tmp) memcpy (&ut_ent, ut_tmp, sizeof (ut_ent));
282     else        memset (&ut_ent, 0, sizeof (ut_ent));
283
284     ut_ent.ut_type = USER_PROCESS;
285     ut_ent.ut_pid = pid;
286     if (tty_id)
287         strncpy (ut_ent.ut_id, tty_id, sizeof (ut_ent.ut_id));
288     if (username)
289         strncpy (ut_ent.ut_user, username, sizeof (ut_ent.ut_user));
290     if (tty_no_dev_name)
291         strncpy (ut_ent.ut_line, tty_no_dev_name, sizeof (ut_ent.ut_line));
292     if (hostname)
293         strncpy (ut_ent.ut_host, hostname, sizeof (ut_ent.ut_host));
294     if (hostaddress)
295         memcpy (&ut_ent.ut_addr_v6, hostaddress, sizeof (ut_ent.ut_addr_v6));
296
297     ut_ent.ut_session = getsid (0);
298     gettimeofday (&tv, NULL);
299 #ifdef _HAVE_UT_TV
300     ut_ent.ut_tv.tv_sec = tv.tv_sec;
301     ut_ent.ut_tv.tv_usec = tv.tv_usec;
302 #else
303     ut_ent.ut_time = tv.tv_sec;
304 #endif
305
306     pututline (&ut_ent);
307     endutent ();
308
309     updwtmp (_PATH_WTMP, &ut_ent);
310
311     g_free (hostaddress);
312     g_free (hostname);
313     g_free (tty_no_dev_name);
314     g_free (tty_id);
315 }
316
317 static gchar **
318 _split_command_line_with_regex(const char *command, GRegex *regex) {
319   gchar **temp_strv = NULL;
320   gchar **temp_iter = NULL, **args_iter = NULL;
321   gchar **argv = NULL;
322
323   temp_strv = regex ? g_regex_split (regex, command, G_REGEX_MATCH_NOTEMPTY)
324                     : g_strsplit (command, " ", -1);
325   if (!temp_strv) {
326     WARN("Failed to split command: %s", command);
327     return NULL;
328   }
329
330   argv = g_new0 (gchar *, g_strv_length (temp_strv));
331   for (temp_iter = temp_strv, args_iter = argv;
332       *temp_iter != NULL;
333       temp_iter++) {
334     size_t item_len = 0;
335     gchar *item = g_strstrip (*temp_iter);
336
337     item_len = strlen (item);
338     if (item_len == 0) {
339       continue;
340     }
341     if ((item[0] == '\"' && item[item_len - 1] == '\"') ||
342         (item[0] == '\'' && item[item_len - 1] == '\'')) {
343       item[item_len - 1] = '\0';
344       memmove (item, item + 1, item_len - 1);
345     }
346     *args_iter = g_strcompress (item);
347     args_iter++;
348   }
349   g_strfreev (temp_strv);
350
351   return argv;
352 }
353
354 gchar **
355 tlm_utils_split_command_line(const gchar *command) {
356   const gchar *pattern = "('.*?'|\".*?\"|\\S+)";
357   GError *error = NULL;
358   GRegex *regex = NULL;
359   gchar **argv = NULL;
360  
361   if (!command) {
362     WARN("Cannot pase NULL arguments string");
363     return NULL;
364   }
365  
366   if (!(regex = g_regex_new(pattern, 0, G_REGEX_MATCH_NOTEMPTY, &error))) {
367     WARN("Failed to create regex: %s", error->message);
368     g_error_free(error);
369   }
370
371   argv = _split_command_line_with_regex (command, regex);
372
373   g_regex_unref (regex);
374
375   return argv;
376 }
377
378 GList *
379 tlm_utils_split_command_lines (const GList const *commands_list) {
380   const gchar *pattern = "('.*?'|\".*?\"|\\S+)";
381   GError *error = NULL;
382   GRegex *regex = NULL;
383   GList *argv_list = NULL;
384   const GList *tmp_list = NULL;
385
386   if (!commands_list) {
387     return NULL;
388   }
389
390   if (!(regex = g_regex_new(pattern, 0, G_REGEX_MATCH_NOTEMPTY, &error))) {
391     WARN("Failed to create regex: %s", error->message);
392     g_error_free(error);
393   }
394
395   for (tmp_list = commands_list; tmp_list; tmp_list = tmp_list->next) {
396     argv_list = g_list_append (argv_list, _split_command_line_with_regex (
397                     (const gchar *)tmp_list->data, regex));
398   }
399
400   g_regex_unref (regex);
401
402   return argv_list;
403 }
404
405 typedef struct {
406   int ifd;
407   GHashTable *dir_table; /* { gchar*: GList* } */
408   GHashTable *wd_table; /* { int: const gchar* } */
409   WatchCb cb;
410   gpointer userdata;
411 } WatchInfo;
412
413 static WatchInfo*
414 _watch_info_new (
415     int ifd,
416     WatchCb cb,
417     gpointer userdata)
418 {
419   WatchInfo *info = g_slice_new0 (WatchInfo);
420   info->ifd = ifd;
421   info->cb = cb;
422   info->userdata = userdata;
423   info->dir_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
424   info->wd_table = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL);
425
426   return info;
427 }
428
429 static void
430 _destroy_dir_table_entry (gpointer key, gpointer value, gpointer userdata) {
431   (void)key;
432   (void)userdata;
433   g_list_free_full ((GList *)value, g_free);
434 }
435
436 static void
437 _destroy_wd_table_entry (gpointer key, gpointer value, gpointer userdata) {
438   (void)value;
439   int ifd = GPOINTER_TO_INT(userdata);
440   int wd = GPOINTER_TO_INT(key);
441
442   inotify_rm_watch (ifd, wd);
443 }
444
445 static void
446 _watch_info_free (WatchInfo *info)
447 {
448   if (!info) return;
449
450   if (info->dir_table) {
451     g_hash_table_foreach (info->dir_table, _destroy_dir_table_entry, NULL);
452     g_hash_table_unref (info->dir_table);
453   }
454   if (info->wd_table) {
455     g_hash_table_foreach (info->wd_table, _destroy_wd_table_entry,
456         GINT_TO_POINTER(info->ifd));
457     g_hash_table_unref (info->wd_table);
458   }
459   if (info->ifd) close(info->ifd);
460
461   g_slice_free (WatchInfo, info);
462 }
463
464 typedef enum {
465   WATCH_FAILED,
466   WATCH_ADDED,
467   WATCH_READY
468 } AddWatchResults;
469   
470 AddWatchResults
471 _add_watch (int ifd, char *file_path, WatchInfo *info) {
472   GList *file_list = NULL;
473   int wd = 0;
474   gchar *file_name = NULL;
475   gchar *dir = NULL;
476   AddWatchResults res = WATCH_FAILED;
477
478   if (!file_path) return WATCH_FAILED;
479
480   file_name = g_path_get_basename (file_path);
481   if (!file_name) return WATCH_FAILED;
482
483   dir = g_path_get_dirname (file_path);
484   if (!dir) {
485     g_free (file_name);
486     return WATCH_FAILED;
487   }
488   if ((file_list = (GList *)g_hash_table_lookup (
489         info->dir_table, (gconstpointer)dir))) {
490     file_list = g_list_append (file_list, file_name);
491     g_free (dir);
492     return WATCH_ADDED;
493   }
494   file_list = g_list_append (NULL, file_name);
495   g_hash_table_insert (info->dir_table, g_strdup(dir), file_list);
496
497   /* add watch on directory if its not existing */
498   if (g_access (dir, 0)) {
499     return _add_watch (ifd, dir, info);
500   }
501
502   DBG("Adding watch for file '%s' in dir '%s'", file_name, dir);
503   if ((wd = inotify_add_watch (ifd, dir, IN_CREATE)) == -1) {
504     WARN ("failed to add inotify watch on %s: %s", dir, strerror (errno));
505     res = WATCH_FAILED;
506     goto remove_and_return;
507   }
508
509   if (!g_access (file_path, 0)) {
510     /* socket is ready, need not have a inotify watch for this */
511     inotify_rm_watch (ifd, wd);
512     res = WATCH_READY;
513     goto remove_and_return;
514   }
515
516   g_hash_table_insert (info->wd_table, GINT_TO_POINTER(wd), dir);
517
518   return WATCH_ADDED;
519
520 remove_and_return:
521   g_hash_table_remove (info->dir_table, (gconstpointer)dir);
522   g_list_free_full (file_list, (GDestroyNotify)g_free);
523   
524   return res;
525 }
526
527 static gboolean
528 _inotify_watcher_cb (gint ifd, GIOCondition condition, gpointer userdata)
529 {
530   WatchInfo *info = (WatchInfo *)userdata;
531   struct inotify_event *ie = NULL;
532   gsize size = sizeof (struct inotify_event) + PATH_MAX + 1;
533   guint nwatch = g_hash_table_size (info->wd_table);
534
535   ie = (struct inotify_event *) g_slice_alloc0(size);
536   while (nwatch &&
537          read (ifd, ie, size) > (ssize_t)sizeof (struct inotify_event)) {
538     GList *file_list = NULL;
539     GList *element = NULL;
540     GList *pending_list = NULL;
541     gboolean is_first = FALSE;
542     gchar *file_path = NULL;
543     const gchar *dir = NULL;
544
545     dir = (gchar *)g_hash_table_lookup (
546         info->wd_table, GINT_TO_POINTER(ie->wd));
547     if (!dir) continue;
548
549     file_list = g_hash_table_lookup (info->dir_table, (gconstpointer)dir);
550     element = g_list_find_custom (file_list,
551         (gpointer)ie->name, (GCompareFunc)g_strcmp0);
552     if (!element) {
553       DBG("Ignoring '%s' file creation", ie->name);
554       continue;
555     }
556     is_first = (file_list == element);
557
558     g_free (element->data);
559     file_list = g_list_delete_link(file_list, element);
560     if (!file_list) {
561       g_hash_table_remove (info->dir_table, dir);
562       g_hash_table_remove (info->wd_table, GINT_TO_POINTER(ie->wd));
563       inotify_rm_watch (ifd, ie->wd);
564       nwatch--;
565     } else if (is_first) {
566       g_hash_table_insert (info->dir_table, g_strdup (dir), file_list);
567     }
568
569     file_path = g_build_filename (dir, ie->name, NULL);
570     if ((pending_list = (GList *)g_hash_table_lookup (info->dir_table,
571           (gconstpointer)file_path)) != NULL) {
572       GList *tmp = NULL;
573
574       // as we are about add real inotify watch, first remove from dir_table
575       g_hash_table_steal (info->dir_table, (gconstpointer)file_path);
576
577       // Add watches to all the files depend on this directory
578       for (tmp = pending_list; tmp; tmp = tmp->next) {
579         gchar *file_name = (gchar *)tmp->data;
580         gchar *new_file_path = g_build_filename (file_path, file_name, NULL);
581         AddWatchResults res = _add_watch (ifd, new_file_path, info);
582         if (res == WATCH_READY) {
583           if (info->cb) {
584             info->cb (new_file_path, nwatch == 0, NULL, info->userdata);
585           }
586         } else if (res == WATCH_ADDED) nwatch++;
587         else {
588           WARN ("Couldn't add watch on '%s'", new_file_path);
589         }
590         g_free (file_name);
591         g_free (new_file_path);
592       }
593       g_list_free (pending_list);
594     } else {
595       DBG("%s", file_path);
596       if (info->cb) info->cb (file_path, nwatch == 0, NULL, info->userdata);
597     }
598     g_free (file_path);
599   }
600
601   g_slice_free1 (size, ie);
602
603   return nwatch ? G_SOURCE_CONTINUE : G_SOURCE_REMOVE;
604 }
605
606 gchar *
607 _expand_file_path (const gchar *file_path)
608 {
609   gchar **items =NULL;
610   gchar **tmp_item =NULL;
611   gchar *expanded_path = NULL;
612
613   if (!file_path) return NULL;
614
615   /* nothing to expand
616    * FIXME: we are not considering filename which having \$ in it
617    */
618   if (g_strrstr (file_path, "$") == NULL) return g_strdup(file_path);
619
620   items = g_strsplit (file_path, G_DIR_SEPARATOR_S, -1);
621   /* soemthing wrong in file path */
622   if (!items) { return g_strdup (file_path); }
623
624   for (tmp_item = items; *tmp_item; tmp_item++) {
625     char *item = *tmp_item;
626     if (item[0] == '$') {
627       const gchar *env = g_getenv (item+1);
628       g_free (item);
629       *tmp_item = g_strdup (env ? env : "");
630     }
631   }
632   
633   expanded_path = g_strjoinv (G_DIR_SEPARATOR_S, items);
634
635   g_strfreev(items);
636
637   return expanded_path;
638 }
639
640 guint
641 tlm_utils_watch_for_files (
642     const gchar **watch_list,
643     WatchCb cb,
644     gpointer userdata)
645 {
646   gint nwatch = 0;
647   int ifd = 0;
648   WatchInfo *w_info = NULL;
649
650   if (!watch_list) return 0;
651
652   if ((ifd = inotify_init1 (IN_NONBLOCK | IN_CLOEXEC)) < 0) {
653     WARN("Failed to start inotify: %s", strerror(errno));
654     return 0;
655   }
656
657   w_info = _watch_info_new (ifd, cb, userdata);
658
659   for (; *watch_list; watch_list++) {
660     char *socket_path  = _expand_file_path (*watch_list);
661     AddWatchResults res = _add_watch (ifd, socket_path, w_info);
662     if (res == WATCH_FAILED) {
663       WARN ("Failed to watch for '%s'", socket_path);
664     } else if (res == WATCH_READY) {
665       gboolean is_final = !nwatch && !*(watch_list + 1);
666       if (cb) cb (socket_path, is_final, NULL, userdata);
667     } else {
668       nwatch++;
669     }
670   }
671
672   if (nwatch == 0) {
673     _watch_info_free (w_info);
674     return 0;
675   }
676
677   return g_unix_fd_add_full (G_PRIORITY_DEFAULT, ifd, G_IO_IN,
678       _inotify_watcher_cb, w_info, (GDestroyNotify)_watch_info_free);
679 }
680