Commit message: Fix potential access violation, use runtime user dir instead of tmp dir.

Note: it limits libmenu-cache compatibility to menu-cached >= 0.7.0.
CVE: CVE-2017-8933
CWE: CWE-20
Vulnerability description: Libmenu-cache 1.0.2 insecurely uses /tmp for a socket file, allowing a local user to cause a denial of service (menu unavailability).

Project: menu-cache
File: menu-cache.c

MenuCacheDir* menu_cache_get_dir_from_path( MenuCache* cache, const char* path )
{
    char** names = g_strsplit( path + 1, "/", -1 );
    int i = 0;
    MenuCacheDir* dir = NULL;

    if( !names )
        return NULL;

    if( G_UNLIKELY(!names[0]) )
    {
        g_strfreev(names);
        return NULL;
    }
    /* the topmost dir of the path should be the root menu dir. */
    MENU_CACHE_LOCK;
    dir = cache->root_dir;
    if (G_UNLIKELY(dir == NULL) || strcmp(names[0], MENU_CACHE_ITEM(dir)->id))
    {
        MENU_CACHE_UNLOCK;
        return NULL;
    }

    for( ++i; names[i]; ++i )
    {
        GSList* l;
        for( l = dir->children; l; l = l->next )
        {
            MenuCacheItem* item = MENU_CACHE_ITEM(l->data);
            if( item->type == MENU_CACHE_TYPE_DIR && 0 == strcmp( item->id, names[i] ) )
                dir = MENU_CACHE_DIR(item);
        }
        /* FIXME: we really should ref it on return since other thread may
           destroy the parent at this time and returned data become invalid.
           Therefore this call isn't thread-safe! */
        if( ! dir )
        {
            MENU_CACHE_UNLOCK;
            return NULL;
        }
    }
    MENU_CACHE_UNLOCK;
    return dir;
}

Project: menu-cache
File: menu-cache.c

MenuCacheDir* menu_cache_dup_root_dir( MenuCache* cache )
{
    MenuCacheDir* dir;
    MENU_CACHE_LOCK;
    dir = cache->root_dir;
    if(G_LIKELY(dir))
        menu_cache_item_ref(MENU_CACHE_ITEM(dir));
    MENU_CACHE_UNLOCK;
    return dir;
}

Project: menu-cache
File: menu-cached.c

static gboolean on_client_data_in(GIOChannel* ch, GIOCondition cond, gpointer user_data)
{
    char *line;
    gsize len;
    GIOStatus st;
    const char* md5;
    Cache* cache;
    GFile* gf;
    gboolean ret = TRUE;

    if(cond & (G_IO_HUP|G_IO_ERR) )
    {
        on_client_closed(user_data);
        return FALSE; /* remove the watch */
    }

retry:
    st = g_io_channel_read_line( ch, &line, &len, NULL, NULL );
    if( st == G_IO_STATUS_AGAIN )
        goto retry;
    if( st != G_IO_STATUS_NORMAL ) {
        on_client_closed(user_data);
        return FALSE;
    }

    --len;
    line[len] = 0;

    DEBUG("line(%d) = %s", (int)len, line);

    if( memcmp(line, "REG:", 4) == 0 )
    {
        int n_files = 0, i;
        char *pline = line + 4;
        char *sep, *menu_name, *lang_name, *cache_dir;
        char **files = NULL;
        char **env;
        char reload_cmd[38] = "REL:";

        len -= 4;
        /* Format of received string, separated by '\t'.
         * Menu Name
         * Language Name
         * XDG_CACHE_HOME
         * XDG_CONFIG_DIRS
         * XDG_MENU_PREFIX
         * XDG_DATA_DIRS
         * XDG_CONFIG_HOME
         * XDG_DATA_HOME
         * (optional) CACHE_GEN_VERSION
         * md5 hash */

        md5 = pline + len - 32; /* the md5 hash of this menu */

        cache = (Cache*)g_hash_table_lookup(hash, md5);
        if( !cache )
        {
            sep = strchr(pline, '\t');
            *sep = '\0';
            menu_name = pline;
            pline = sep + 1;

            sep = strchr(pline, '\t');
            *sep = '\0';
            lang_name = pline;
            pline = sep + 1;

            ((char *)md5)[-1] = '\0';
            env = g_strsplit(pline, "\t", 0);

            cache_dir = env[0]; /* XDG_CACHE_HOME */
            /* obtain cache dir from client's env */

            cache = g_slice_new0( Cache );
            cache->cache_file = g_build_filename(*cache_dir ? cache_dir : g_get_user_cache_dir(), "menus", md5, NULL );
            if( ! cache_file_is_updated(cache->cache_file, &n_files, &files) )
            {
                /* run menu-cache-gen */
                if(! regenerate_cache( menu_name, lang_name, cache->cache_file, env, &n_files, &files ) )
                {
                    DEBUG("regeneration of cache failed!!");
                }
            }
            else
            {
                /* file loaded, schedule update anyway */
                cache->need_reload = TRUE;
                cache->delayed_reload_handler = g_timeout_add_seconds_full(G_PRIORITY_LOW, 3,
                                                    (GSourceFunc)delayed_reload, cache, NULL);
            }
            memcpy( cache->md5, md5, 33 );
            cache->n_files = n_files;
            cache->files = files;
            cache->menu_name = g_strdup(menu_name);
            cache->lang_name = g_strdup(lang_name);
            cache->env = env;
            cache->mons = g_new0(GFileMonitor*, n_files+1);
            /* create required file monitors */
            DEBUG("%d files/dirs are monitored.", n_files);
            for( i = 0; i < n_files; ++i )
            {
                gf = g_file_new_for_path( files[i] + 1 );
                if( files[i][0] == 'D' )
                    cache->mons[i] = g_file_monitor_directory( gf, 0, NULL, NULL );
                else
                    cache->mons[i] = g_file_monitor_file( gf, 0, NULL, NULL );
                DEBUG("monitor: %s", g_file_get_path(gf));
                g_signal_connect(cache->mons[i], "changed",
                                 G_CALLBACK(on_file_changed), cache);
                g_object_unref(gf);
            }
            /*
            gf = g_file_new_for_path( cache_file );
            cache->cache_mon = g_file_monitor_file( gf, 0, NULL, NULL );
            g_signal_connect( cache->cache_mon, "changed", on_file_changed, cache);
            g_object_unref(gf);
            */
            g_hash_table_insert(hash, cache->md5, cache);
            DEBUG("new menu cache %p added to hash", cache);
        }
        else if (access(cache->cache_file, R_OK) != 0)
        {
            /* bug SF#657: if user deleted cache file we have to regenerate it */
            if (!regenerate_cache(cache->menu_name, cache->lang_name, cache->cache_file,
                                  cache->env, &cache->n_files, &cache->files))
            {
                DEBUG("regeneration of cache failed.");
            }
        }
        /* DEBUG("menu %s requested by client %d", md5, g_io_channel_unix_get_fd(ch)); */
        cache->clients = g_slist_prepend(cache->clients, user_data);
        if(cache->delayed_free_handler)
        {
            g_source_remove(cache->delayed_free_handler);
            cache->delayed_free_handler = 0;
        }
        DEBUG("client %p added to cache %p", ch, cache);

        /* generate a fake reload notification */
        DEBUG("fake reload!");
        memcpy(reload_cmd + 4, md5, 32);

        reload_cmd[36] = '\n';
        reload_cmd[37] = '\0';

        DEBUG("reload command: %s", reload_cmd);
        ret = write(g_io_channel_unix_get_fd(ch), reload_cmd, 37) > 0;
    }
    else if( memcmp(line, "UNR:", 4) == 0 )
    {
        md5 = line + 4;
        DEBUG("unregister: %s", md5);
        cache = (Cache*)g_hash_table_lookup(hash, md5);
        if(cache && cache->clients)
        {
            /* remove the IO channel from the cache */
            cache->clients = g_slist_remove(cache->clients, user_data);
            if(cache->clients == NULL)
                cache_free(cache);
        }
        else
            DEBUG("bug! client is not found.");
    }
    g_free( line );

    return ret;
}

Project: menu-cache
File: menu-cached.c

static gboolean on_server_conn_close(GIOChannel* ch, GIOCondition cond, gpointer user_data)
{
    /* FIXME: is this possible? */
    /* the server socket is accidentally closed. terminate the server. */
    terminate(SIGTERM);
    return TRUE;
}

Project: menu-cache
File: menu-cache.c

MenuCacheItem *menu_cache_find_child_by_id(MenuCacheDir *dir, const char *id)
{
    GSList *child;
    MenuCacheItem *item = NULL;

    if (MENU_CACHE_ITEM(dir)->type != MENU_CACHE_TYPE_DIR || id == NULL)
        return NULL;
    MENU_CACHE_LOCK;
    for (child = dir->children; child; child = child->next)
        if (g_strcmp0(MENU_CACHE_ITEM(child->data)->id, id) == 0)
        {
            item = menu_cache_item_ref(child->data);
            break;
        }
    MENU_CACHE_UNLOCK;
    return item;
}

Project: menu-cache
File: menu-cache.c

static gpointer menu_cache_loader_thread(gpointer data)
{
    MenuCache* cache = (MenuCache*)data;

    /* try to connect server now */
    if(!connect_server(cache->cancellable))
    {
        g_print("unable to connect to menu-cached.\n");
        SET_CACHE_READY(cache);
        return NULL;
    }
    /* and request update from server */
    if ((cache->cancellable && g_cancellable_is_cancelled(cache->cancellable)) ||
        !register_menu_to_server(cache))
        SET_CACHE_READY(cache);
    return NULL;
}

Project: menu-cache
File: menu-cache.c

