/***************************************************************************
* CVSID: $Id$
*
* device_store.c : Search for .fdi files and merge on match
*
* Copyright (C) 2003 David Zeuthen, <david@fubar.dk>
*
* Licensed under the Academic Free License version 2.1
*
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <expat.h>
#include <assert.h>
#include <dbus/dbus-glib.h>
#include <math.h>
#include "hald.h"
#include "logger.h"
#include "device_info.h"
#include "device_store.h"
#include "util.h"
/**
* @defgroup DeviceInfo Device Info File Parsing
* @ingroup HalDaemon
* @brief Parsing of device info files
* @{
*/
/** Maximum nesting depth */
/** Maximum amount of CDATA */
/** Max length of property key */
/** Possible elements the parser can process */
enum {
/** Not processing a known tag */
/** Processing a deviceinfo element */
CURELEM_DEVICE_INFO = 0,
/** Processing a device element */
/** Processing a match element */
/** Processing a merge element */
/** Processing an append element */
/** Processing a prepend element */
/** Processing a remove element */
/** Processing a clear element */
/** Processing a spawn element */
};
/** What and how to merge */
enum {
MERGE_TYPE_UNKNOWN = 0,
};
/** Parsing Context
*/
typedef struct {
/** Name of file being parsed */
char *file;
/** Parser object */
/** Device we are trying to match*/
/** Buffer to put CDATA in */
/** Current length of CDATA buffer */
int cdata_buf_len;
/** Current depth we are parsing at */
int depth;
/** Element currently being processed */
int curelem;
/** Stack of elements being processed */
/** #TRUE if parsing of document have been aborted */
/** Depth of match-fail */
/** #TRUE if all matches on prior depths have been OK */
/** When merging, the key to store the value in */
/** Type to merge*/
int merge_type;
/** Set to #TRUE if a device is matched */
/** Resolve a udi-property path as used in .fdi files.
*
* Examples of udi-property paths:
*
* info.udi
* /org/freedesktop/Hal/devices/computer:kernel.name
* @block.storage_device:storage.bus
*
* @param source_udi UDI of source device
* @param path The given path
* @param udi_result Where to store the resulting UDI
* @param udi_result_size Size of UDI string
* @param prop_result Where to store the resulting property name
* @param prop_result_size Size of property string
* @return TRUE if and only if the path resolved.
*/
static gboolean
{
int i;
/*HAL_INFO (("Looking at '%s' for udi='%s'", path, source_udi));*/
/* Split up path into ':' tokens */
/* Detect trivial property access, e.g. path='foo.bar' */
goto out;
}
/* Start with the source udi */
HalDevice *d;
/*HAL_INFO (("tokens[%d] = '%s'", i, tokens[i]));*/
if (d == NULL)
if (d == NULL)
goto out;
/* process all but the last tokens as UDI paths */
goto out;
}
/* Check for indirection */
if (curtoken[0] == '@') {
const char *udiprop;
const char *newudi;
goto out;
/*HAL_INFO (("new_udi = '%s' (from indirection)", newudi));*/
} else {
/*HAL_INFO (("new_udi = '%s'", curtoken));*/
}
}
out:
/*
HAL_INFO (("success = '%s'", rc ? "yes" : "no"));
HAL_INFO (("udi_result = '%s'", udi_result));
HAL_INFO (("prop_result = '%s'", prop_result));
*/
g_strfreev (tokens);
return rc;
}
/* Compare the value of a property on a hal device object against a string value
* and return the result. Note that this works for several types, e.g. both strings
* and integers - in the latter case the given right side string will be interpreted
* as a number.
*
* The comparison might not make sense if you are comparing a property which is an integer
* against a string in which case this function returns FALSE. Also, if the property doesn't
* exist this function will also return FALSE.
*
* @param d hal device object
* @param key Key of the property to compare
* @param right_side Value to compare against
* @param result Pointer to where to store result
* @return TRUE if, and only if, the comparison could take place
*/
static gboolean
match_compare_property (HalDevice *d, const char *key, const char *right_side, dbus_int64_t *result)
{
int proptype;
if (!hal_device_has_property (d, key))
goto out;
switch (proptype) {
case HAL_PROPERTY_TYPE_STRING:
break;
case HAL_PROPERTY_TYPE_INT32:
break;
case HAL_PROPERTY_TYPE_UINT64:
*result = ((dbus_int64_t) hal_device_property_get_uint64 (d, key)) - ((dbus_int64_t) strtoll (right_side, NULL, 0));
break;
case HAL_PROPERTY_TYPE_DOUBLE:
break;
default:
/* explicit fallthrough */
/* explicit blank since this doesn't make sense */
break;
}
out:
return rc;
}
/** Called when the match element begins.
*
* @param pc Parsing context
* @return #FALSE if the device in question didn't
* match the data in the attributes
*/
static dbus_bool_t
{
const char *key;
int num_attrib;
HalDevice *d;
if (num_attrib != 4)
return FALSE;
return FALSE;
if (!resolve_udiprop_path (key,
udi_to_check, sizeof (udi_to_check),
prop_to_check, sizeof (prop_to_check))) {
return FALSE;
}
if (d == NULL) {
}
if (d == NULL) {
return FALSE;
}
const char *value;
/* match string property */
/*HAL_INFO(("Checking that key='%s' is a string that "
"equals '%s'", key, value)); */
return FALSE;
value) != 0)
return FALSE;
/*HAL_INFO (("*** string match for key %s", key));*/
return TRUE;
/* match integer property */
/** @todo Check error condition */
/*HAL_INFO (("Checking that key='%s' is a int that equals %d",
key, value));*/
return FALSE;
return FALSE;
}
return TRUE;
/* match integer property */
/** @todo Check error condition */
/*HAL_INFO (("Checking that key='%s' is a int that equals %d",
key, value));*/
return FALSE;
return FALSE;
}
return TRUE;
/* match string property */
else
return FALSE;
/*HAL_INFO (("Checking that key='%s' is a bool that equals %s",
key, value ? "TRUE" : "FALSE"));*/
if (hal_device_property_get_type (d, prop_to_check) !=
return FALSE;
return FALSE;
/*HAL_INFO (("*** bool match for key %s", key));*/
return TRUE;
if (should_exist) {
if (hal_device_has_property (d, prop_to_check))
return TRUE;
else
return FALSE;
} else {
if (hal_device_has_property (d, prop_to_check))
return FALSE;
else
return TRUE;
}
int type;
switch (type) {
case HAL_PROPERTY_TYPE_STRING:
if (hal_device_has_property (d, prop_to_check))
break;
if (hal_device_has_property (d, prop_to_check))
break;
default:
/* explicit fallthrough */
return FALSE;
break;
}
if (should_be_empty) {
if (is_empty)
return TRUE;
else
return FALSE;
} else {
if (is_empty)
return FALSE;
else
return TRUE;
}
unsigned int i;
const char *str;
return FALSE;
for (i = 0; str[i] != '\0'; i++) {
if (((unsigned char) str[i]) > 0x7f)
}
if (should_be_ascii) {
if (is_ascii)
return TRUE;
else
return FALSE;
} else {
if (is_ascii)
return FALSE;
else
return TRUE;
}
/*HAL_INFO (("d->udi='%s', prop_to_check='%s'", d->udi, prop_to_check));*/
return FALSE;
if (hal_device_has_property (d, prop_to_check)) {
if (g_path_is_absolute (path))
}
/*HAL_INFO (("is_absolute=%d, should_be=%d, path='%s'", is_absolute_path, should_be_absolute_path, path));*/
if (should_be_absolute_path) {
if (is_absolute_path)
return TRUE;
else
return FALSE;
} else {
if (is_absolute_path)
return FALSE;
else
return TRUE;
}
const char *needle;
if (hal_device_has_property (d, prop_to_check)) {
const char *haystack;
}
}
GSList *i;
break;
}
}
} else {
return FALSE;
}
return contains;
const char *needle;
if (hal_device_has_property (d, prop_to_check)) {
char *needle_lowercase;
char *haystack_lowercase;
if (needle_lowercase != NULL && haystack_lowercase != NULL && strstr (haystack_lowercase, needle_lowercase)) {
}
}
GSList *i;
break;
}
}
} else {
return FALSE;
}
return contains_ncase;
return FALSE;
} else {
return result < 0;
}
return FALSE;
else
return result <= 0;
return FALSE;
else
return result > 0;
return FALSE;
else
return result >= 0;
}
return FALSE;
}
/** Called when the merge element begins.
*
* @param pc Parsing context
*/
static void
{
int num_attrib;
;
}
if (num_attrib != 4)
return;
return;
return;
/* match string property */
return;
/* match string property */
return;
/* match string property */
return;
/* match string property */
return;
/* match string property */
return;
/* match string property */
return;
/* copy another property */
return;
}
return;
}
/** Called when the append or prepend element begins.
*
* @param pc Parsing context
*/
static void
{
int num_attrib;
;
}
if (num_attrib != 4)
return;
return;
return;
/* append to a string */
return;
/* append to a string list*/
return;
/* copy another property */
return;
}
return;
}
/** Called when the spawn element begins.
*
* @param pc Parsing context
*/
static void
{
int num_attrib;
;
}
if (num_attrib != 2)
return;
return;
return;
}
/** Called when the remove element begins.
*
* @param pc Parsing context
*/
static void
{
int num_attrib;
;
}
return;
return;
if (num_attrib == 4) {
return;
/* remove from strlist */
return;
} else {
return;
}
} else {
}
return;
}
/** Called when the clear element begins.
*
* @param pc Parsing context
*/
static void
{
int num_attrib;
;
}
if (num_attrib != 4)
return;
return;
return;
return;
}
/** Abort parsing of document
*
* @param pc Parsing context
*/
static void
{
/* Grr, expat can't abort parsing */
HAL_ERROR (("Aborting parsing of document"));
}
/** Called by expat when an element begins.
*
* @param pc Parsing context
* @param el Element name
*/
static void
{
return;
pc->cdata_buf_len = 0;
/*
for (i = 0; i < pc->depth; i++)
printf(" ");
printf("%s", el);
for (i = 0; attr[i]; i += 2) {
printf(" %s='%s'", attr[i], attr[i + 1]);
}
printf(" curelem=%d\n", pc->curelem);
*/
HAL_ERROR (("%s:%d:%d: Element <match> can only be "
"inside <device> and <match>",
parsing_abort (pc);
}
/* don't bother checking if matching at lower depths failed */
/* No match */
}
}
HAL_ERROR (("%s:%d:%d: Element <merge> can only be "
"inside <device> and <match>",
parsing_abort (pc);
}
} else {
/*HAL_INFO(("No merge!")); */
}
HAL_ERROR (("%s:%d:%d: Element <append> can only be "
"inside <device> and <match>",
parsing_abort (pc);
}
} else {
/*HAL_INFO(("No merge!")); */
}
HAL_ERROR (("%s:%d:%d: Element <prepend> can only be "
"inside <device> and <match>",
parsing_abort (pc);
}
} else {
/*HAL_INFO(("No merge!")); */
}
HAL_ERROR (("%s:%d:%d: Element <remove> can only be "
"inside <device> and <match>",
parsing_abort (pc);
}
} else {
/*HAL_INFO(("No merge!")); */
}
HAL_ERROR (("%s:%d:%d: Element <remove> can only be "
"inside <device> and <match>",
parsing_abort (pc);
}
} else {
/*HAL_INFO(("No merge!")); */
}
HAL_ERROR (("%s:%d:%d: Element <device> can only be "
"inside <deviceinfo>",
parsing_abort (pc);
}
HAL_ERROR (("%s:%d:%d: Element <deviceinfo> must be "
"a top-level element",
parsing_abort (pc);
}
HAL_ERROR (("%s:%d:%d: Element <spawn> can only be "
"inside <match>",
parsing_abort (pc);
}
}
} else {
HAL_ERROR (("%s:%d:%d: Unknown element <%s>",
parsing_abort (pc);
}
/* Nasty hack */
/* store depth */
}
static void
{
/* Move from temporary to global device store */
hal_device_store_remove (hald_get_tdl (), d);
hal_device_store_add (hald_get_gdl (), d);
}
/** Called by expat when an element ends.
*
* @param pc Parsing context
* @param el Element name
*/
static void
{
return;
/* printf(" curelem=%d\n", pc->curelem);*/
/* As soon as we are merging, we have matched the device... */
switch (pc->merge_type) {
case MERGE_TYPE_STRING:
break;
case MERGE_TYPE_STRLIST:
{
}
break;
}
case MERGE_TYPE_INT32:
{
/* match integer property */
/** @todo FIXME: Check error condition */
break;
}
case MERGE_TYPE_UINT64:
{
/* match integer property */
/** @todo FIXME: Check error condition */
break;
}
case MERGE_TYPE_BOOLEAN:
"true") == 0)
break;
case MERGE_TYPE_DOUBLE:
break;
case MERGE_TYPE_COPY_PROPERTY:
{
* '@prop.here.is.an.udi:with.prop.name'
*/
udi_to_merge_from, sizeof (udi_to_merge_from),
prop_to_merge, sizeof (prop_to_merge))) {
} else {
HalDevice *d;
if (d == NULL) {
}
if (d == NULL) {
} else {
}
}
break;
}
default:
HAL_ERROR (("Unknown merge_type=%d='%c'",
break;
}
/* As soon as we are appending, we have matched the device... */
} else {
const char *existing_string;
switch (pc->merge_type) {
case MERGE_TYPE_STRING:
break;
case MERGE_TYPE_COPY_PROPERTY:
break;
default:
break;
}
if (existing_string != NULL) {
} else {
}
}
/* As soon as we are prepending, we have matched the device... */
} else {
const char *existing_string;
switch (pc->merge_type) {
case MERGE_TYPE_STRING:
break;
case MERGE_TYPE_COPY_PROPERTY:
break;
default:
break;
}
if (existing_string != NULL) {
} else {
}
}
/* covers <remove key="foobar" type="strlist">blah</remove> */
} else {
/* only allow <remove key="foobar"/>, not <remove key="foobar">blah</remove> */
}
}
HAL_INFO (("Spawning new device object '%s' caused by <spawn> on udi '%s'",
spawned = hal_device_new ();
}
}
}
pc->cdata_buf_len = 0;
/* maintain curelem */
/* maintain pc->match_ok */
}
/** Called when there is CDATA
*
* @param pc Parsing context
* @param s Pointer to data
* @param len Length of data
*/
static void
{
int bytes_left;
int bytes_to_copy;
return;
if (len > bytes_left) {
HAL_ERROR (("CDATA in element larger than %d",
}
bytes_to_copy = len;
if (bytes_to_copy > bytes_left)
if (bytes_to_copy > 0)
}
/** Process a device information info file.
*
* @param dir Directory file resides in
* @param filename File name
* @param device Device to match on
* @return #TRUE if file matched device and information
* was merged
*/
static dbus_bool_t
{
int rc;
int filesize;
char *filebuf;
/*HAL_INFO(("analyzing file %s", buf));*/
/* open file and read it into a buffer; it's a small file... */
goto out;
}
goto out;
}
/* initialize parsing context */
if (parsing_context == NULL) {
HAL_ERROR (("Could not allocate parsing context"));
goto out;
}
/* TODO: reuse parser
*/
HAL_ERROR (("Could not allocate XML parser"));
goto out;
}
parsing_context->depth = 0;
/*printf("XML_Parse rc=%d\r\n", rc); */
if (rc == 0) {
/* error parsing document */
HAL_ERROR (("Error parsing XML document %s at line %d, "
"column %d : %s",
buf,
} else {
/* document parsed ok */
}
out:
if (parsing_context != NULL)
return device_matched;
}
static int
#ifdef __GLIBC__
my_alphasort(const void *a, const void *b)
#else
#endif
{
return -alphasort (a, b);
}
/** Scan all directories and subdirectories in the given directory and
* process each *.fdi file
*
* @param d Device to merge information into
* @return #TRUE if information was merged
*/
static dbus_bool_t
{
int i;
int num_entries;
found_fdi_file = 0;
/*HAL_INFO(("scan_fdi_files: Processing dir '%s'", dir));*/
if (num_entries == -1) {
return FALSE;
}
for (i = num_entries - 1; i >= 0; i--) {
int len;
char *filename;
/*HAL_INFO (("Full path = %s", full_path));*/
/* Mmm, d_type can be DT_UNKNOWN, use glib to determine
* the type
*/
/* regular file */
if (len >= 5 &&
/*HAL_INFO (("scan_fdi_files: Processing file '%s'", filename));*/
if (found_fdi_file) {
/*break;*/
}
}
int num_bytes;
char *dirname;
/* Directory; do the recursion thingy but not
* for . and ..
*/
HAL_ERROR (("couldn't allocated %d bytes",
num_bytes));
break;
}
filename);
/*
if (found_fdi_file)
break;
*/
}
}
for (; i >= 0; i--) {
}
return found_fdi_file;
}
/** Search the device info file repository for a .fdi file to merge
* more information into the device object.
*
* @param d Device to merge information into
* @return #TRUE if information was merged
*/
{
char *s1;
char *s2;
if (!have_checked_hal_fdi_source) {
}
switch (type) {
if (hal_fdi_source_preprobe != NULL) {
} else {
}
break;
if (hal_fdi_source_information != NULL) {
} else {
}
break;
case DEVICE_INFO_TYPE_POLICY:
if (hal_fdi_source_policy != NULL) {
} else {
}
break;
default:
break;
}
return ret;
}
/** @} */