+/*
+ * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <glib.h>
+
+#include "helper-inotify.h"
+#include "stc-manager-util.h"
+
+typedef struct {
+ GIOChannel *channel;
+ uint watch;
+ int wd;
+
+ inotify_event_cb cb;
+} stc_inotify_s;
+
+static GHashTable *g_inotify_hash;
+
+static gboolean __inotify_data(GIOChannel *channel, GIOCondition cond,
+ gpointer user_data)
+{
+ stc_inotify_s *inotify = user_data;
+ char buffer[sizeof(struct inotify_event) + NAME_MAX + 1];
+ char *next_event;
+ gsize bytes_read;
+ GIOStatus status;
+
+ if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
+ inotify->watch = 0;
+ return FALSE;
+ }
+
+ status = g_io_channel_read_chars(channel, buffer,
+ sizeof(buffer), &bytes_read, NULL);
+
+ switch (status) {
+ case G_IO_STATUS_NORMAL:
+ break;
+ case G_IO_STATUS_AGAIN:
+ return TRUE;
+ default:
+ STC_LOGE("Reading from inotify channel failed");
+ inotify->watch = 0;
+ return FALSE;
+ }
+
+ next_event = buffer;
+
+ while (bytes_read > 0) {
+ struct inotify_event *event;
+ gchar *ident;
+ gsize len;
+ inotify_event_cb callback = inotify->cb;
+
+ event = (struct inotify_event *) next_event;
+ if (event->len)
+ ident = next_event + sizeof(struct inotify_event);
+ else
+ ident = NULL;
+
+ len = sizeof(struct inotify_event) + event->len;
+ if (len > bytes_read)
+ break;
+
+ next_event += len;
+ bytes_read -= len;
+
+ (*callback)(event, ident);
+ }
+
+ return TRUE;
+}
+
+static void __remove_watch(stc_inotify_s *inotify)
+{
+ int fd;
+
+ if (!inotify->channel)
+ return;
+
+ if (inotify->watch > 0)
+ g_source_remove(inotify->watch);
+
+ fd = g_io_channel_unix_get_fd(inotify->channel);
+
+ if (inotify->wd >= 0)
+ inotify_rm_watch(fd, inotify->wd);
+
+ g_io_channel_unref(inotify->channel);
+}
+
+static int __create_watch(const char *path, stc_inotify_s *inotify)
+{
+ int fd;
+
+ STC_LOGD("Add directory watch for [%s]", path);
+
+ fd = inotify_init();
+ if (fd < 0)
+ return -EIO;
+
+ inotify->wd = inotify_add_watch(fd, path,
+ IN_MODIFY | IN_CREATE | IN_DELETE |
+ IN_MOVED_TO | IN_MOVED_FROM);
+ if (inotify->wd < 0) {
+ STC_LOGE("Creation of [%s] watch failed", path);
+ close(fd);
+ return -EIO;
+ }
+
+ inotify->channel = g_io_channel_unix_new(fd);
+ if (!inotify->channel) {
+ STC_LOGE("Creation of inotify channel failed");
+ inotify_rm_watch(fd, inotify->wd);
+ inotify->wd = 0;
+
+ close(fd);
+ return -EIO;
+ }
+
+ g_io_channel_set_close_on_unref(inotify->channel, TRUE);
+ g_io_channel_set_encoding(inotify->channel, NULL, NULL);
+ g_io_channel_set_buffered(inotify->channel, FALSE);
+
+ inotify->watch = g_io_add_watch(inotify->channel,
+ G_IO_IN | G_IO_HUP | G_IO_NVAL | G_IO_ERR,
+ __inotify_data, inotify);
+
+ return 0;
+}
+
+static void __inotify_destroy(gpointer user_data)
+{
+ stc_inotify_s *inotify = user_data;
+
+ __remove_watch(inotify);
+ FREE(inotify);
+}
+
+int inotify_register(const char *path, inotify_event_cb callback)
+{
+ int err;
+ stc_inotify_s *inotify;
+
+ if (!callback)
+ return -EINVAL;
+
+ inotify = g_hash_table_lookup(g_inotify_hash, path);
+ if (inotify)
+ goto update;
+
+ inotify = g_try_new0(stc_inotify_s, 1);
+ if (!inotify)
+ return -ENOMEM;
+
+ inotify->wd = -1;
+
+ err = __create_watch(path, inotify);
+ if (err < 0) {
+ FREE(inotify);
+ return err;
+ }
+
+ g_hash_table_replace(g_inotify_hash, g_strdup(path), inotify);
+
+update:
+ inotify->cb = callback;
+
+ return 0;
+}
+
+void inotify_deregister(const char *path)
+{
+ stc_inotify_s *inotify;
+
+ inotify = g_hash_table_lookup(g_inotify_hash, path);
+ if (!inotify)
+ return;
+
+ g_hash_table_remove(g_inotify_hash, path);
+}
+
+int inotify_initialize(void)
+{
+ g_inotify_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, __inotify_destroy);
+ return 0;
+}
+
+void inotify_deinitialize(void)
+{
+ g_hash_table_destroy(g_inotify_hash);
+}