static MenuCache* menu_cache_create(const char* menu_name)
{
    MenuCache* cache;
    const gchar * const * langs = g_get_language_names();
    const char* xdg_cfg_env = g_getenv("XDG_CONFIG_DIRS");
    const char* xdg_prefix_env = g_getenv("XDG_MENU_PREFIX");
    const char* xdg_data_env = g_getenv("XDG_DATA_DIRS");
    const char* xdg_cfg_home_env = g_getenv("XDG_CONFIG_HOME");
    const char* xdg_data_home_env = g_getenv("XDG_DATA_HOME");
    const char* xdg_cache_home_env = g_getenv("XDG_CACHE_HOME");
    char *xdg_cfg, *xdg_prefix, *xdg_data, *xdg_cfg_home, *xdg_data_home, *xdg_cache_home;
    char* buf;
    const char* md5;
    char* file_name;
    int len = 0;
    GChecksum *sum;
    char *langs_list;

    xdg_cfg = _validate_env(xdg_cfg_env);
    xdg_prefix = _validate_env(xdg_prefix_env);
    xdg_data = _validate_env(xdg_data_env);
    xdg_cfg_home = _validate_env(xdg_cfg_home_env);
    xdg_data_home = _validate_env(xdg_data_home_env);
    xdg_cache_home = _validate_env(xdg_cache_home_env);

    /* reconstruct languages list in form as it should be in $LANGUAGES */
    langs_list = g_strjoinv(":", (char **)langs);
    for (buf = langs_list; *buf; buf++) /* reusing buf var as char pointer */
        if (*buf == '\n' || *buf == '\t')
            *buf = ' ';

    buf = g_strdup_printf( "REG:%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t" CACHE_VERSION
                           "\t00000000000000000000000000000000\n",
                            menu_name,
                            langs_list,
                            xdg_cache_home,
                            xdg_cfg,
                            xdg_prefix,
                            xdg_data,
                            xdg_cfg_home,
                            xdg_data_home );

    /* calculate the md5 sum of menu name + lang + all environment variables */
    sum = g_checksum_new(G_CHECKSUM_MD5);
    len = strlen(buf);
    g_checksum_update(sum, (guchar*)buf + 4, len - 38);
    md5 = g_checksum_get_string(sum);
    file_name = g_build_filename( g_get_user_cache_dir(), "menus", md5, NULL );
    DEBUG("cache file_name = %s", file_name);
    cache = menu_cache_new( file_name );
    cache->reg = buf;
    cache->md5 = buf + len - 33;
    memcpy( cache->md5, md5, 32 );
    cache->menu_name = g_strdup(menu_name);
    g_free( file_name );
    g_free(langs_list);
    g_free(xdg_cfg);
    g_free(xdg_prefix);
    g_free(xdg_data);
    g_free(xdg_cfg_home);
    g_free(xdg_data_home);
    g_free(xdg_cache_home);

    g_checksum_free(sum); /* md5 is also freed here */

    MENU_CACHE_LOCK;
    g_hash_table_insert( hash, g_strdup(menu_name), cache );
    MENU_CACHE_UNLOCK;

    return cache;
}

Project: menu-cache
File: menu-cache.c

GSList* menu_cache_dir_get_children( MenuCacheDir* dir )
{
    /* NOTE: this is very ugly hack but dir may be freed by cache reload
       in server-io thread, so we should keep it alive :( */
    g_timeout_add_seconds(10, (GSourceFunc)menu_cache_item_unref,
                          menu_cache_item_ref(MENU_CACHE_ITEM(dir)));
    return dir->children;
}

Project: menu-cache
File: menu-cache.c

const char* menu_cache_item_get_file_dirname( MenuCacheItem* item )
{
    return item->file_dir ? item->file_dir->dir + 1 : NULL;
}

Project: menu-cache
File: menu-cache.c

static gboolean reload_notify(gpointer data)
{
    MenuCache* cache = (MenuCache*)data;
    GSList* l;
    MENU_CACHE_LOCK;
    /* we have it referenced and there is no source removal so no check */
    for( l = cache->notifiers; l; l = l->next )
    {
        CacheReloadNotifier* n = (CacheReloadNotifier*)l->data;
        if(n->func)
            n->func( cache, n->user_data );
    }
    MENU_CACHE_UNLOCK;
    return FALSE;
}

Project: menu-cache
File: menu-cache.c

MenuCacheDir* menu_cache_get_root_dir( MenuCache* cache )
{
    MenuCacheDir* dir = menu_cache_dup_root_dir(cache);
    /* NOTE: this is very ugly hack but cache->root_dir may be changed by
       cache reload in server-io thread, so we should keep it alive :( */
    if(dir)
        g_timeout_add_seconds(10, (GSourceFunc)menu_cache_item_unref, dir);
    return dir;
}

Project: menu-cache
File: menu-cache.c

static gint read_all_used_files(GDataInputStream* f, MenuCache* cache,
                                MenuCacheFileDir*** all_used_files)
{
    char *line;
    gsize len;
    int i, n;
    MenuCacheFileDir** dirs;

    line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
    if(G_UNLIKELY(line == NULL))
        return -1;

    n = atoi( line );
    g_free(line);
    if (G_UNLIKELY(n <= 0))
        return n;

    dirs = g_new0( MenuCacheFileDir *, n );

    for( i = 0; i < n; ++i )
    {
        line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
        if(G_UNLIKELY(line == NULL))
        {
            while (i-- > 0)
                menu_cache_file_dir_unref(dirs[i]);
            g_free(dirs);
            return -1;
        }
        dirs[i] = g_new(MenuCacheFileDir, 1);
        dirs[i]->n_ref = 1;
        dirs[i]->dir = line; /* don't include \n */
    }
    *all_used_files = dirs;
    return n;
}

Project: menu-cache
File: menu-cache.c

GSList *menu_cache_list_all_for_keyword(MenuCache* cache, const char *keyword)
{
    char *casefolded = g_utf8_casefold(keyword, -1);
    GSList *list;

    g_return_val_if_fail(cache != NULL && keyword != NULL, NULL);
    MENU_CACHE_LOCK;
    if (G_UNLIKELY(cache->root_dir == NULL))
        list = NULL;
    else
        list = list_app_in_dir_for_kw(cache->root_dir, NULL, casefolded);
    MENU_CACHE_UNLOCK;
    g_free(casefolded);
    return list;
}

Project: menu-cache
File: menu-cache.c

static gboolean _can_be_exec(MenuCacheApp *app)
{
    char *path;

    if (app->try_exec == NULL)
        return TRUE;
    path = g_find_program_in_path(app->try_exec);
    g_free(path);
    return (path != NULL);
}

Project: menu-cache
File: menu-cache.c

static gboolean connect_server(GCancellable* cancellable)
{
    int fd, rc;
    struct sockaddr_un addr;
    int retries = 0;

    G_LOCK(connect);
    if(server_fd != -1 || (cancellable && g_cancellable_is_cancelled(cancellable)))
    {
        G_UNLOCK(connect);
        return TRUE;
    }

retry:
    fd = socket(PF_UNIX, SOCK_STREAM, 0);
    if (fd < 0)
    {
        g_print("Failed to create socket\n");
        G_UNLOCK(connect);
        return FALSE;
    }
    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;

    get_socket_name( addr.sun_path, sizeof( addr.sun_path ) );

    if( connect(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0)
    {
        rc = errno;
        close(fd);
        if(cancellable && g_cancellable_is_cancelled(cancellable))
        {
            G_UNLOCK(connect);
            return TRUE;
        }
        if((rc == ECONNREFUSED || rc == ENOENT) && retries == 0)
        {
            DEBUG("no running server found, starting it");
            fork_server(addr.sun_path);
            ++retries;
            goto retry;
        }
        if(retries < MAX_RETRIES)
        {
            usleep(50000);
            ++retries;
            goto retry;
        }
        g_print("Unable to connect\n");
        G_UNLOCK(connect);
        return FALSE;
    }
    server_fd = fd;
    G_UNLOCK(connect);
#if GLIB_CHECK_VERSION(2, 32, 0)
    g_thread_new("menu-cache-io", server_io_thread, GINT_TO_POINTER(fd));
#else
    g_thread_create(server_io_thread, GINT_TO_POINTER(fd), FALSE, NULL);
#endif
    return TRUE;
}

Project: menu-cache
File: menu-cache.c

MenuCache* menu_cache_lookup( const char* menu_name )
{
    MenuCache* cache;

    /* lookup in a hash table for already loaded menus */
    MENU_CACHE_LOCK;
#if !GLIB_CHECK_VERSION(2, 32, 0)
    /* FIXME: destroy them on application exit? */
    if(!sync_run_mutex)
        sync_run_mutex = g_mutex_new();
    if(!sync_run_cond)
        sync_run_cond = g_cond_new();
#endif
    if( G_UNLIKELY( ! hash ) )
        hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL );
    else
    {
        cache = (MenuCache*)g_hash_table_lookup(hash, menu_name);
        if( cache )
        {
            menu_cache_ref(cache);
            MENU_CACHE_UNLOCK;
            return cache;
        }
    }
    MENU_CACHE_UNLOCK;

    cache = menu_cache_create(menu_name);
    cache->cancellable = g_cancellable_new();
#if GLIB_CHECK_VERSION(2, 32, 0)
    cache->thr = g_thread_new(menu_name, menu_cache_loader_thread, cache);
#else
    cache->thr = g_thread_create(menu_cache_loader_thread, cache, TRUE, NULL);
#endif
    return cache;
}

Project: menu-cache
File: menu-cache.c

