/* Copyright (C) 2011 David Robillard Copyright (C) 2013 Paul Davis Copyright (C) 2019 Filipe Coelho This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "JackMetadata.h" #include "JackClient.h" #include #include #include #define JACK_METADATA_PREFIX "http://jackaudio.org/metadata/" LIB_EXPORT const char* JACK_METADATA_CONNECTED = JACK_METADATA_PREFIX "connected"; LIB_EXPORT const char* JACK_METADATA_EVENT_TYPES = JACK_METADATA_PREFIX "event-types"; LIB_EXPORT const char* JACK_METADATA_HARDWARE = JACK_METADATA_PREFIX "hardware"; LIB_EXPORT const char* JACK_METADATA_ICON_LARGE = JACK_METADATA_PREFIX "icon-large"; LIB_EXPORT const char* JACK_METADATA_ICON_NAME = JACK_METADATA_PREFIX "icon-name"; LIB_EXPORT const char* JACK_METADATA_ICON_SMALL = JACK_METADATA_PREFIX "icon-small"; LIB_EXPORT const char* JACK_METADATA_ORDER = JACK_METADATA_PREFIX "order"; LIB_EXPORT const char* JACK_METADATA_PORT_GROUP = JACK_METADATA_PREFIX "port-group"; LIB_EXPORT const char* JACK_METADATA_PRETTY_NAME = JACK_METADATA_PREFIX "pretty-name"; LIB_EXPORT const char* JACK_METADATA_SIGNAL_TYPE = JACK_METADATA_PREFIX "signal-type"; #undef JACK_METADATA_PREFIX namespace Jack { JackMetadata::JackMetadata(bool isEngine) #if HAVE_DB : fDB(NULL), fDBenv(NULL), fIsEngine(isEngine) #endif { PropertyInit(); } JackMetadata::~JackMetadata() { #if HAVE_DB char dbpath[PATH_MAX + 1]; if (fDB) { fDB->close (fDB, 0); fDB = NULL; } if (fDBenv) { fDBenv->close (fDBenv, 0); fDBenv = NULL; } if (fIsEngine) { // cleanup after libdb, nasty! snprintf (dbpath, sizeof(dbpath), "%s/jack_db/metadata.db", fDBFilesDir); remove (dbpath); snprintf (dbpath, sizeof(dbpath), "%s/jack_db/__db.001", fDBFilesDir); remove (dbpath); snprintf (dbpath, sizeof(dbpath), "%s/jack_db/__db.002", fDBFilesDir); remove (dbpath); snprintf (dbpath, sizeof(dbpath), "%s/jack_db/__db.003", fDBFilesDir); remove (dbpath); // remove our custom dir snprintf (dbpath, sizeof(dbpath), "%s/jack_db", fDBFilesDir); rmdir (dbpath); } #endif } int JackMetadata::PropertyInit() { #if HAVE_DB int ret; char dbpath[PATH_MAX + 1]; #ifdef WIN32 ret = GetTempPathA (PATH_MAX, fDBFilesDir); if ((ret > PATH_MAX) || (ret == 0)) { jack_error ("cannot get path for temp files"); return -1; } #else strncpy (fDBFilesDir, jack_server_dir, PATH_MAX); #endif /* idempotent */ if (fDBenv) { return 0; } if ((ret = db_env_create (&fDBenv, 0)) != 0) { jack_error ("cannot initialize DB environment: %s\n", db_strerror (ret)); return -1; } snprintf (dbpath, sizeof(dbpath), "%s/jack_db", fDBFilesDir); #ifdef WIN32 mkdir (dbpath); #else mkdir (dbpath, S_IRWXU | S_IRWXG); #endif if ((ret = fDBenv->open (fDBenv, dbpath, DB_CREATE | DB_INIT_LOCK | DB_INIT_MPOOL | DB_THREAD, 0)) != 0) { jack_error ("cannot open DB environment: %s", db_strerror (ret)); fDBenv = NULL; return -1; } if ((ret = db_create (&fDB, fDBenv, 0)) != 0) { jack_error ("Cannot initialize metadata DB (%s)", db_strerror (ret)); fDBenv->close (fDBenv, 0); fDBenv = NULL; return -1; } snprintf (dbpath, sizeof(dbpath), "%s/jack_db/metadata.db", fDBFilesDir); if ((ret = fDB->open (fDB, NULL, dbpath, NULL, DB_HASH, DB_CREATE | DB_THREAD, 0666)) != 0) { jack_error ("Cannot open metadata DB at %s: %s", dbpath, db_strerror (ret)); fDB->close (fDB, 0); fDB = NULL; fDBenv->close (fDBenv, 0); fDBenv = NULL; return -1; } return 0; #else // !HAVE_DB return -1; #endif } int JackMetadata::PropertyChangeNotify(JackClient* client, jack_uuid_t subject, const char* key, jack_property_change_t change) { /* the engine passes in a NULL client when it removes metadata during port or client removal */ if (client == NULL) { return 0; } return client->PropertyChangeNotify(subject, key, change); } #if HAVE_DB void JackMetadata::MakeKeyDbt(DBT* dbt, jack_uuid_t subject, const char* key) { char ustr[JACK_UUID_STRING_SIZE]; size_t len1, len2; memset (dbt, 0, sizeof(DBT)); memset (ustr, 0, JACK_UUID_STRING_SIZE); jack_uuid_unparse (subject, ustr); len1 = JACK_UUID_STRING_SIZE; len2 = strlen (key) + 1; dbt->size = len1 + len2; dbt->data = malloc (dbt->size); memcpy (dbt->data, ustr, len1); // copy subject+null memcpy ((char *)dbt->data + len1, key, len2); // copy key+null } #endif int JackMetadata::SetProperty(JackClient* client, jack_uuid_t subject, const char* key, const char* value, const char* type) { #if HAVE_DB DBT d_key; DBT data; int ret; size_t len1, len2; jack_property_change_t change; if (!key || key[0] == '\0') { jack_error ("empty key string for metadata not allowed"); return -1; } if (!value || value[0] == '\0') { jack_error ("empty value string for metadata not allowed"); return -1; } if (PropertyInit()) { return -1; } /* build a key */ MakeKeyDbt(&d_key, subject, key); /* build data */ memset (&data, 0, sizeof(data)); len1 = strlen (value) + 1; if (type && type[0] != '\0') { len2 = strlen (type) + 1; } else { len2 = 0; } data.size = len1 + len2; data.data = malloc (data.size); memcpy (data.data, value, len1); if (len2) { memcpy ((char *)data.data + len1, type, len2); } if (fDB->exists (fDB, NULL, &d_key, 0) == DB_NOTFOUND) { change = PropertyCreated; } else { change = PropertyChanged; } if ((ret = fDB->put (fDB, NULL, &d_key, &data, 0)) != 0) { char ustr[JACK_UUID_STRING_SIZE]; jack_uuid_unparse (subject, ustr); jack_error ("Cannot store metadata for %s/%s (%s)", ustr, key, db_strerror (ret)); if (d_key.size > 0) { free (d_key.data); } if (data.size > 0) { free (data.data); } return -1; } PropertyChangeNotify(client, subject, key, change); if (d_key.size > 0) { free (d_key.data); } if (data.size > 0) { free (data.data); } return 0; #else // !HAVE_DB return -1; #endif } int JackMetadata::GetProperty(jack_uuid_t subject, const char* key, char** value, char** type) { #if HAVE_DB DBT d_key; DBT data; int ret; size_t len1, len2; if (key == NULL || key[0] == '\0') { return -1; } if (PropertyInit()) { return -1; } /* build a key */ MakeKeyDbt(&d_key, subject, key); /* setup data DBT */ memset (&data, 0, sizeof(data)); data.flags = DB_DBT_MALLOC; if ((ret = fDB->get (fDB, NULL, &d_key, &data, 0)) != 0) { if (ret != DB_NOTFOUND) { char ustr[JACK_UUID_STRING_SIZE]; jack_uuid_unparse (subject, ustr); jack_error ("Cannot retrieve metadata for %s/%s (%s)", ustr, key, db_strerror (ret)); } if (d_key.size > 0) { free (d_key.data); } if (data.size > 0) { free (data.data); } return -1; } /* result must have at least 1 char plus 1 null to be valid (old rule was: result must have at least 2 chars plus 2 nulls to be valid ) */ if (data.size < 2) { if (d_key.size > 0) { free (d_key.data); } if (data.size > 0) { free (data.data); } return -1; } len1 = strlen ((const char*)data.data) + 1; (*value) = (char*)malloc (len1); memcpy (*value, data.data, len1); if (len1 < data.size) { len2 = strlen ((const char*)data.data + len1) + 1; (*type) = (char*)malloc (len2); memcpy (*type, (const char *)data.data + len1, len2); } else { /* no type specified, assume default */ *type = NULL; } if (d_key.size > 0) { free (d_key.data); } if (data.size > 0) { free (data.data); } return 0; #else // !HAVE_DB return -1; #endif } int JackMetadata::GetProperties(jack_uuid_t subject, jack_description_t* desc) { #if HAVE_DB DBT key; DBT data; DBC* cursor; int ret; size_t len1, len2; size_t cnt = 0; char ustr[JACK_UUID_STRING_SIZE]; size_t props_size = 0; jack_property_t* prop; desc->properties = NULL; desc->property_cnt = 0; memset (ustr, 0, JACK_UUID_STRING_SIZE); jack_uuid_unparse (subject, ustr); if (PropertyInit()) { return -1; } if ((ret = fDB->cursor (fDB, NULL, &cursor, 0)) != 0) { jack_error ("Cannot create cursor for metadata search (%s)", db_strerror (ret)); return -1; } memset (&key, 0, sizeof(key)); memset (&data, 0, sizeof(data)); data.flags = DB_DBT_MALLOC; while ((ret = cursor->get (cursor, &key, &data, DB_NEXT)) == 0) { /* require 2 extra chars (data+null) for key, which is composed of UUID str plus a key name */ if (key.size < JACK_UUID_STRING_SIZE + 2) { /* if (key.size > 0) free(key.data); */ if (data.size > 0) { free (data.data); } continue; } if (memcmp (ustr, key.data, JACK_UUID_STRING_SIZE) != 0) { /* not relevant */ /* if (key.size > 0) free(key.data); */ if (data.size > 0) { free (data.data); } continue; } /* result must have at least 1 char plus 1 null to be valid (old rule was: result must have at least 2 chars plus 2 nulls to be valid ) */ if (data.size < 2) { /* if (key.size > 0) free(key.data); */ if (data.size > 0) { free (data.data); } continue; } /* realloc array if necessary */ if (cnt == props_size) { if (props_size == 0) { props_size = 8; /* a rough guess at a likely upper bound for the number of properties */ } else { props_size *= 2; } desc->properties = (jack_property_t*)realloc (desc->properties, sizeof(jack_property_t) * props_size); } prop = &desc->properties[cnt]; /* store UUID/subject */ jack_uuid_copy (&desc->subject, subject); /* copy key (without leading UUID as subject */ len1 = key.size - JACK_UUID_STRING_SIZE; prop->key = (char*)malloc (len1); memcpy ((char*)prop->key, (const char *)key.data + JACK_UUID_STRING_SIZE, len1); /* copy data (which contains 1 or 2 null terminated strings, the value and optionally a MIME type. */ len1 = strlen ((const char*)data.data) + 1; prop->data = (char*)malloc (len1); memcpy ((char*)prop->data, data.data, len1); if (len1 < data.size) { len2 = strlen ((const char *)data.data + len1) + 1; prop->type = (char*)malloc (len2); memcpy ((char*)prop->type, (const char *)data.data + len1, len2); } else { /* no type specified, assume default */ prop->type = NULL; } /* if (key.size > 0) free(key.data); */ if (data.size > 0) { free (data.data); } ++cnt; } cursor->close (cursor); desc->property_cnt = cnt; return cnt; #else // !HAVE_DB return -1; #endif } int JackMetadata::GetAllProperties(jack_description_t** descriptions) { #if HAVE_DB DBT key; DBT data; DBC* cursor; int ret; size_t dcnt = 0; size_t dsize = 0; size_t n = 0; jack_description_t* desc = NULL; jack_uuid_t uuid = JACK_UUID_EMPTY_INITIALIZER; jack_description_t* current_desc = NULL; jack_property_t* current_prop = NULL; size_t len1, len2; if (PropertyInit()) { return -1; } if ((ret = fDB->cursor (fDB, NULL, &cursor, 0)) != 0) { jack_error ("Cannot create cursor for metadata search (%s)", db_strerror (ret)); return -1; } memset (&key, 0, sizeof(key)); memset (&data, 0, sizeof(data)); data.flags = DB_DBT_MALLOC; dsize = 8; /* initial guess at number of descriptions we need */ dcnt = 0; desc = (jack_description_t*)malloc (dsize * sizeof(jack_description_t)); while ((ret = cursor->get (cursor, &key, &data, DB_NEXT)) == 0) { /* require 2 extra chars (data+null) for key, which is composed of UUID str plus a key name */ if (key.size < JACK_UUID_STRING_SIZE + 2) { /* if (key.size > 0) free(key.data); */ if (data.size > 0) { free (data.data); } continue; } if (jack_uuid_parse ((const char *)key.data, &uuid) != 0) { continue; } /* do we have an existing description for this UUID */ for (n = 0; n < dcnt; ++n) { if (jack_uuid_compare (uuid, desc[n].subject) == 0) { break; } } if (n == dcnt) { /* we do not have an existing description, so grow the array */ if (dcnt == dsize) { dsize *= 2; desc = (jack_description_t*)realloc (desc, sizeof(jack_description_t) * dsize); } /* initialize */ desc[n].property_size = 0; desc[n].property_cnt = 0; desc[n].properties = NULL; /* set up UUID */ jack_uuid_copy (&desc[n].subject, uuid); dcnt++; } current_desc = &desc[n]; /* see if there is room for the new property or if we need to realloc */ if (current_desc->property_cnt == current_desc->property_size) { if (current_desc->property_size == 0) { current_desc->property_size = 8; } else { current_desc->property_size *= 2; } current_desc->properties = (jack_property_t*)realloc (current_desc->properties, sizeof(jack_property_t) * current_desc->property_size); } current_prop = ¤t_desc->properties[current_desc->property_cnt++]; /* copy key (without leading UUID) */ len1 = key.size - JACK_UUID_STRING_SIZE; current_prop->key = (char*)malloc (len1); memcpy ((char*)current_prop->key, (const char *)key.data + JACK_UUID_STRING_SIZE, len1); /* copy data (which contains 1 or 2 null terminated strings, the value and optionally a MIME type. */ len1 = strlen ((const char *)data.data) + 1; current_prop->data = (char*)malloc (len1); memcpy ((char*)current_prop->data, data.data, len1); if (len1 < data.size) { len2 = strlen ((const char *)data.data + len1) + 1; current_prop->type = (char*)malloc (len2); memcpy ((char*)current_prop->type, (const char *)data.data + len1, len2); } else { /* no type specified, assume default */ current_prop->type = NULL; } /* if (key.size > 0) free(key.data); */ if (data.size > 0) { free (data.data); } } cursor->close (cursor); (*descriptions) = desc; return dcnt; #else // !HAVE_DB return -1; #endif } int JackMetadata::GetDescription(jack_uuid_t subject, jack_description_t* desc) { return 0; } int JackMetadata::GetAllDescriptions(jack_description_t** descs) { return 0; } void JackMetadata::FreeDescription(jack_description_t* desc, int free_actual_description_too) { uint32_t n; for (n = 0; n < desc->property_cnt; ++n) { free ((char*)desc->properties[n].key); free ((char*)desc->properties[n].data); if (desc->properties[n].type) { free ((char*)desc->properties[n].type); } } free (desc->properties); if (free_actual_description_too) { free (desc); } } int JackMetadata::RemoveProperty(JackClient* client, jack_uuid_t subject, const char* key) { #if HAVE_DB DBT d_key; int ret; if (PropertyInit()) { return -1; } MakeKeyDbt(&d_key, subject, key); if ((ret = fDB->del (fDB, NULL, &d_key, 0)) != 0) { jack_error ("Cannot delete key %s (%s)", key, db_strerror (ret)); if (d_key.size > 0) { free (d_key.data); } return -1; } PropertyChangeNotify(client, subject, key, PropertyDeleted); if (d_key.size > 0) { free (d_key.data); } return 0; #else // !HAVE_DB return -1; #endif } int JackMetadata::RemoveProperties(JackClient* client, jack_uuid_t subject) { #if HAVE_DB DBT key; DBT data; DBC* cursor; int ret; char ustr[JACK_UUID_STRING_SIZE]; int retval = 0; uint32_t cnt = 0; memset (ustr, 0, JACK_UUID_STRING_SIZE); jack_uuid_unparse (subject, ustr); if (PropertyInit() || fDB == NULL) { return -1; } if ((ret = fDB->cursor (fDB, NULL, &cursor, 0)) != 0) { jack_error ("Cannot create cursor for metadata search (%s)", db_strerror (ret)); return -1; } memset (&key, 0, sizeof(key)); memset (&data, 0, sizeof(data)); data.flags = DB_DBT_MALLOC; while ((ret = cursor->get (cursor, &key, &data, DB_NEXT)) == 0) { /* require 2 extra chars (data+null) for key, which is composed of UUID str plus a key name */ if (key.size < JACK_UUID_STRING_SIZE + 2) { /* if (key.size > 0) free(key.data); */ if (data.size > 0) { free (data.data); } continue; } if (memcmp (ustr, key.data, JACK_UUID_STRING_SIZE) != 0) { /* not relevant */ /* if (key.size > 0) free(key.data); */ if (data.size > 0) { free (data.data); } continue; } if ((ret = cursor->del (cursor, 0)) != 0) { jack_error ("cannot delete property (%s)", db_strerror (ret)); /* don't return -1 here since this would leave things even more inconsistent. wait till the cursor is finished */ retval = -1; } cnt++; /* if (key.size > 0) free(key.data); */ if (data.size > 0) { free (data.data); } } cursor->close (cursor); if (cnt) { PropertyChangeNotify(client, subject, NULL, PropertyDeleted); } if (retval) { return -1; } return cnt; #else // !HAVE_DB return -1; #endif } int JackMetadata::RemoveAllProperties(JackClient* client) { #if HAVE_DB int ret; jack_uuid_t empty_uuid = JACK_UUID_EMPTY_INITIALIZER; if (PropertyInit()) { return -1; } if ((ret = fDB->truncate (fDB, NULL, NULL, 0)) != 0) { jack_error ("Cannot clear properties (%s)", db_strerror (ret)); return -1; } PropertyChangeNotify(client, empty_uuid, NULL, PropertyDeleted); return 0; #else // !HAVE_DB return -1; #endif } } // end of namespace