Advertise the _raop._tcp service as well as the _airplay._tcp service. Use a real MAC as the basis for the device ID.
This commit is contained in:
@@ -1609,9 +1609,9 @@ void pthread_cleanup_debug_mutex_unlock(void *arg) { pthread_mutex_unlock((pthre
|
||||
|
||||
char *get_version_string() {
|
||||
char *version_string = malloc(1024);
|
||||
if (version_string) {
|
||||
if (version_string) {
|
||||
#ifdef CONFIG_USE_GIT_VERSION_STRING
|
||||
if (git_version_string[0] != '\0')
|
||||
if (git_version_string[0] != '\0')
|
||||
strcpy(version_string, git_version_string);
|
||||
else
|
||||
#endif
|
||||
@@ -1939,29 +1939,29 @@ int32_t mod32Difference(uint32_t a, uint32_t b) {
|
||||
return result;
|
||||
}
|
||||
|
||||
char *get_device_id() {
|
||||
char *response = NULL;
|
||||
int get_device_id(uint8_t *id, int int_length) {
|
||||
int response = 0;
|
||||
struct ifaddrs *ifaddr = NULL;
|
||||
struct ifaddrs *ifa = NULL;
|
||||
int i = 0;
|
||||
uint8_t *t = id;
|
||||
for (i = 0; i < int_length ; i++) {
|
||||
*t++ = 0;
|
||||
}
|
||||
|
||||
if (getifaddrs(&ifaddr) == -1) {
|
||||
debug(1, "getifaddrs");
|
||||
response = -1;
|
||||
} else {
|
||||
t = id;
|
||||
int found = 0;
|
||||
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
|
||||
if ((ifa->ifa_addr) && (ifa->ifa_addr->sa_family == AF_PACKET)) {
|
||||
char obf[256] = {0};
|
||||
char *obfp = obf;
|
||||
struct sockaddr_ll *s = (struct sockaddr_ll *)ifa->ifa_addr;
|
||||
if ((strcmp(ifa->ifa_name, "lo") != 0) && (found == 0)) {
|
||||
for (i = 0; i < s->sll_halen; i++) {
|
||||
snprintf(obfp, 4, "%02x:", s->sll_addr[i]);
|
||||
obfp += 3;
|
||||
for (i = 0; ((i < s->sll_halen) && (i < int_length)); i++) {
|
||||
*t++ = s->sll_addr[i];
|
||||
}
|
||||
obfp -= 1;
|
||||
*obfp = 0;
|
||||
response = strdup(obf);
|
||||
found = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,7 +209,9 @@ typedef struct {
|
||||
// char *errfile;
|
||||
char *configfile;
|
||||
char *regtype; // The regtype is the service type followed by the protocol, separated by a dot, by
|
||||
// default “_raop._tcp.”.
|
||||
// default “_raop._tcp.” for AirPlay 1.
|
||||
char *regtype2; // The regtype is the service type followed by the protocol, separated by a dot, by
|
||||
// default “_raop._tcp.” for AirPlay 2.
|
||||
char *interface; // a string containg the interface name, or NULL if nothing specified
|
||||
int interface_index; // only valid if the interface string is non-NULL
|
||||
double audio_backend_buffer_desired_length; // this will be the length in seconds of the
|
||||
@@ -489,7 +491,13 @@ void *memdup(const void *mem, size_t size);
|
||||
// negative otherwise
|
||||
|
||||
int32_t mod32Difference(uint32_t a, uint32_t b);
|
||||
char *get_device_id();
|
||||
|
||||
int get_device_id(uint8_t *id, int int_length);
|
||||
|
||||
#ifdef CONFIG_USE_GIT_VERSION_STRING
|
||||
extern char git_version_string[];
|
||||
#endif
|
||||
|
||||
|
||||
#endif // _COMMON_H
|
||||
|
||||
|
||||
@@ -64,17 +64,15 @@ static mdns_backend *mdns_backends[] = {
|
||||
#endif
|
||||
NULL};
|
||||
|
||||
void mdns_register(char **txt_records) {
|
||||
char *mdns_service_name = alloca(strlen(config.service_name) + 14);
|
||||
char *p = mdns_service_name;
|
||||
#ifndef CONFIG_AIRPLAY_2
|
||||
void mdns_register(char **txt_records, char **secondary_txt_records) {
|
||||
char *ap1_service_name = alloca(strlen(config.service_name) + 14);
|
||||
char *p = ap1_service_name;
|
||||
int i;
|
||||
for (i = 0; i < 6; i++) {
|
||||
snprintf(p, 3, "%02X", config.hw_addr[i]);
|
||||
p += 2;
|
||||
}
|
||||
*p++ = '@';
|
||||
#endif
|
||||
strcpy(p, config.service_name);
|
||||
|
||||
mdns_backend **b = NULL;
|
||||
@@ -83,7 +81,7 @@ void mdns_register(char **txt_records) {
|
||||
for (b = mdns_backends; *b; b++) {
|
||||
if (strcmp((*b)->name, config.mdns_name) != 0) // Not the one we are looking for
|
||||
continue;
|
||||
int error = (*b)->mdns_register(mdns_service_name, config.port, txt_records);
|
||||
int error = (*b)->mdns_register(ap1_service_name, config.service_name, config.port, txt_records, secondary_txt_records);
|
||||
if (error >= 0) {
|
||||
config.mdns = *b;
|
||||
}
|
||||
@@ -93,8 +91,9 @@ void mdns_register(char **txt_records) {
|
||||
if (*b == NULL)
|
||||
warn("%s mDNS backend not found");
|
||||
} else {
|
||||
// default -- pick the first back end
|
||||
for (b = mdns_backends; *b; b++) {
|
||||
int error = (*b)->mdns_register(mdns_service_name, config.port, txt_records);
|
||||
int error = (*b)->mdns_register(ap1_service_name, config.service_name, config.port, txt_records, secondary_txt_records);
|
||||
if (error >= 0) {
|
||||
config.mdns = *b;
|
||||
break;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
extern int mdns_pid;
|
||||
|
||||
void mdns_unregister(void);
|
||||
void mdns_register(char **txt_records);
|
||||
void mdns_register(char **txt_records, char **secondary_txt_records);
|
||||
void mdns_dacp_monitor_start();
|
||||
void mdns_dacp_monitor_stop(void);
|
||||
void mdns_dacp_monitor_set_id(const char *dacp_id);
|
||||
@@ -17,7 +17,7 @@ void mdns_ls_backends(void);
|
||||
|
||||
typedef struct {
|
||||
char *name;
|
||||
int (*mdns_register)(char *apname, int port, char **txt_records);
|
||||
int (*mdns_register)(char *ap1name, char *ap2name, int port, char **txt_records, char **secondary_txt_records);
|
||||
void (*mdns_unregister)(void);
|
||||
void (*mdns_dacp_monitor_start)();
|
||||
void (*mdns_dacp_monitor_set_id)(const char *);
|
||||
|
||||
+34
-9
@@ -62,6 +62,7 @@ typedef struct {
|
||||
dacp_browser_struct private_dbs;
|
||||
|
||||
AvahiStringList *text_record_string_list = NULL;
|
||||
AvahiStringList *ap2_text_record_string_list = NULL;
|
||||
|
||||
// static AvahiServiceBrowser *sb = NULL;
|
||||
static AvahiClient *client = NULL;
|
||||
@@ -71,6 +72,8 @@ static AvahiThreadedPoll *tpoll = NULL;
|
||||
// static AvahiThreadedPoll *service_poll = NULL;
|
||||
|
||||
static char *service_name = NULL;
|
||||
char *ap2_service_name = NULL;
|
||||
|
||||
static int port = 0;
|
||||
|
||||
static void resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIndex interface,
|
||||
@@ -187,7 +190,7 @@ static void egroup_callback(AvahiEntryGroup *g, AvahiEntryGroupState state,
|
||||
|
||||
/* A service name collision with a remote service
|
||||
* happened. Let's pick a new name */
|
||||
debug(2, "avahi name collision -- look for another");
|
||||
debug(1, "avahi name collision -- look for another");
|
||||
n = avahi_alternative_service_name(service_name);
|
||||
if (service_name)
|
||||
avahi_free(service_name);
|
||||
@@ -238,10 +241,16 @@ static void register_service(AvahiClient *c) {
|
||||
selected_interface = config.interface_index;
|
||||
else
|
||||
selected_interface = AVAHI_IF_UNSPEC;
|
||||
if (text_record_string_list) {
|
||||
if (ap2_text_record_string_list) {
|
||||
ret = avahi_entry_group_add_service_strlst(group, selected_interface, AVAHI_PROTO_UNSPEC, 0,
|
||||
ap2_service_name, config.regtype2, NULL, NULL, port,
|
||||
ap2_text_record_string_list);
|
||||
}
|
||||
if ((ret == 0) && (text_record_string_list)) {
|
||||
ret = avahi_entry_group_add_service_strlst(group, selected_interface, AVAHI_PROTO_UNSPEC, 0,
|
||||
service_name, config.regtype, NULL, NULL, port,
|
||||
text_record_string_list);
|
||||
}
|
||||
if (ret == 0) {
|
||||
ret = avahi_entry_group_commit(group);
|
||||
debug(2, "avahi: avahi_entry_group_commit %d", ret);
|
||||
@@ -252,9 +261,6 @@ static void register_service(AvahiClient *c) {
|
||||
} else {
|
||||
debug(1, "avahi: unexpected positive return");
|
||||
}
|
||||
} else {
|
||||
debug(1, "Can't find a valid text_record_string_list");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,12 +318,17 @@ static void client_callback(AvahiClient *c, AvahiClientState state,
|
||||
}
|
||||
}
|
||||
|
||||
static int avahi_register(char *srvname, int srvport, char **txt_records) {
|
||||
static int avahi_register(char *ap1name, char *ap2name, int srvport, char **txt_records,char **secondary_txt_records) {
|
||||
// debug(1, "avahi_register.");
|
||||
service_name = strdup(srvname);
|
||||
service_name = strdup(ap1name);
|
||||
if (ap2name != NULL)
|
||||
ap2_service_name = strdup(ap2name);
|
||||
|
||||
text_record_string_list = avahi_string_list_new_from_array((const char **)txt_records, -1);
|
||||
|
||||
if (secondary_txt_records != NULL)
|
||||
ap2_text_record_string_list = avahi_string_list_new_from_array((const char **)secondary_txt_records, -1);
|
||||
|
||||
port = srvport;
|
||||
|
||||
int err;
|
||||
@@ -362,15 +373,29 @@ static void avahi_unregister(void) {
|
||||
if (service_name) {
|
||||
debug(2, "avahi: free the service name.");
|
||||
free(service_name);
|
||||
service_name = NULL;
|
||||
} else
|
||||
debug(1, "avahi attempt to free NULL service name");
|
||||
|
||||
if (ap2_service_name) {
|
||||
debug(2, "avahi: free the ap2 service_name.");
|
||||
free(ap2_service_name);
|
||||
ap2_service_name = NULL;
|
||||
}
|
||||
|
||||
if (text_record_string_list) {
|
||||
debug(2, "avahi free text_record_string_list");
|
||||
avahi_string_list_free(text_record_string_list);
|
||||
}
|
||||
text_record_string_list = NULL;
|
||||
} else
|
||||
debug(1, "avahi attempt to free NULL text_record_string_list");
|
||||
|
||||
service_name = NULL;
|
||||
|
||||
if (ap2_text_record_string_list) {
|
||||
debug(2, "avahi free ap_2text_record_string_list");
|
||||
avahi_string_list_free(ap2_text_record_string_list);
|
||||
ap2_text_record_string_list = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void avahi_dacp_monitor_start(void) {
|
||||
|
||||
@@ -696,7 +696,7 @@ int msg_handle_line(rtsp_message **pmsg, char *line) {
|
||||
*p = 0;
|
||||
p += 2;
|
||||
msg_add_header(msg, line, p);
|
||||
debug(3, " %s: %s.", line, p);
|
||||
// debug(3, " %s: %s.", line, p);
|
||||
return -1;
|
||||
} else {
|
||||
char *cl = msg_get_header(msg, "Content-Length");
|
||||
@@ -1714,8 +1714,8 @@ void handle_options(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *
|
||||
resp->respcode = 200;
|
||||
msg_add_header(resp, "Public",
|
||||
"ANNOUNCE, SETUP, RECORD, "
|
||||
"PAUSE, FLUSH, TEARDOWN, "
|
||||
"OPTIONS, GET_PARAMETER, SET_PARAMETER, GET");
|
||||
"PAUSE, FLUSH, FLUSHBUFFERED, TEARDOWN, "
|
||||
"OPTIONS, POST, GET, PUT");
|
||||
}
|
||||
|
||||
void handle_teardown_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req,
|
||||
@@ -3414,7 +3414,7 @@ static void apple_challenge(int fd, rtsp_message *req, rtsp_message *resp) {
|
||||
char *hdr = msg_get_header(req, "Apple-Challenge");
|
||||
if (!hdr)
|
||||
return;
|
||||
|
||||
debug(1,"Apple Challenge");
|
||||
SOCKADDR fdsa;
|
||||
socklen_t sa_len = sizeof(fdsa);
|
||||
getsockname(fd, (struct sockaddr *)&fdsa, &sa_len);
|
||||
@@ -4084,50 +4084,58 @@ void *rtsp_listen_loop(__attribute((unused)) void *arg) {
|
||||
maxfd = sockfd[i];
|
||||
}
|
||||
|
||||
char **t1; // ap1 test records
|
||||
char **t2; // two text records
|
||||
|
||||
t1 = NULL;
|
||||
t2 = NULL;
|
||||
|
||||
// make up a txt record
|
||||
char *txt_records[64];
|
||||
char **p = txt_records;
|
||||
t1 = p; // first set of text records
|
||||
|
||||
// make up a firmware version
|
||||
char firmware_version[64];
|
||||
#ifdef CONFIG_USE_GIT_VERSION_STRING
|
||||
if (git_version_string[0] != '\0')
|
||||
snprintf(firmware_version, sizeof(firmware_version), "fv=%s",git_version_string);
|
||||
else
|
||||
#endif
|
||||
snprintf(firmware_version, sizeof(firmware_version), "fv=%s",PACKAGE_VERSION);
|
||||
|
||||
#ifdef CONFIG_AIRPLAY_2
|
||||
*p++ = "srcvers=366.0";
|
||||
char deviceIdString[64];
|
||||
snprintf(deviceIdString, sizeof(deviceIdString), "deviceid=%s", config.airplay_device_id);
|
||||
*p++ = deviceIdString;
|
||||
// features is a 64 bit number, least significant 32 bits
|
||||
char featuresString[64];
|
||||
char ap1_featuresString[64];
|
||||
uint64_t features_hi = config.airplay_features;
|
||||
features_hi = (features_hi >> 32) & 0xffffffff;
|
||||
uint64_t features_lo = config.airplay_features;
|
||||
features_lo = features_lo & 0xffffffff;
|
||||
snprintf(featuresString, sizeof(featuresString), "features=0x%" PRIx64 ",0x%" PRIx64 "",
|
||||
snprintf(ap1_featuresString, sizeof(ap1_featuresString), "ft=0x%" PRIX64 ",0x%" PRIX64 "",
|
||||
features_lo, features_hi);
|
||||
*p++ = featuresString;
|
||||
char statusflagsString[32];
|
||||
snprintf(statusflagsString, sizeof(statusflagsString), "flags=0x%" PRIx32,
|
||||
config.airplay_statusflags);
|
||||
*p++ = statusflagsString;
|
||||
*p++ = "protovers=1.1";
|
||||
*p++ = "acl=0";
|
||||
*p++ = "rsf=0x0";
|
||||
*p++ = "fv=p20.78000.12";
|
||||
*p++ = "model=SPS";
|
||||
char piString[64];
|
||||
snprintf(piString, sizeof(piString), "pi=%s", config.airplay_pi);
|
||||
*p++ = piString;
|
||||
char gidString[64];
|
||||
snprintf(gidString, sizeof(gidString), "gid=%s", config.airplay_gid);
|
||||
*p++ = gidString;
|
||||
*p++ = "gcgl=0";
|
||||
char pkString[128];
|
||||
snprintf(pkString, sizeof(pkString), "pk=");
|
||||
pkString_make(pkString + strlen("pk="), sizeof(pkString) - strlen("pk="),
|
||||
config.airplay_device_id);
|
||||
|
||||
// the ap1 text record is different if it is set up for ap2
|
||||
*p++ = "cn=0,1";
|
||||
*p++ = "da=true";
|
||||
*p++ = "et=0,4";
|
||||
*p++ = ap1_featuresString;
|
||||
*p++ = firmware_version;
|
||||
*p++ = "md=2";
|
||||
*p++ = "am=SPS";
|
||||
*p++ = "sf=0x4";
|
||||
*p++ = "tp=UDP";
|
||||
*p++ = "vn=65537";
|
||||
*p++ = "vs=366.0";
|
||||
*p++ = pkString;
|
||||
*p++ = NULL;
|
||||
|
||||
#else
|
||||
// here, just replicate what happens in mdns.h when using those #defines
|
||||
*p++ = "sf=0x4";
|
||||
*p++ = "fv=76400.10";
|
||||
*p++ = firmware_version;
|
||||
*p++ = "am=ShairportSync";
|
||||
*p++ = "vs=105.1";
|
||||
*p++ = "tp=TCP,UDP";
|
||||
@@ -4146,23 +4154,52 @@ void *rtsp_listen_loop(__attribute((unused)) void *arg) {
|
||||
*p++ = "ek=1";
|
||||
*p++ = "cn=0,1";
|
||||
*p++ = "ch=2";
|
||||
*p++ = "am=ShairportSync";
|
||||
*p++ = "txtvers=1";
|
||||
if (config.password == 0)
|
||||
*p++ = "pw=false";
|
||||
else
|
||||
*p++ = "pw=true";
|
||||
|
||||
*p++ = NULL;
|
||||
#endif
|
||||
|
||||
/*
|
||||
debug(1,"txt_record:");
|
||||
p = txt_records;
|
||||
while (*p)
|
||||
debug(1," %s", *p++);
|
||||
*/
|
||||
|
||||
mdns_register(txt_records);
|
||||
#ifdef CONFIG_AIRPLAY_2
|
||||
// make up a secondary set of text records
|
||||
char *secondary_txt_records[64];
|
||||
p = secondary_txt_records;
|
||||
t2 = p; // second set of text records in AirPlay 2 only
|
||||
|
||||
*p++ = "srcvers=366.0";
|
||||
char deviceIdString[64];
|
||||
snprintf(deviceIdString, sizeof(deviceIdString), "deviceid=%s", config.airplay_device_id);
|
||||
*p++ = deviceIdString;
|
||||
char featuresString[64];
|
||||
snprintf(featuresString, sizeof(featuresString), "features=0x%" PRIX64 ",0x%" PRIX64 "",
|
||||
features_lo, features_hi);
|
||||
*p++ = featuresString;
|
||||
char statusflagsString[32];
|
||||
snprintf(statusflagsString, sizeof(statusflagsString), "flags=0x%" PRIX32,
|
||||
config.airplay_statusflags);
|
||||
*p++ = statusflagsString;
|
||||
*p++ = "protovers=1.1";
|
||||
*p++ = "acl=0";
|
||||
*p++ = "rsf=0x0";
|
||||
*p++ = firmware_version;
|
||||
*p++ = "model=SPS";
|
||||
char piString[64];
|
||||
snprintf(piString, sizeof(piString), "pi=%s", config.airplay_pi);
|
||||
*p++ = piString;
|
||||
char gidString[64];
|
||||
snprintf(gidString, sizeof(gidString), "gid=%s", config.airplay_gid);
|
||||
*p++ = gidString;
|
||||
*p++ = "gcgl=0";
|
||||
*p++ = pkString;
|
||||
*p++ = NULL;
|
||||
#endif
|
||||
|
||||
|
||||
mdns_register(t1,t2);
|
||||
|
||||
pthread_setcancelstate(oldState, NULL);
|
||||
int acceptfd;
|
||||
|
||||
+35
-11
@@ -1246,10 +1246,10 @@ int parse_options(int argc, char **argv) {
|
||||
|
||||
/* if the regtype hasn't been set, do it now */
|
||||
if (config.regtype == NULL)
|
||||
#ifdef CONFIG_AIRPLAY_2
|
||||
config.regtype = strdup("_airplay._tcp");
|
||||
#else
|
||||
config.regtype = strdup("_raop._tcp");
|
||||
#ifdef CONFIG_AIRPLAY_2
|
||||
if (config.regtype2 == NULL)
|
||||
config.regtype2 = strdup("_airplay._tcp");
|
||||
#endif
|
||||
|
||||
if (tdebuglev != 0)
|
||||
@@ -1416,6 +1416,11 @@ void exit_function() {
|
||||
|
||||
if (config.regtype)
|
||||
free(config.regtype);
|
||||
#ifdef CONFIG_AIRPLAY_2
|
||||
if (config.regtype2)
|
||||
free(config.regtype2);
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef CONFIG_LIBDAEMON
|
||||
if (this_is_the_daemon_process) {
|
||||
@@ -1515,6 +1520,9 @@ int main(int argc, char **argv) {
|
||||
|
||||
// set defaults
|
||||
|
||||
// get a device id -- the first non-local MAC address
|
||||
get_device_id((uint8_t *)&config.hw_addr, 6);
|
||||
|
||||
// get the endianness
|
||||
union {
|
||||
uint32_t u32;
|
||||
@@ -1582,15 +1590,27 @@ int main(int argc, char **argv) {
|
||||
// required (transient pairing), no Homekit access control 0x204: Audio cable attached,
|
||||
// OneTimePairingRequired 0x604: Audio cable attached, OneTimePairingRequired, allow Homekit
|
||||
// access control
|
||||
|
||||
// note: 0x300401F4A00 works but with weird delays and stuff
|
||||
|
||||
config.airplay_statusflags = 0x4;
|
||||
// Set to NULL to work with transient pairing
|
||||
config.airplay_pin = NULL;
|
||||
// get a device id -- the first non-local MAC address, not necessarily the one in use
|
||||
config.airplay_device_id = get_device_id();
|
||||
if (config.airplay_device_id) {
|
||||
debug(1, "Started in Airplay 2 mode on device \"%s\"!", config.airplay_device_id);
|
||||
} else
|
||||
debug(1, "Started in Airplay 2 mode!");
|
||||
|
||||
// use the config.hw_addr to generated an airplay_device_id
|
||||
{
|
||||
char obf[256] = {0};
|
||||
char *obfp = obf;
|
||||
int i;
|
||||
for (i = 0; i < 6; i++) {
|
||||
snprintf(obfp, 4, "%02X:", config.hw_addr[i]);
|
||||
obfp += 3;
|
||||
}
|
||||
obfp -= 1;
|
||||
*obfp = 0;
|
||||
config.airplay_device_id = strdup(obf);
|
||||
}
|
||||
debug(1, "Started in Airplay 2 mode on device \"%s\"!", config.airplay_device_id);
|
||||
|
||||
// now generate a UUID
|
||||
// from https://stackoverflow.com/questions/51053568/generating-a-random-uuid-in-c
|
||||
@@ -2014,12 +2034,13 @@ int main(int argc, char **argv) {
|
||||
debug(1, "loudness is %d.", config.loudness);
|
||||
debug(1, "loudness reference level is %f", config.loudness_reference_volume_db);
|
||||
|
||||
uint8_t ap_md5[16];
|
||||
|
||||
#ifdef CONFIG_SOXR
|
||||
pthread_create(&soxr_time_check_thread, NULL, &soxr_time_check, NULL);
|
||||
#endif
|
||||
|
||||
/*
|
||||
uint8_t ap_md5[16];
|
||||
|
||||
#ifdef CONFIG_OPENSSL
|
||||
MD5_CTX ctx;
|
||||
MD5_Init(&ctx);
|
||||
@@ -2047,7 +2068,10 @@ int main(int argc, char **argv) {
|
||||
md5_update(&tctx, (unsigned char *)config.service_name, strlen(config.service_name));
|
||||
md5_finish(&tctx, ap_md5);
|
||||
#endif
|
||||
|
||||
memcpy(config.hw_addr, ap_md5, sizeof(config.hw_addr));
|
||||
*/
|
||||
|
||||
#ifdef CONFIG_METADATA
|
||||
metadata_init(); // create the metadata pipe if necessary
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user