static GSList* list_app_in_dir_for_kw(MenuCacheDir *dir, GSList *list, const char *kw)
{
    GSList *l;

    for (l = dir->children; l; l = l->next)
    {
        MenuCacheItem *item = MENU_CACHE_ITEM(l->data);
        switch (item->type)
        {
        case MENU_CACHE_TYPE_DIR:
            list = list_app_in_dir_for_kw(MENU_CACHE_DIR(item), list, kw);
            break;
        case MENU_CACHE_TYPE_APP:
            if (strstr(MENU_CACHE_APP(item)->keywords, kw) != NULL)
                list = g_slist_prepend(list, menu_cache_item_ref(item));
            break;
        case MENU_CACHE_TYPE_NONE:
        case MENU_CACHE_TYPE_SEP:
            break;
        }
    }
    return list;
}

Project: menu-cache
File: menu-cache.c

const char* menu_cache_item_get_name( MenuCacheItem* item )
{
    return item->name;
}

Project: menu-cache
File: menu-cached.c

static void do_reload(Cache* cache)
{
    GSList* l;
    char buf[38];
    int i;
    GFile* gf;

    int new_n_files;
    char **new_files = NULL;

    /* DEBUG("Re-generation of cache is needed!"); */
    /* DEBUG("call menu-cache-gen to re-generate the cache"); */
    memcpy( buf, "REL:", 4 );
    memcpy( buf + 4, cache->md5, 32 );
    buf[36] = '\n';
    buf[37] = '\0';

    if( ! regenerate_cache( cache->menu_name, cache->lang_name, cache->cache_file,
                            cache->env, &new_n_files, &new_files ) )
    {
        DEBUG("regeneration of cache failed.");
        return;
    }

    /* cancel old file monitors */
    g_strfreev(cache->files);
    for( i = 0; i < cache->n_files; ++i )
    {
        g_file_monitor_cancel( cache->mons[i] );
        g_signal_handlers_disconnect_by_func( cache->mons[i], on_file_changed, cache );
        g_object_unref( cache->mons[i] );
    }
/*
    g_file_monitor_cancel(cache->cache_mon);
    g_object_unref(cache->cache_mon);
*/

    cache->n_files = new_n_files;
    cache->files = new_files;

    cache->mons = g_realloc( cache->mons, sizeof(GFileMonitor*)*(cache->n_files+1) );
    /* create required file monitors */
    for( i = 0; i < cache->n_files; ++i )
    {
        gf = g_file_new_for_path( cache->files[i] + 1 );
        if( cache->files[i][0] == 'D' )
            cache->mons[i] = g_file_monitor_directory( gf, 0, NULL, NULL );
        else
            cache->mons[i] = g_file_monitor_file( gf, 0, NULL, NULL );
        g_signal_connect(cache->mons[i], "changed",
                         G_CALLBACK(on_file_changed), cache);
        g_object_unref(gf);
    }
/*
    gf = g_file_new_for_path( cache_file );
    cache->cache_mon = g_file_monitor_file( gf, 0, NULL, NULL );
    g_signal_connect( cache->cache_mon, "changed", on_file_changed, cache);
    g_object_unref(gf);
*/

    /* notify the clients that reload is needed. */
    for( l = cache->clients; l; )
    {
        ClientIO *channel_io = (ClientIO *)l->data;
        GIOChannel* ch = channel_io->channel;
        l = l->next; /* do it beforehand, as client may be removed below */
        if(write(g_io_channel_unix_get_fd(ch), buf, 37) < 37)
        {
            on_client_closed(channel_io);
        }
    }
    cache->need_reload = FALSE;
}

Project: menu-cache
File: menu-cache.c

static GSList* list_app_in_dir(MenuCacheDir* dir, GSList* list)
{
    GSList* l;
    for( l = dir->children; l; l = l->next )
    {
        MenuCacheItem* item = MENU_CACHE_ITEM(l->data);
        switch( menu_cache_item_get_type(item) )
        {
        case MENU_CACHE_TYPE_DIR:
            list = list_app_in_dir( MENU_CACHE_DIR(item), list );
            break;
        case MENU_CACHE_TYPE_APP:
            list = g_slist_prepend(list, menu_cache_item_ref(item));
            break;
        case MENU_CACHE_TYPE_NONE:
        case MENU_CACHE_TYPE_SEP:
            break;
        }
    }
    return list;
}

Project: menu-cache
File: menu-cache.c

const char* menu_cache_item_get_file_basename( MenuCacheItem* item )
{
    return item->file_name;
}

Project: menu-cache
File: menu-cache.c

static MenuCacheItem* read_item(GDataInputStream* f, MenuCache* cache,
                                MenuCacheFileDir** all_used_files, int n_all_used_files)
{
    MenuCacheItem* item;
    char *line;
    gsize len;
    gint idx;

    /* desktop/menu id */
    line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
    if(G_UNLIKELY(line == NULL))
        return NULL;

    if( G_LIKELY(len >= 1) )
    {
        if( line[0] == '+' ) /* menu dir */
        {
            item = (MenuCacheItem*)g_slice_new0( MenuCacheDir );
            item->n_ref = 1;
            item->type = MENU_CACHE_TYPE_DIR;
        }
        else if( line[0] == '-' ) /* menu item */
        {
            item = (MenuCacheItem*)g_slice_new0( MenuCacheApp );
            item->n_ref = 1;
            if( G_LIKELY( len > 1 ) ) /* application item */
                item->type = MENU_CACHE_TYPE_APP;
            else /* separator */
            {
                item->type = MENU_CACHE_TYPE_SEP;
                return item;
            }
        }
        else
            return NULL;

        item->id = g_strndup( line + 1, len - 1 );
        g_free(line);
    }
    else
    {
        g_free(line);
        return NULL;
    }

    /* name */
    line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
    if(G_UNLIKELY(line == NULL))
        goto _fail;
    if(G_LIKELY(len > 0))
        item->name = _unescape_lf(line);
    else
        g_free(line);

    /* comment */
    line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
    if(G_UNLIKELY(line == NULL))
        goto _fail;
    if(G_LIKELY(len > 0))
        item->comment = _unescape_lf(line);
    else
        g_free(line);

    /* icon */
    line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
    if(G_UNLIKELY(line == NULL))
        goto _fail;
    if(G_LIKELY(len > 0))
        item->icon = line;
    else
        g_free(line);

    /* file dir/basename */

    /* file name */
    line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
    if(G_UNLIKELY(line == NULL))
        goto _fail;
    if(G_LIKELY(len > 0))
        item->file_name = line;
    else if( item->type == MENU_CACHE_TYPE_APP )
    {
        /* When file name is the same as desktop_id, which is
         * quite common in desktop files, we use this trick to
         * save memory usage. */
        item->file_name = item->id;
        g_free(line);
    }
    else
        g_free(line);

    /* desktop file dir */
    line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
    if(G_UNLIKELY(line == NULL))
    {
_fail:
        g_free(item->id);
        g_free(item->name);
        g_free(item->comment);
        g_free(item->icon);
        if(item->file_name && item->file_name != item->id)
            g_free(item->file_name);
        if(item->type == MENU_CACHE_TYPE_DIR)
            g_slice_free(MenuCacheDir, MENU_CACHE_DIR(item));
        else
            g_slice_free(MenuCacheApp, MENU_CACHE_APP(item));
        return NULL;
    }
    idx = atoi( line );
    g_free(line);
    if( G_LIKELY( idx >=0 && idx < n_all_used_files ) )
    {
        item->file_dir = all_used_files[ idx ];
        g_atomic_int_inc(&item->file_dir->n_ref);
    }

    if( item->type == MENU_CACHE_TYPE_DIR )
        read_dir( f, MENU_CACHE_DIR(item), cache, all_used_files, n_all_used_files );
    else if( item->type == MENU_CACHE_TYPE_APP )
        read_app( f, MENU_CACHE_APP(item), cache );

    return item;
}

Project: menu-cache
File: menu-cached.c

static gboolean regenerate_cache( const char* menu_name,
                                  const char* lang_name,
                                  const char* cache_file,
                                  char** env,
                                  int* n_used_files,
                                  char*** used_files )
{
    FILE* f;
    int n_files, status = 0;
    char** files;
    const char* argv[] = {
        MENUCACHE_LIBEXECDIR "/menu-cache-gen",
        "-l", NULL,
        "-i", NULL,
        "-o", NULL,
        NULL};
    argv[2] = lang_name;
    argv[4] = menu_name;
    argv[6] = cache_file;

    /* DEBUG("cmd: %s", g_strjoinv(" ", argv)); */

    /* run menu-cache-gen */
    /* FIXME: is cast to (char**) valid here? */
    if( !g_spawn_sync(NULL, (char **)argv, NULL, 0,
                    pre_exec, env, NULL, NULL, &status, NULL ))
    {
        DEBUG("error executing menu-cache-gen");
    }
    /* DEBUG("exit status of menu-cache-gen: %d", status); */
    if( status != 0 )
        return FALSE;

    f = fopen( cache_file, "r" );
    if( f )
    {
        if( !read_all_used_files( f, &n_files, &files ) )
        {
            n_files = 0;
            files = NULL;
            /* DEBUG("error: read_all_used_files"); */
        }
        fclose(f);
    }
    else
        return FALSE;
    *n_used_files = n_files;
    *used_files = files;
    return TRUE;
}

Project: menu-cache
File: menu-cached.c

static void set_env( char* penv, const char* name )
{
    if( penv && *penv )
        g_setenv( name, penv, TRUE);
    else
        g_unsetenv( name );
}

Project: menu-cache
File: menu-cache.c

gboolean menu_cache_dir_is_visible(MenuCacheDir *dir)
{
    return ((dir->flags & FLAG_IS_NODISPLAY) == 0);
}

