/* * * Connection Manager * * Copyright (C) 2007-2012 Intel Corporation. All rights reserved. * Copyright (C) 2012 BMW Car IT GmbH. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include "connman.h" struct connman_inotify { GIOChannel *channel; uint watch; int wd; GSList *list; }; static GHashTable *inotify_hash; static gboolean inotify_data(GIOChannel *channel, GIOCondition cond, gpointer user_data) { struct connman_inotify *inotify = user_data; char buffer[256]; char *next_event; gsize bytes_read; GIOStatus status; GSList *list; 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) -1, &bytes_read, NULL); switch (status) { case G_IO_STATUS_NORMAL: break; case G_IO_STATUS_AGAIN: return TRUE; default: connman_error("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; 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; /* check if inotify_event block fit */ if (len > bytes_read) break; next_event += len; bytes_read -= len; for (list = inotify->list; list != NULL; list = list->next) { inotify_event_cb callback = list->data; (*callback)(event, ident); } } return TRUE; } static int create_watch(const char *path, struct connman_inotify *inotify) { int fd; DBG("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) { connman_error("Creation of %s watch failed", path); close(fd); return -EIO; } inotify->channel = g_io_channel_unix_new(fd); if (inotify->channel == NULL) { connman_error("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 remove_watch(struct connman_inotify *inotify) { int fd; if (inotify->channel == NULL) 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); } int connman_inotify_register(const char *path, inotify_event_cb callback) { struct connman_inotify *inotify; int err; if (callback == NULL) return -EINVAL; inotify = g_hash_table_lookup(inotify_hash, path); if (inotify != NULL) goto update; inotify = g_try_new0(struct connman_inotify, 1); if (inotify == NULL) return -ENOMEM; inotify->wd = -1; err = create_watch(path, inotify); if (err < 0) { g_free(inotify); return err; } g_hash_table_replace(inotify_hash, g_strdup(path), inotify); update: inotify->list = g_slist_prepend(inotify->list, callback); return 0; } static void cleanup_inotify(gpointer user_data) { struct connman_inotify *inotify = user_data; g_slist_free(inotify->list); remove_watch(inotify); g_free(inotify); } void connman_inotify_unregister(const char *path, inotify_event_cb callback) { struct connman_inotify *inotify; inotify = g_hash_table_lookup(inotify_hash, path); if (inotify == NULL) return; inotify->list = g_slist_remove(inotify->list, callback); if (inotify->list != NULL) return; g_hash_table_remove(inotify_hash, path); } int __connman_inotify_init(void) { DBG(""); inotify_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, cleanup_inotify); return 0; } void __connman_inotify_cleanup(void) { DBG(""); g_hash_table_destroy(inotify_hash); }