/**
* @file fileusers.c
*
* Mode Control Entity - Get users of evdev input nodes
*
*
*
* Copyright (C) 2015 Jolla Ltd.
*
*
*
* @author Simo Piiroinen
*
* mce is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* mce 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with mce. If not, see .
*/
#include "fileusers.h"
#include "../mce-log.h"
#include
#include
#include
#include
#include
#include
#include
/* ========================================================================= *
* TYPES & FUNCTIONS
* ========================================================================= */
/* ------------------------------------------------------------------------- *
* GENERIC_UTILS
* ------------------------------------------------------------------------- */
static void *fu_malloc (size_t size);
static void *fu_calloc (size_t nmemb, size_t size);
static char *fu_strdup (const char *str);
static bool fu_ascii_digit_p (int ch);
static char *fu_read_content (const char *path);
static char *fu_get_command_name (int pid);
/* ------------------------------------------------------------------------- *
* FILEUSER_OBJS
* ------------------------------------------------------------------------- */
static fileuser_t *fileuser_create (const char *path, const char *cmd, int pid, int fd);
static void fileuser_delete (fileuser_t *self);
static void fileuser_delete_cb (void *self);
/* ------------------------------------------------------------------------- *
* MODULE_API
* ------------------------------------------------------------------------- */
static void fileusers_scan_pid_files (int pid);
static void fileusers_scan_pids (void);
GSList *fileusers_get (const char *path);
void fileusers_init (void);
void fileusers_quit (void);
/* ========================================================================= *
* GENERIC_UTILS
* ========================================================================= */
/** Do or die malloc() helper
*/
static void *fu_malloc(size_t size)
{
void *res = malloc(size);
if( !res && size )
abort();
return res;
}
/** Do or die calloc() helper
*/
static void *fu_calloc(size_t nmemb, size_t size)
{
void *res = calloc(nmemb, size);
if( !res && nmemb && size )
abort();
return res;
}
/** Do or die strdup() helper
*/
static char *fu_strdup(const char *str)
{
char *res = str ? strdup(str) : 0;
if( !res && str )
abort();
return res;
}
/** Ascii digit character predicate function
*/
static bool
fu_ascii_digit_p(int ch)
{
return '0' <= ch && ch <= '9';
}
/** Read file content as string
*
* Note: Content beyond 4k in size is ignored.
*/
static char *
fu_read_content(const char *path)
{
char *res = 0;
int fd = -1;
if( (fd = open(path, O_RDONLY)) == -1 )
goto EXIT;
char buf[4<<10];
int rc = read(fd, buf, sizeof buf);
if( rc < 0 )
goto EXIT;
res = fu_malloc(rc + 1);
memcpy(res, buf, rc);
res[rc] = 0;
EXIT:
if( fd != -1 )
close(fd);
return res;
}
/** Use heuristics to derive command name for process identifier
*/
static char *
fu_get_command_name(int pid)
{
char *res = 0;
char path[256];
snprintf(path, sizeof path, "/proc/%d/cmdline", pid);
char *tmp = fu_read_content(path);
if( tmp )
res = fu_strdup(basename(tmp));
free(tmp);
return res ?: fu_strdup("unknown");
}
/* ========================================================================= *
* FILEUSER_OBJS
* ========================================================================= */
/** Create fileuser object
*/
static fileuser_t *
fileuser_create(const char *path, const char *cmd, int pid, int fd)
{
fileuser_t *self = fu_calloc(1, sizeof *self);
self->path = fu_strdup(path ?: "unknown");
self->cmd = fu_strdup(cmd ?: "unknown");
self->pid = pid;
self->fd = fd;
return self;
}
/** Delete fileuser object
*/
static void
fileuser_delete(fileuser_t *self)
{
if( self ) {
free(self->cmd);
free(self->path);
free(self);
}
}
/** Type agnostic fileuser object delete function for use as a callback
*/
static void
fileuser_delete_cb(void *self)
{
fileuser_delete(self);
}
/* ========================================================================= *
* MODULE_API
* ========================================================================= */
/** Cache of evdev input files that at least one process has open */
static GSList *fileusers_list = 0;
/** Scan evdev input files that a process has open
*/
static void
fileusers_scan_pid_files(int pid)
{
static const char pfix_str[] = "/dev/input/event";
static const size_t pfix_len = sizeof pfix_str - 1;
DIR *dir = 0;
char *cmd = 0;
char base[256];
snprintf(base, sizeof base, "/proc/%d/fd", pid);
if( !(dir = opendir(base)) ) {
mce_log(LL_WARN, "%s: can't scan dir: %m", base);
goto EXIT;
}
struct dirent *de;
int errno_prev = 0;
while( (de = readdir(dir)) ) {
if( de->d_type != DT_LNK )
continue;
if( !fu_ascii_digit_p(*de->d_name) )
continue;
char srce[256];
snprintf(srce, sizeof srce, "%s/%s", base, de->d_name);
char dest[256];
ssize_t rc = readlink(srce, dest, sizeof dest - 1);
if( rc < 0 ) {
if( errno_prev != errno ) {
errno_prev = errno;
mce_log(LL_WARN, "%s: can't read link: %m", srce);
}
continue;
}
if( rc > (int)sizeof dest - 1 )
continue;
if( rc < (int)pfix_len )
continue;
if( memcmp(dest, pfix_str, pfix_len) )
continue;
dest[rc] = 0;
if( !cmd )
cmd = fu_get_command_name(pid);
int fd = strtol(de->d_name, 0, 0);
fileuser_t *fu = fileuser_create(dest, cmd, pid, fd);
fileusers_list = g_slist_prepend(fileusers_list, fu);
}
EXIT:
free(cmd);
if( dir )
closedir(dir);
}
/** Scan processes that might have evdev input files open
*/
static void
fileusers_scan_pids(void)
{
DIR *dir = 0;
if( !(dir = opendir("/proc")) ) {
mce_log(LL_WARN, "%s: can't scan dir: %m", "/proc");
goto EXIT;
}
struct dirent *de;
while( (de = readdir(dir)) ) {
if( de->d_type != DT_DIR )
continue;
if( !fu_ascii_digit_p(*de->d_name) )
continue;
int pid = strtol(de->d_name, 0, 0);
fileusers_scan_pid_files(pid);
}
EXIT:
if( dir )
closedir(dir);
}
/** Initialize open-evdev-files cache
*/
void
fileusers_init(void)
{
fileusers_scan_pids();
}
/** Flush open-evdev-files cache
*/
void
fileusers_quit(void)
{
g_slist_free_full(fileusers_list, fileuser_delete_cb),
fileusers_list = 0;
}
/** Get a list of open files for an evdev input file
*
* Note: While the entries in the returned list must not
* be freed or modified, the returned list itself
* must be released with g_slist_free().
*
* @param path Canonical path to an evdev input device node
*
* @return list of fileuser objects; or NULL
*/
GSList *
fileusers_get(const char *path)
{
GSList *list = 0;
if( !path )
goto EXIT;
for( GSList *item = fileusers_list; item; item = item->next ) {
fileuser_t *fu = item->data;
if( !fu->path )
continue;
if( !strcmp(fu->path, path) )
list = g_slist_prepend(list, fu);
}
EXIT:
return list;
}