Project: menu-cache
File: menu-cached.c

static gboolean read_all_used_files( FILE* f, int* n_files, char*** used_files )
{
    char line[ 4096 ];
    int i, n, x;
    char** files;
    int ver_maj, ver_min;

    /* the first line of the cache file is version number */
    if( !fgets(line, G_N_ELEMENTS(line),f) )
        return FALSE;
    if( sscanf(line, "%d.%d", &ver_maj, &ver_min)< 2 )
        return FALSE;
    if (ver_maj != VER_MAJOR ||
        ver_min > VER_MINOR || ver_min < VER_MINOR_SUPPORTED)
        return FALSE;

    /* skip the second line containing menu name */
    if( ! fgets( line, G_N_ELEMENTS(line), f ) )
        return FALSE;

    /* num of files used */
    if( ! fgets( line, G_N_ELEMENTS(line), f ) )
        return FALSE;

    n = atoi( line );
    files = g_new0( char*, n + 1 );

    for( i = 0, x = 0; i < n; ++i )
    {
        int len;

        if( ! fgets( line, G_N_ELEMENTS(line), f ) )
            return FALSE;

        len = strlen( line );
        if( len <= 1 )
            return FALSE;
        files[ x ] = g_strndup( line, len - 1 ); /* don't include \n */
        if (g_file_test(files[x]+1, G_FILE_TEST_EXISTS))
            x++;
        else
        {
            DEBUG("ignoring not existent file from menu-cache-gen: %s", files[x]);
            g_free(files[x]);
            files[x] = NULL;
        }
    }
    *n_files = x;
    *used_files = files;
    return TRUE;
}

Project: menu-cache
File: menu-cache.c

MenuCacheDir* menu_cache_item_dup_parent( MenuCacheItem* item )
{
    MenuCacheDir* dir;
    MENU_CACHE_LOCK;
    dir = item->parent;
    if(G_LIKELY(dir))
        menu_cache_item_ref(MENU_CACHE_ITEM(dir));
    MENU_CACHE_UNLOCK;
    return dir;
}

Project: menu-cache
File: menu-cache.c

gboolean menu_cache_app_get_use_sn( MenuCacheApp* app )
{
    return ( (app->flags & FLAG_USE_SN) != 0 );
}

Project: menu-cache
File: menu-cached.c

static void cache_free(Cache* cache)
{
    /* shutdown cache in 10 minutes of inactivity */
    if(!cache->delayed_free_handler)
        cache->delayed_free_handler = g_timeout_add_seconds(600,
                                                            delayed_cache_free,
                                                            cache);
    DEBUG("menu %p cache unused, removing in 600s", cache);
}

Project: menu-cache
File: menu-cached.c

void on_file_changed( GFileMonitor* mon, GFile* gf, GFile* other,
                      GFileMonitorEvent evt, Cache* cache )
{
#ifdef G_ENABLE_DEBUG
    char *path = g_file_get_path(gf);
    g_debug("file %s is changed (%d).", path, evt);
    g_free(path);
#endif
    /* if( mon != cache->cache_mon ) */
    {
        /* Optimization: Some files in the dir are changed, but it
         * won't affect the content of the menu. So, just omit them,
         * and update the mtime of the cached file with utime.
         */
        int idx;
        /* dirty hack: get index of the monitor in array */
        for(idx = 0; idx < cache->n_files; ++idx)
        {
            if(mon == cache->mons[idx])
                break;
        }
        /* if the monitored file is a directory */
        if( G_LIKELY(idx < cache->n_files) && cache->files[idx][0] == 'D' )
        {
            char* changed_file = g_file_get_path(gf);
            char* dir_path = cache->files[idx]+1;
            int len = strlen(dir_path);
            /* if the changed file is a file in the monitored dir */
            if( strncmp(changed_file, dir_path, len) == 0 && changed_file[len] == '/' )
            {
                char* base_name = changed_file + len + 1;
                gboolean skip = TRUE;
                /* only *.desktop and *.directory files can affect the content of the menu. */
                if( g_str_has_suffix(base_name, ".desktop") )
                {
                        skip = FALSE;
                }
                else if( g_str_has_suffix(base_name, ".directory") )
                    skip = FALSE;

                if( skip )
                {
                    DEBUG("files are changed, but no re-generation is needed.");
                    return;
                }
            }
            g_free(changed_file);
        }
    }

    if( cache->delayed_reload_handler )
    {
        /* we got some change in last 3 seconds... not reload again */
        cache->need_reload = TRUE;
        g_source_remove(cache->delayed_reload_handler);
    }
    else
    {
        /* no reload in last 3 seconds... good, do it immediately */
        /* mark need_reload anyway. If do_reload succeeds, it is cleaned up */
        cache->need_reload = TRUE;
        do_reload(cache);
    }

    cache->delayed_reload_handler = g_timeout_add_seconds_full( G_PRIORITY_LOW, 3, (GSourceFunc)delayed_reload, cache, NULL );
}

Project: menu-cache
File: menu-cache.c

MenuCacheType menu_cache_item_get_type( MenuCacheItem* item )
{
    return item->type;
}

Project: menu-cache
File: menu-cache.c

static gboolean read_all_known_des(GDataInputStream* f, MenuCache* cache)
{
    char *line;
    gsize len;
    line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
    if(G_UNLIKELY(line == NULL))
        return FALSE;
    cache->known_des = g_strsplit_set( line, ";\n", 0 );
    g_free(line);
    return TRUE;
}

Project: menu-cache
File: menu-cached.c

static void get_socket_name( char* buf, int len )
{
    char* dpy = g_strdup(g_getenv("DISPLAY"));
    if(dpy && *dpy)
    {
        char* p = strchr(dpy, ':');
        for(++p; *p && *p != '.' && *p != '\n';)
            ++p;
        if(*p)
            *p = '\0';
    }
    /* NOTE: this socket name is incompatible with versions > 1.0.2,
            although this function is never used since 0.7.0 but
            libmenu-cache always requests exact socket name instead */
    g_snprintf( buf, len, "%s/.menu-cached-%s-%s", g_get_tmp_dir(),
                dpy ? dpy : ":0", g_get_user_name() );
    g_free(dpy);
}

Project: menu-cache
File: menu-cached.c

static int create_socket(struct sockaddr_un *addr)
{
    int fd = -1;

    fd = socket(PF_UNIX, SOCK_STREAM, 0);
    if (fd < 0)
    {
        DEBUG("Failed to create socket");
        return -1;
    }

    /* remove previous socket file */
    if (unlink(addr->sun_path) < 0) {
        if (errno != ENOENT)
            g_error("Couldn't remove previous socket file %s", addr->sun_path);
    }
    /* remove of previous socket file successful */
    else
        g_warning("removed previous socket file %s", addr->sun_path);

    if(bind(fd, (struct sockaddr *)addr, sizeof(*addr)) < 0)
    {
        DEBUG("Failed to bind to socket");
        close(fd);
        return -1;
    }
    if(listen(fd, 30) < 0)
    {
        DEBUG( "Failed to listen to socket" );
        close(fd);
        return -1;
    }
    return fd;
}

Project: menu-cache
File: menu-cache.c

guint32 menu_cache_get_desktop_env_flag( MenuCache* cache, const char* desktop_env )
{
    char** de;
    char **envs;
    guint32 flags = 0;
    int j;

    if (desktop_env == NULL || desktop_env[0] == '\0')
        return flags;

    envs = g_strsplit(desktop_env, ":", -1);
    MENU_CACHE_LOCK;
    de = cache->known_des;
    for (j = 0; envs[j]; j++)
    {
        if( de )
        {
            int i;
            for( i = 0; de[i]; ++i )
                if (strcmp(envs[j], de[i]) == 0)
                    break;
            if (de[i])
            {
                flags |= 1 << (i + N_KNOWN_DESKTOPS);
                continue;
            }
        }
        if (strcmp(envs[j], "GNOME") == 0)
            flags |= SHOW_IN_GNOME;
        else if (strcmp(envs[j], "KDE") == 0)
            flags |= SHOW_IN_KDE;
        else if (strcmp(envs[j], "XFCE") == 0)
            flags |= SHOW_IN_XFCE;
        else if (strcmp(envs[j], "LXDE") == 0)
            flags |= SHOW_IN_LXDE;
        else if (strcmp(envs[j], "ROX") == 0)
            flags |= SHOW_IN_ROX;
    }
    MENU_CACHE_UNLOCK;
    g_strfreev(envs);
    return flags;
}

Project: menu-cache
File: menu-cache.c

const char* menu_cache_app_get_working_dir( MenuCacheApp* app )
{
    return app->working_dir;
}

Project: menu-cache
File: menu-cache.c

static gboolean fork_server(const char *path)
{
    int ret, pid, status;

    if (!g_file_test (MENUCACHE_LIBEXECDIR "/menu-cached", G_FILE_TEST_IS_EXECUTABLE))
    {
        g_error("failed to find menu-cached");
    }

    /* Start daemon */
    pid = fork();
    if (pid == 0)
    {
        execl(MENUCACHE_LIBEXECDIR "/menu-cached", MENUCACHE_LIBEXECDIR "/menu-cached",
              path, NULL);
        g_print("failed to exec %s %s\n", MENUCACHE_LIBEXECDIR "/menu-cached", path);
    }

    /*
     * do a waitpid on the intermediate process to avoid zombies.
     */
retry_wait:
    ret = waitpid(pid, &status, 0);
    if (ret < 0) {
        if (errno == EINTR)
            goto retry_wait;
    }
    return TRUE;
}

Project: menu-cache
File: menu-cached.c

static void pre_exec( gpointer user_data )
{
    char** env = (char**)user_data;
    set_env(*env, "XDG_CACHE_HOME");
    set_env(*++env, "XDG_CONFIG_DIRS");
    set_env(*++env, "XDG_MENU_PREFIX");
    set_env(*++env, "XDG_DATA_DIRS");
    set_env(*++env, "XDG_CONFIG_HOME");
    set_env(*++env, "XDG_DATA_HOME");
    set_env(*++env, "CACHE_GEN_VERSION");
}

Project: menu-cache
File: menu-cache.c

static void get_socket_name( char* buf, int len )
{
    char* dpy = g_strdup(g_getenv("DISPLAY"));
    if(dpy && *dpy)
    {
        char* p = strchr(dpy, ':');
        for(++p; *p && *p != '.' && *p != '\n';)
            ++p;
        if(*p)
            *p = '\0';
    }
#if GLIB_CHECK_VERSION(2, 28, 0)
    g_snprintf( buf, len, "%s/menu-cached-%s", g_get_user_runtime_dir(),
                dpy ? dpy : ":0" );
#else
    g_snprintf( buf, len, "%s/.menu-cached-%s-%s", g_get_tmp_dir(),
                dpy ? dpy : ":0", g_get_user_name() );
#endif
    g_free(dpy);
}

Project: menu-cache
File: menu-cache.c

static MenuCacheItem *_scan_by_id(MenuCacheItem *item, const char *id)
{
    GSList *l;

    if (item)
        switch (menu_cache_item_get_type(item))
        {
            case MENU_CACHE_TYPE_DIR:
                for (l = MENU_CACHE_DIR(item)->children; l; l = l->next)
                {
                    item = _scan_by_id(MENU_CACHE_ITEM(l->data), id);
                    if (item)
                        return item;
                }
                break;
            case MENU_CACHE_TYPE_APP:
                if (g_strcmp0(menu_cache_item_get_id(item), id) == 0)
                    return item;
                break;
            default: ;
        }
    return NULL;
}

Project: menu-cache
File: menu-cache.c

static gpointer server_io_thread(gpointer data)
{
    char buf[1024]; /* protocol has a lot shorter strings */
    ssize_t sz;
    size_t ptr = 0;
    int fd = GPOINTER_TO_INT(data);
    GHashTableIter it;
    char* menu_name;
    MenuCache* cache;

    while(fd >= 0)
    {
        sz = read(fd, &buf[ptr], sizeof(buf) - ptr);
        if(sz <= 0) /* socket error or EOF */
        {
            MENU_CACHE_LOCK;
            ptr = hash ? g_hash_table_size(hash) : 0;
            MENU_CACHE_UNLOCK;
            if (ptr == 0) /* don't need it anymore */
                break;
            G_LOCK(connect);
            if(fd != server_fd) /* someone replaced us?! go out immediately! */
            {
                G_UNLOCK(connect);
                break;
            }
            server_fd = -1;
            G_UNLOCK(connect);
            DEBUG("connect failed, trying reconnect");
            sleep(1);
            if( ! connect_server(NULL) )
            {
                g_critical("fail to re-connect to the server.");
                MENU_CACHE_LOCK;
                if(hash)
                {
                    g_hash_table_iter_init(&it, hash);
                    while(g_hash_table_iter_next(&it, (gpointer*)&menu_name, (gpointer*)&cache))
                        SET_CACHE_READY(cache);
                }
                MENU_CACHE_UNLOCK;
                break;
            }
            DEBUG("successfully reconnected server, re-register menus.");
            /* re-register all menu caches */
            MENU_CACHE_LOCK;
            if(hash)
            {
                g_hash_table_iter_init(&it, hash);
                while(g_hash_table_iter_next(&it, (gpointer*)&menu_name, (gpointer*)&cache))
                    register_menu_to_server(cache);
                    /* FIXME: need we remove it from hash if failed? */
            }
            MENU_CACHE_UNLOCK;
            break; /* next thread will do it */
        }
        while(sz > 0)
        {
            while(sz > 0)
            {
                if(buf[ptr] == '\n')
                    break;
                sz--;
                ptr++;
            }
            if(ptr == sizeof(buf)) /* EOB reached, seems we got garbage */
            {
                g_warning("menu cache: got garbage from server, break connect");
                shutdown(fd, SHUT_RDWR); /* drop connection */
                break; /* we handle it above */
            }
            else if(sz == 0) /* incomplete line, wait for data again */
                break;
            /* we got a line, let check what we got */
            buf[ptr] = '\0';
            if(memcmp(buf, "REL:", 4) == 0) /* reload */
            {
                DEBUG("server ask us to reload cache: %s", &buf[4]);
                MENU_CACHE_LOCK;
                if(hash)
                {
                    g_hash_table_iter_init(&it, hash);
                    while(g_hash_table_iter_next(&it, (gpointer*)&menu_name, (gpointer*)&cache))
                    {
                        if(memcmp(cache->md5, &buf[4], 32) == 0)
                        {
                            DEBUG("RELOAD!");
                            menu_cache_reload(cache);
                            SET_CACHE_READY(cache);
                            break;
                        }
                    }
                }
                MENU_CACHE_UNLOCK;
                /* DEBUG("cache reloaded"); */
            }
            else
                g_warning("menu cache: unrecognized input: %s", buf);
            /* go to next line */
            sz--;
            if(sz > 0)
                memmove(buf, &buf[ptr+1], sz);
            ptr = 0;
        }
    }
    G_LOCK(connect);
    if (fd == server_fd)
        server_fd = -1;
    G_UNLOCK(connect);
    close(fd);
    /* DEBUG("server io thread terminated"); */
#if GLIB_CHECK_VERSION(2, 32, 0)
    g_thread_unref(g_thread_self());
#endif
    return NULL;
}

Project: menu-cache
File: menu-cache.c

gboolean menu_cache_app_get_is_visible( MenuCacheApp* app, guint32 de_flags )
{
    if(app->flags & FLAG_IS_NODISPLAY)
        return FALSE;
    return (!app->show_in_flags || (app->show_in_flags & de_flags)) &&
           _can_be_exec(app);
}

Project: menu-cache
File: menu-cache.c

static void menu_cache_file_dir_unref(MenuCacheFileDir *file_dir)
{
    if (file_dir && g_atomic_int_dec_and_test(&file_dir->n_ref))
    {
        g_free(file_dir->dir);
        g_free(file_dir);
    }
}

Project: menu-cache
File: menu-cache.c

GSList* menu_cache_dir_list_children(MenuCacheDir* dir)
{
    GSList *children, *l;

    if(MENU_CACHE_ITEM(dir)->type != MENU_CACHE_TYPE_DIR)
        return NULL;
    MENU_CACHE_LOCK;
    children = g_slist_copy(dir->children);
    for(l = children; l; l = l->next)
        menu_cache_item_ref(l->data);
    MENU_CACHE_UNLOCK;
    return children;
}

Project: menu-cache
File: menu-cache.c

MenuCache* menu_cache_ref(MenuCache* cache)
{
    g_atomic_int_inc( &cache->n_ref );
    return cache;
}

Project: menu-cache
File: menu-cache.c

MenuCacheItem* menu_cache_item_ref(MenuCacheItem* item)
{
    g_atomic_int_inc( &item->n_ref );
    /* DEBUG("item_ref %s: %d -> %d", item->id, item->n_ref-1, item->n_ref); */
    return item;
}

Project: menu-cache
File: menu-cache.c

void menu_cache_remove_reload_notify(MenuCache* cache, MenuCacheNotifyId notify_id)
{
    MENU_CACHE_LOCK;
    g_slice_free( CacheReloadNotifier, ((GSList*)notify_id)->data );
    cache->notifiers = g_slist_delete_link( cache->notifiers, (GSList*)notify_id );
    MENU_CACHE_UNLOCK;
}

Project: menu-cache
File: menu-cache.c

const char * const * menu_cache_app_get_categories(MenuCacheApp* app)
{
    return app->categories;
}

Project: menu-cache
File: menu-cache.c

char* menu_cache_dir_make_path( MenuCacheDir* dir )
{
    GString* path = g_string_sized_new(1024);
    MenuCacheItem* it;

    MENU_CACHE_LOCK;
    while( (it = MENU_CACHE_ITEM(dir)) ) /* this is not top dir */
    {
        g_string_prepend( path, menu_cache_item_get_id(it) );
        g_string_prepend_c( path, '/' );
        /* FIXME: if parent is already unref'd by another thread then
           path being made will be broken. Is there any way to avoid that? */
        dir = it->parent;
    }
    MENU_CACHE_UNLOCK;
    return g_string_free( path, FALSE );
}

Project: menu-cache
File: menu-cache.c

const char* menu_cache_item_get_comment( MenuCacheItem* item )
{
    return item->comment;
}

Project: menu-cache
File: menu-cache.c

MenuCacheItem *menu_cache_find_child_by_name(MenuCacheDir *dir, const char *name)
{
    GSList *child;
    MenuCacheItem *item = NULL;

    if (MENU_CACHE_ITEM(dir)->type != MENU_CACHE_TYPE_DIR || name == NULL)
        return NULL;
    MENU_CACHE_LOCK;
    for (child = dir->children; child; child = child->next)
        if (g_strcmp0(MENU_CACHE_ITEM(child->data)->name, name) == 0)
        {
            item = menu_cache_item_ref(child->data);
            break;
        }
    MENU_CACHE_UNLOCK;
    return item;
}

Project: menu-cache
File: menu-cache.c

MenuCacheItem* menu_cache_item_from_path( MenuCache* cache, const char* path )
{
    char** names = g_strsplit( path + 1, "/", -1 );
    int i;
    MenuCacheDir* dir;
    MenuCacheItem* item = NULL;

    if( !names )
        return NULL;

    if( G_UNLIKELY(!names[0]) )
    {
        g_strfreev(names);
        return NULL;
    }
    /* the topmost dir of the path should be the root menu dir. */
    MENU_CACHE_LOCK;
    dir = cache->root_dir;
    if( G_UNLIKELY(!dir) || strcmp(names[0], MENU_CACHE_ITEM(dir)->id) != 0 )
        goto _end;

    for( i = 1; names[i]; ++i )
    {
        GSList* l;
        item = NULL;
        if( !dir )
            break;
        l = dir->children;
        dir = NULL;
        for( ; l; l = l->next )
        {
            item = MENU_CACHE_ITEM(l->data);
            if( g_strcmp0( item->id, names[i] ) == 0 )
            {
                if( item->type == MENU_CACHE_TYPE_DIR )
                    dir = MENU_CACHE_DIR(item);
                break;
            }
            item = NULL;
        }
        if( !item )
            break;
    }
    if(item)
        menu_cache_item_ref(item);
_end:
    MENU_CACHE_UNLOCK;
    g_strfreev(names);
    return item;
}

Project: menu-cache
File: menu-cache.c

MenuCacheItem *menu_cache_find_item_by_id(MenuCache *cache, const char *id)
{
    MenuCacheItem *item = NULL;

    MENU_CACHE_LOCK;
    if (cache && id)
        item = _scan_by_id(MENU_CACHE_ITEM(cache->root_dir), id);
    if (item)
        menu_cache_item_ref(item);
    MENU_CACHE_UNLOCK;
    return item;
}

Project: menu-cache
File: menu-cached.c

static void terminate(int sig)
{
/* #ifndef HAVE_ABSTRACT_SOCKETS */
    unlink(socket_file);
    exit(0);
/* #endif */
}

Project: menu-cache
File: menu-cache.c

static gboolean register_menu_to_server(MenuCache* cache)
{
    ssize_t len = strlen(cache->reg);
    /* FIXME: do unblocking I/O */
    if(write(server_fd, cache->reg, len) < len)
    {
        DEBUG("register_menu_to_server: sending failed");
        return FALSE; /* socket write failed */
    }
    return TRUE;
}

Project: menu-cache
File: menu-cache.c

MenuCache* menu_cache_lookup_sync( const char* menu_name )
{
    MenuCache* mc = menu_cache_lookup(menu_name);
    MenuCacheDir* root_dir = menu_cache_dup_root_dir(mc);
    /* ensure that the menu cache is loaded */
    if(root_dir)
        menu_cache_item_unref(MENU_CACHE_ITEM(root_dir));
    else /* if it's not yet loaded */
    {
        MenuCacheNotifyId notify_id;
        /* add stub */
        notify_id = menu_cache_add_reload_notify(mc, NULL, NULL);
#if GLIB_CHECK_VERSION(2, 32, 0)
        g_mutex_lock(&sync_run_mutex);
        while(!mc->ready)
            g_cond_wait(&sync_run_cond, &sync_run_mutex);
        g_mutex_unlock(&sync_run_mutex);
#else
        g_mutex_lock(sync_run_mutex);
        g_debug("menu_cache_lookup_sync: enter wait %p", mc);
        while(!mc->ready)
            g_cond_wait(sync_run_cond, sync_run_mutex);
        g_debug("menu_cache_lookup_sync: leave wait");
        g_mutex_unlock(sync_run_mutex);
#endif
        menu_cache_remove_reload_notify(mc, notify_id);
    }
    return mc;
}

Project: menu-cache
File: menu-cache.c

gboolean menu_cache_item_unref(MenuCacheItem* item)
{
    /* DEBUG("item_unref(%s): %d", item->id, item->n_ref); */
    /* We need a lock here unfortunately since another thread may have access
       to it via some child->parent which isn't protected by reference */
    MENU_CACHE_LOCK; /* lock may be recursive here */
    if( g_atomic_int_dec_and_test( &item->n_ref ) )
    {
        /* DEBUG("free item: %s", item->id); */
        g_free( item->id );
        g_free( item->name );
        g_free( item->comment );
        g_free( item->icon );

        menu_cache_file_dir_unref(item->file_dir);

        if( item->file_name && item->file_name != item->id )
            g_free( item->file_name );

        if( item->parent )
        {
            /* DEBUG("remove %s from parent %s", item->id, MENU_CACHE_ITEM(item->parent)->id); */
            /* remove ourselve from the parent node. */
            item->parent->children = g_slist_remove(item->parent->children, item);
        }

        if( item->type == MENU_CACHE_TYPE_DIR )
        {
            MenuCacheDir* dir = MENU_CACHE_DIR(item);
            GSList* l;
            for(l = dir->children; l; )
            {
                MenuCacheItem* child = MENU_CACHE_ITEM(l->data);
                /* remove ourselve from the children. */
                child->parent = NULL;
                l = l->next;
                menu_cache_item_unref(child);
            }
            g_slist_free( dir->children );
            g_slice_free( MenuCacheDir, dir );
        }
        else
        {
            MenuCacheApp* app = MENU_CACHE_APP(item);
            g_free( app->exec );
            g_free(app->try_exec);
            g_free(app->working_dir);
            g_free(app->categories);
            g_free(app->keywords);
            g_slice_free( MenuCacheApp, app );
        }
    }
    MENU_CACHE_UNLOCK;
    return FALSE;
}

Project: menu-cache
File: menu-cached.c

static gboolean delayed_cache_free(gpointer data)
{
    Cache* cache = data;
    int i;

    if(g_source_is_destroyed(g_main_current_source()))
        return FALSE;

    g_hash_table_remove( hash, cache->md5 );
    /* DEBUG("menu cache freed"); */
    for(i = 0; i < cache->n_files; ++i)
    {
        g_file_monitor_cancel( cache->mons[i] );
        g_object_unref( cache->mons[i] );
    }
/*
    g_file_monitor_cancel(cache->cache_mon);
    g_object_unref(cache->cache_mon);
*/
    g_free( cache->mons );
    g_free(cache->menu_name);
    g_free(cache->lang_name);
    g_free(cache->cache_file);
    g_strfreev( cache->env );
    g_strfreev( cache->files );

    if( cache->delayed_reload_handler )
        g_source_remove( cache->delayed_reload_handler );

    g_slice_free( Cache, cache );

    if(g_hash_table_size(hash) == 0)
        g_main_loop_quit(main_loop);
    return FALSE;
}

Project: menu-cache
File: menu-cache.c

MenuCacheNotifyId menu_cache_add_reload_notify(MenuCache* cache, MenuCacheReloadNotify func, gpointer user_data)
{
    GSList* l = g_slist_alloc();
    CacheReloadNotifier* n = g_slice_new(CacheReloadNotifier);
    gboolean is_first;
    n->func = func;
    n->user_data = user_data;
    l->data = n;
    MENU_CACHE_LOCK;
    is_first = (cache->root_dir == NULL && cache->notifiers == NULL);
    cache->notifiers = g_slist_concat( cache->notifiers, l );
    /* reload existing file first so it will be ready right away */
    if(is_first && cache->reload_id == 0)
        cache->reload_id = g_idle_add_full(G_PRIORITY_HIGH_IDLE,
                                           menu_cache_reload_idle,
                                           menu_cache_ref(cache),
                                           (GDestroyNotify)menu_cache_unref);
    MENU_CACHE_UNLOCK;
    return (MenuCacheNotifyId)l;
}

Project: menu-cache
File: menu-cache.c

char* menu_cache_item_get_file_path( MenuCacheItem* item )
{
    if( ! item->file_name || ! item->file_dir )
        return NULL;
    return g_build_filename( item->file_dir->dir + 1, item->file_name, NULL );
}

Project: menu-cache
File: menu-cache.c

static GSList* list_app_in_dir_for_cat(MenuCacheDir *dir, GSList *list, const char *id)
{
    const char **cat;
    GSList *l;

    for (l = dir->children; l; l = l->next)
    {
        MenuCacheItem *item = MENU_CACHE_ITEM(l->data);
        switch (item->type)
        {
        case MENU_CACHE_TYPE_DIR:
            list = list_app_in_dir_for_cat(MENU_CACHE_DIR(item), list, id);
            break;
        case MENU_CACHE_TYPE_APP:
            cat = MENU_CACHE_APP(item)->categories;
            if (cat) while (*cat)
                if (*cat++ == id)
                {
                    list = g_slist_prepend(list, menu_cache_item_ref(item));
                    break;
                }
            break;
        case MENU_CACHE_TYPE_NONE:
        case MENU_CACHE_TYPE_SEP:
            break;
        }
    }
    return list;
}

Project: menu-cache
File: menu-cache.c

static inline char *_validate_env(const char *env)
{
    char *res, *c;

    if (env)
        res = g_strdup(env);
    else
        res = g_strdup("");
    for (c = res; *c; c++)
        if (*c == '\n' || *c == '\t')
            *c = ' ';
    return res;
}

Project: menu-cache
File: menu-cache.c

gboolean menu_cache_reload( MenuCache* cache )
{
    char* line;
    gsize len;
    GFile* file;
    GFileInputStream* istr = NULL;
    GDataInputStream* f;
    MenuCacheFileDir** all_used_files;
    int i, n;
    int ver_maj, ver_min;

    MENU_CACHE_LOCK;
    if (cache->reload_id)
        g_source_remove(cache->reload_id);
    cache->reload_id = 0;
    MENU_CACHE_UNLOCK;
    file = g_file_new_for_path(cache->cache_file);
    if(!file)
        return FALSE;
    istr = g_file_read(file, cache->cancellable, NULL);
    g_object_unref(file);
    if(!istr)
        return FALSE;
    f = g_data_input_stream_new(G_INPUT_STREAM(istr));
    g_object_unref(istr);
    if( ! f )
        return FALSE;

    /* the first line is version number */
    line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
    if(G_LIKELY(line))
    {
        len = sscanf(line, "%d.%d", &ver_maj, &ver_min);
        g_free(line);
        if(len < 2)
            goto _fail;
        if( ver_maj != VER_MAJOR ||
            ver_min > VER_MINOR || ver_min < VER_MINOR_SUPPORTED )
            goto _fail;
    }
    else
        goto _fail;

    g_debug("menu cache: got file version 1.%d", ver_min);
    /* the second line is menu name */
    line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
    if(G_UNLIKELY(line == NULL))
        goto _fail;
    g_free(line);

    /* FIXME: this may lock other threads for some time */
    MENU_CACHE_LOCK;
    if(cache->notifiers == NULL)
    {
        /* nobody aware of reloads, stupid clients may think root is forever */
        MENU_CACHE_UNLOCK;
        goto _fail;
    }

    /* get all used files */
    n = read_all_used_files( f, cache, &all_used_files );
    if (n <= 0)
    {
        MENU_CACHE_UNLOCK;
        goto _fail;
    }

    /* read known DEs */
    g_strfreev( cache->known_des );
    if( ! read_all_known_des( f, cache ) )
    {
        cache->known_des = NULL;
        MENU_CACHE_UNLOCK;
        for (i = 0; i < n; i++)
            menu_cache_file_dir_unref(all_used_files[i]);
        g_free(all_used_files);
_fail:
        g_object_unref(f);
        return FALSE;
    }
    cache->version = ver_min;

    if(cache->root_dir)
        menu_cache_item_unref( MENU_CACHE_ITEM(cache->root_dir) );

    cache->root_dir = (MenuCacheDir*)read_item( f, cache, all_used_files, n );
    g_object_unref(f);

    g_idle_add_full(G_PRIORITY_HIGH_IDLE, reload_notify, menu_cache_ref(cache),
                    (GDestroyNotify)menu_cache_unref);
    MENU_CACHE_UNLOCK;

    for (i = 0; i < n; i++)
        menu_cache_file_dir_unref(all_used_files[i]);
    g_free(all_used_files);

    return TRUE;
}

Project: menu-cache
File: menu-cached.c

static gboolean delayed_reload( Cache* cache )
{
    if(g_source_is_destroyed(g_main_current_source()))
        return FALSE;

    if(cache->need_reload)
        do_reload(cache);

    if (cache->need_reload)
      return TRUE;

    cache->delayed_reload_handler = 0;
    return FALSE;
}

Project: menu-cache
File: menu-cache.c

gboolean menu_cache_app_get_use_terminal( MenuCacheApp* app )
{
    return ( (app->flags & FLAG_USE_TERMINAL) != 0 );
}

Project: menu-cache
File: menu-cache.c

const char* menu_cache_item_get_id( MenuCacheItem* item )
{
    return item->id;
}

Project: menu-cache
File: menu-cache.c

MenuCacheDir* menu_cache_item_get_parent( MenuCacheItem* item )
{
    MenuCacheDir* dir = menu_cache_item_dup_parent(item);
    /* NOTE: this is very ugly hack but parent may be changed by item freeing
       so we should keep it alive :( */
    if(dir)
        g_timeout_add_seconds(10, (GSourceFunc)menu_cache_item_unref, dir);
    return dir;
}

Project: menu-cache
File: menu-cache.c

static char *_unescape_lf(char *str)
{
    char *c, *p = str;
    gsize len = 0;

    while ((c = strchr(p, '\\')) != NULL)
    {
        if (p != &str[len])
            memmove(&str[len], p, c - p);
        len += (c - p);
        if (c[1] == 'n')
        {
            str[len++] = '\n';
            c++;
        }
        else if (c != &str[len])
            str[len++] = *c;
        p = &c[1];
    }
    if (p != &str[len])
        memmove(&str[len], p, strlen(p) + 1);
    return str;
}

Project: menu-cache
File: menu-cache.c

guint32 menu_cache_app_get_show_flags( MenuCacheApp* app )
{
    return app->show_in_flags;
}

Project: menu-cache
File: menu-cached.c

static void on_client_closed(gpointer user_data)
{
    ClientIO* client_io = user_data;
    GHashTableIter it;
    char* md5;
    Cache* cache;
    GSList *l;
    GIOChannel *ch = client_io->channel;

    DEBUG("client closed: %p", ch);
    g_hash_table_iter_init (&it, hash);
    while( g_hash_table_iter_next (&it, (gpointer*)&md5, (gpointer*)&cache) )
    {
        while((l = g_slist_find( cache->clients, client_io )) != NULL)
        {
            /* FIXME: some clients are closed accidentally without
             * unregister the menu first due to crashes.
             * We need to do unref for them to prevent memory leaks.
             *
             * The behavior is not currently well-defined.
             * if a client do unregister first, and then was closed,
             * unref will be called twice and incorrect ref. counting
             * will happen.
             */
            cache->clients = g_slist_delete_link( cache->clients, l );
            DEBUG("remove channel from cache %p", cache);
            if(cache->clients == NULL)
                cache_free(cache);
        }
    }
    /* DEBUG("client closed"); */

    g_source_remove(client_io->source_id);
    g_free(client_io);
}

Project: menu-cache
File: menu-cache.c

void menu_cache_unref(MenuCache* cache)
{
    /* DEBUG("cache_unref: %d", cache->n_ref); */
    /* we need a lock here unfortunately because item in hash isn't protected
       by reference therefore another thread may get access to it right now */
    MENU_CACHE_LOCK;
    if( g_atomic_int_dec_and_test(&cache->n_ref) )
    {
        /* g_assert(cache->reload_id != 0); */
        unregister_menu_from_server( cache );
        /* DEBUG("unregister to server"); */
        g_hash_table_remove( hash, cache->menu_name );
        if( g_hash_table_size(hash) == 0 )
        {
            /* DEBUG("destroy hash"); */
            g_hash_table_destroy(hash);

            /* DEBUG("disconnect from server"); */
            G_LOCK(connect);
            shutdown(server_fd, SHUT_RDWR); /* the IO thread will terminate itself */
            server_fd = -1;
            G_UNLOCK(connect);
            hash = NULL;
        }
        MENU_CACHE_UNLOCK;

        if(G_LIKELY(cache->thr))
        {
            g_cancellable_cancel(cache->cancellable);
            g_thread_join(cache->thr);
        }
        g_object_unref(cache->cancellable);
        if( G_LIKELY(cache->root_dir) )
        {
            /* DEBUG("unref root dir"); */
            menu_cache_item_unref( MENU_CACHE_ITEM(cache->root_dir) );
            /* DEBUG("unref root dir finished"); */
        }
        g_free( cache->cache_file );
        g_free( cache->menu_name );
        g_free(cache->reg);
        /* g_free( cache->menu_file_path ); */
        g_strfreev(cache->known_des);
        g_slist_free(cache->notifiers);
        g_slice_free( MenuCache, cache );
    }
    else
        MENU_CACHE_UNLOCK;
}

Project: menu-cache
File: menu-cache.c

static void read_dir(GDataInputStream* f, MenuCacheDir* dir, MenuCache* cache,
                     MenuCacheFileDir** all_used_files, int n_all_used_files)
{
    MenuCacheItem* item;
    char *line;
    gsize len;

    /* nodisplay flag */
    if (cache->version >= 2)
    {
        line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
        if (G_UNLIKELY(line == NULL))
            return;
        dir->flags = (guint32)atoi(line);
        g_free(line);
    }

    /* load child items in the dir */
    while( (item = read_item( f, cache, all_used_files, n_all_used_files )) )
    {
        /* menu_cache_ref shouldn't be called here for dir.
         * Otherwise, circular reference will happen. */
        item->parent = dir;
        dir->children = g_slist_prepend( dir->children, item );
    }

    dir->children = g_slist_reverse( dir->children );

    /* set flag by children if working with old cache generator */
    if (cache->version == 1)
    {
        if (dir->children == NULL)
            dir->flags = FLAG_IS_NODISPLAY;
        else if ((line = menu_cache_item_get_file_path(MENU_CACHE_ITEM(dir))) != NULL)
        {
            GKeyFile *kf = g_key_file_new();
            if (g_key_file_load_from_file(kf, line, G_KEY_FILE_NONE, NULL) &&
                g_key_file_get_boolean(kf, G_KEY_FILE_DESKTOP_GROUP,
                                       G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY, NULL))
                dir->flags = FLAG_IS_NODISPLAY;
            g_key_file_free(kf);
            g_free(line);
        }
    }
}

Project: menu-cache
File: menu-cache.c

GSList *menu_cache_list_all_for_category(MenuCache* cache, const char *category)
{
    GQuark q;
    GSList *list;

    g_return_val_if_fail(cache != NULL && category != NULL, NULL);
    q = g_quark_try_string(category);
    if (q == 0)
        return NULL;
    MENU_CACHE_LOCK;
    if (G_UNLIKELY(cache->root_dir == NULL))
        list = NULL;
    else
        list = list_app_in_dir_for_cat(cache->root_dir, NULL, g_quark_to_string(q));
    MENU_CACHE_UNLOCK;
    return list;
}

Project: menu-cache
File: menu-cache.c

const char* menu_cache_app_get_exec( MenuCacheApp* app )
{
    return app->exec;
}

Project: menu-cache
File: menu-cache.c

void menu_cache_init(int flags)
{
#if !GLIB_CHECK_VERSION(2, 36, 0)
    g_type_init();
#endif
}

Project: menu-cache
File: menu-cache.c

static void read_app(GDataInputStream* f, MenuCacheApp* app, MenuCache* cache)
{
    char *line;
    gsize len;
    GString *str;

    /* generic name */
    line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
    if(G_UNLIKELY(line == NULL))
        return;
    if(G_LIKELY(len > 0))
        app->generic_name = _unescape_lf(line);
    else
        g_free(line);

    /* exec */
    line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
    if(G_UNLIKELY(line == NULL))
        return;
    if(G_LIKELY(len > 0))
        app->exec = _unescape_lf(line);
    else
        g_free(line);

    /* terminal / startup notify */
    line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
    if(G_UNLIKELY(line == NULL))
        return;
    app->flags = (guint32)atoi(line);
    g_free(line);

    /* ShowIn flags */
    line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
    if(G_UNLIKELY(line == NULL))
        return;
    app->show_in_flags = (guint32)atol(line);
    g_free(line);

    if (cache->version < 2)
        return;

    /* TryExec */
    line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
    if (G_UNLIKELY(line == NULL))
        return;
    if (G_LIKELY(len > 0))
        app->try_exec = g_strchomp(line);
    else
        g_free(line);

    /* Path */
    line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
    if (G_UNLIKELY(line == NULL))
        return;
    if (G_LIKELY(len > 0))
        app->working_dir = line;
    else
        g_free(line);

    /* Categories */
    line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
    if (G_UNLIKELY(line == NULL))
        return;
    if (G_LIKELY(len > 0))
    {
        const char **x;

        /* split and intern all the strings so categories can be processed
           later for search doing g_quark_try_string()+g_quark_to_string() */
        app->categories = x = (const char **)g_strsplit(line, ";", 0);
        while (*x != NULL)
        {
            char *cat = (char *)*x;
            *x = g_intern_string(cat);
            g_free(cat);
            x++;
        }
    }
    g_free(line);

    /* Keywords */
    str = g_string_new(MENU_CACHE_ITEM(app)->name);
    if (G_LIKELY(app->exec != NULL))
    {
        char *sp = strchr(app->exec, ' ');
        char *bn = strrchr(app->exec, G_DIR_SEPARATOR);

        g_string_append_c(str, ',');
        if (bn == NULL && sp == NULL)
            g_string_append(str, app->exec);
        else if (bn == NULL || (sp != NULL && sp < bn))
            g_string_append_len(str, app->exec, sp - app->exec);
        else if (sp == NULL)
            g_string_append(str, &bn[1]);
        else
            g_string_append_len(str, &bn[1], sp - &bn[1]);
    }
    if (app->generic_name != NULL)
    {
        g_string_append_c(str, ',');
        g_string_append(str, app->generic_name);
    }
    line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
    if (G_UNLIKELY(line == NULL))
        return;
    if (len > 0)
    {
        g_string_append_c(str, ',');
        g_string_append(str, line);
    }
    app->keywords = g_utf8_casefold(str->str, str->len);
    g_string_free(str, TRUE);
    g_free(line);
}

Project: menu-cache
File: menu-cache.c

static MenuCache* menu_cache_new( const char* cache_file )
{
    MenuCache* cache;
    cache = g_slice_new0( MenuCache );
    cache->cache_file = g_strdup( cache_file );
    cache->n_ref = 1;
    return cache;
}

Project: menu-cache
File: menu-cached.c

static gboolean on_new_conn_incoming(GIOChannel* ch, GIOCondition cond, gpointer user_data)
{
    int server, client;
    socklen_t client_addrlen;
    struct sockaddr client_addr;
    GIOChannel* child;
    ClientIO* client_io;

    server = g_io_channel_unix_get_fd(ch);

    client_addrlen = sizeof(client_addr);
    client = accept(server, &client_addr, &client_addrlen);
    if( client == -1 )
    {
        DEBUG("accept error");
        return TRUE;
    }

    child = g_io_channel_unix_new(client);
    g_io_channel_set_close_on_unref( child, TRUE );

    client_io = g_new0 (ClientIO, 1);
    client_io->channel = child;
    client_io->source_id =
        g_io_add_watch(child, G_IO_PRI|G_IO_IN|G_IO_HUP|G_IO_ERR,
                        on_client_data_in, client_io);
    g_io_channel_unref(child);

    DEBUG("new client accepted: %p", child);
    return TRUE;
}

Project: menu-cache
File: menu-cached.c

static gboolean cache_file_is_updated( const char* cache_file, int* n_used_files, char*** used_files )
{
    gboolean ret = FALSE;
    struct stat st;
#if 0
    time_t cache_mtime;
    char** files;
    int n, i;
#endif
    FILE* f;

    f = fopen( cache_file, "r" );
    if( f )
    {
        if( fstat( fileno(f), &st) == 0 )
        {
#if 0
            cache_mtime = st.st_mtime;
            if( read_all_used_files(f, &n, &files) )
            {
                for( i =0; i < n; ++i )
                {
                    /* files[i][0] is 'D' or 'F' indicating file type. */
                    if( stat( files[i] + 1, &st ) == -1 )
                        continue;
                    if( st.st_mtime > cache_mtime )
                        break;
                }
                if( i >= n )
                {
                    ret = TRUE;
                    *n_used_files = n;
                    *used_files = files;
                }
            }
#else
            ret = read_all_used_files(f, n_used_files, used_files);
#endif
        }
        fclose( f );
    }
    return ret;
}

Project: menu-cache
File: menu-cached.c

int main(int argc, char** argv)
{
    GIOChannel* ch;
    int server_fd;
    struct sockaddr_un addr;
#ifndef DISABLE_DAEMONIZE
    int fd, pid;

    long open_max;
    long i;
#endif

    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    if (argc < 2)
        /* old way, not recommended! */
        get_socket_name( addr.sun_path, sizeof( addr.sun_path ) );
    else if (strlen(argv[1]) >= sizeof(addr.sun_path))
        /* ouch, it's too big! */
        return 1;
    else
        /* this is a good way! */
        strncpy(addr.sun_path, argv[1], sizeof(addr.sun_path) - 1);

    socket_file = addr.sun_path;

    server_fd = create_socket(&addr);

    if( server_fd < 0 )
        return 1;

#ifndef DISABLE_DAEMONIZE
    /* Become a daemon */
    if ((pid = fork()) < 0) {
        g_error("can't fork");
    }
    else if (pid != 0) {
        /* exit parent */
        exit(0);
    }

    /* reset session to forget about parent process completely */
    setsid();

    /* change working directory to root, so previous working directory
     * can be unmounted */
    if (chdir("/") < 0) {
        g_error("can't change directory to /");
    }

    open_max = sysconf (_SC_OPEN_MAX);
    for (i = 0; i < open_max; i++)
    {
        /* don't hold open fd opened besides server socket */
        if (i != server_fd)
            fcntl (i, F_SETFD, FD_CLOEXEC);
    }

    /* /dev/null for stdin, stdout, stderr */
    fd = open ("/dev/null", O_RDONLY);
    if (fd != -1)
    {
        dup2 (fd, 0);
        close (fd);
    }
    fd = open ("/dev/null", O_WRONLY);
    if (fd != -1)
    {
        dup2 (fd, 1);
        dup2 (fd, 2);
        close (fd);
    }
#endif

    signal(SIGHUP, terminate);
    signal(SIGINT, terminate);
    signal(SIGQUIT, terminate);
    signal(SIGTERM, terminate);
    signal(SIGPIPE, SIG_IGN);

    ch = g_io_channel_unix_new(server_fd);
    if(!ch)
        return 1;
    g_io_add_watch(ch, G_IO_IN|G_IO_PRI, on_new_conn_incoming, NULL);
    g_io_add_watch(ch, G_IO_ERR, on_server_conn_close, NULL);
    g_io_channel_unref(ch);

#if !GLIB_CHECK_VERSION(2, 36, 0)
    g_type_init();
#endif

    hash = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);

    main_loop = g_main_loop_new( NULL, TRUE );
    g_main_loop_run( main_loop );
    g_main_loop_unref( main_loop );

    unlink(addr.sun_path);

    g_hash_table_destroy(hash);
    return 0;
}

Project: menu-cache
File: menu-cache.c

GSList* menu_cache_list_all_apps(MenuCache* cache)
{
    GSList* list;
    MENU_CACHE_LOCK;
    if (G_UNLIKELY(!cache->root_dir)) /* empty cache */
        list = NULL;
    else
        list = list_app_in_dir(cache->root_dir, NULL);
    MENU_CACHE_UNLOCK;
    return list;
}

Project: menu-cache
File: menu-cache.c

const char* menu_cache_app_get_generic_name( MenuCacheApp* app )
{
	return app->generic_name;
}

Project: menu-cache
File: menu-cache.c

static void unregister_menu_from_server( MenuCache* cache )
{
    char buf[38];
    g_snprintf( buf, 38, "UNR:%s\n", cache->md5 );
    /* FIXME: do unblocking I/O */
    if(write( server_fd, buf, 37 ) <= 0)
    {
        DEBUG("unregister_menu_from_server: sending failed");
    }
}

Project: menu-cache
File: menu-cache.c

const char* menu_cache_item_get_icon( MenuCacheItem* item )
{
    return item->icon;
}

Project: menu-cache
File: menu-cache.c

static gboolean menu_cache_reload_idle(gpointer cache)
{
    /* do reload once */
    if (!g_source_is_destroyed(g_main_current_source()))
        menu_cache_reload(cache);
    return FALSE;
}