mirror of
https://github.com/linux-msm/qrtr.git
synced 2025-12-23 21:46:28 +01:00
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -52,20 +52,17 @@ jobs:
|
||||
#- ubuntu:xenial
|
||||
cross_compile: [""]
|
||||
variant: [""]
|
||||
nosystemd: [""]
|
||||
include:
|
||||
# Alpine (OpenRC)
|
||||
- container: "alpine:edge"
|
||||
arch: x86-64
|
||||
family: x86-64
|
||||
compiler: gcc
|
||||
nosystemd: true
|
||||
|
||||
- container: "alpine:latest"
|
||||
arch: x86-64
|
||||
family: x86-64
|
||||
compiler: gcc
|
||||
nosystemd: true
|
||||
|
||||
# Debian 32-bit builds
|
||||
- container: "debian:testing"
|
||||
@@ -218,7 +215,6 @@ jobs:
|
||||
- name: Meson init with cross compile
|
||||
if: ${{ matrix.variant == 'cross-compile' }}
|
||||
run: |
|
||||
# installing systemd (for pkg-config) has personality issues with cross-compilation
|
||||
# Generate cross compile file (see https://mesonbuild.com/Cross-compilation.html#cross-compilation)
|
||||
echo "[binaries]" > cross.txt
|
||||
echo "c = '${CROSS_COMPILE}-gcc'" >> cross.txt
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,4 +2,3 @@ out/
|
||||
*.so
|
||||
qrtr-cfg
|
||||
qrtr-lookup
|
||||
qrtr-ns
|
||||
|
||||
17
Android.bp
17
Android.bp
@@ -11,22 +11,6 @@ cc_library {
|
||||
local_include_dirs: ["src"],
|
||||
}
|
||||
|
||||
cc_binary {
|
||||
name: "qrtr-ns",
|
||||
vendor: true,
|
||||
srcs: [
|
||||
"lib/logging.c",
|
||||
"src/addr.c",
|
||||
"src/ns.c",
|
||||
"src/map.c",
|
||||
"src/hash.c",
|
||||
"src/waiter.c",
|
||||
"src/util.c",
|
||||
],
|
||||
cflags: ["-Wno-error"],
|
||||
local_include_dirs: ["lib"],
|
||||
}
|
||||
|
||||
cc_binary {
|
||||
name: "qrtr-cfg",
|
||||
vendor: true,
|
||||
@@ -45,7 +29,6 @@ cc_binary {
|
||||
srcs: [
|
||||
"lib/logging.c",
|
||||
"src/lookup.c",
|
||||
"src/util.c",
|
||||
],
|
||||
cflags: ["-Wno-error"],
|
||||
local_include_dirs: ["lib"],
|
||||
|
||||
@@ -17,7 +17,6 @@ case $CC in
|
||||
esac
|
||||
|
||||
pacman -Syu --noconfirm \
|
||||
systemd-libs \
|
||||
pkgconf \
|
||||
meson \
|
||||
$PKGS_CC
|
||||
|
||||
@@ -22,7 +22,4 @@ apt install -y --no-install-recommends \
|
||||
libc6-dev:${ARCH} \
|
||||
gcc-`dpkg-architecture -a ${ARCH} -q DEB_TARGET_GNU_TYPE`
|
||||
|
||||
# Thanks debian
|
||||
apt install -y --no-install-recommends systemd-dev:${ARCH} -a ${ARCH} || true
|
||||
|
||||
echo "Install finished: $0"
|
||||
|
||||
@@ -30,11 +30,7 @@ esac
|
||||
apt install -y --no-install-recommends \
|
||||
pkg-config \
|
||||
meson \
|
||||
systemd \
|
||||
libc6-dev \
|
||||
$PKGS_CC
|
||||
|
||||
# Thanks debian
|
||||
apt install -y --no-install-recommends systemd-dev || true
|
||||
|
||||
echo "Install finished: $0"
|
||||
|
||||
@@ -20,8 +20,6 @@ dnf -y install \
|
||||
meson \
|
||||
pkg-config \
|
||||
libudev-devel \
|
||||
systemd-devel \
|
||||
systemd-libs \
|
||||
$PKGS_CC
|
||||
|
||||
echo "Install finished: $0"
|
||||
|
||||
25
meson.build
25
meson.build
@@ -10,33 +10,8 @@ project('qrtr',
|
||||
])
|
||||
|
||||
prefix = get_option('prefix')
|
||||
with_qrtr_ns = get_option('qrtr-ns')
|
||||
|
||||
install_systemd_unit = get_option('systemd-service')
|
||||
systemd = dependency('systemd', required : install_systemd_unit)
|
||||
if systemd.found()
|
||||
systemd_system_unit_dir = get_option('systemd-unit-prefix')
|
||||
if systemd_system_unit_dir == ''
|
||||
systemd_system_unit_dir = systemd.get_variable(
|
||||
pkgconfig : 'systemdsystemunitdir',
|
||||
pkgconfig_define: ['prefix', prefix])
|
||||
else
|
||||
message('Could not resolve systemd dependencies, skipping unit file')
|
||||
install_systemd_unit = false
|
||||
endif
|
||||
endif
|
||||
|
||||
inc = include_directories('include')
|
||||
subdir('lib')
|
||||
subdir('include')
|
||||
subdir('src')
|
||||
|
||||
if systemd.found() and with_qrtr_ns.enabled()
|
||||
systemd_unit_conf = configuration_data()
|
||||
systemd_unit_conf.set('prefix', prefix)
|
||||
configure_file(
|
||||
input : 'qrtr-ns.service.in',
|
||||
output : 'qrtr-ns.service',
|
||||
configuration : systemd_unit_conf,
|
||||
install_dir : systemd_system_unit_dir)
|
||||
endif
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
option('qrtr-ns',
|
||||
type: 'feature',
|
||||
value: 'auto',
|
||||
description: 'Whether or not to build the qrtr-ns binary'
|
||||
)
|
||||
|
||||
option('systemd-unit-prefix',
|
||||
type: 'string',
|
||||
description: 'Directory for systemd system unit files'
|
||||
)
|
||||
|
||||
option('systemd-service',
|
||||
type: 'feature',
|
||||
value: 'auto',
|
||||
description: 'Whether or not the systemd service should be built'
|
||||
)
|
||||
@@ -1,9 +0,0 @@
|
||||
[Unit]
|
||||
Description=QIPCRTR Name Service
|
||||
|
||||
[Service]
|
||||
ExecStart=@prefix@/bin/qrtr-ns -f 1
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
37
src/hash.c
37
src/hash.c
@@ -1,37 +0,0 @@
|
||||
#include <string.h>
|
||||
#include "hash.h"
|
||||
|
||||
unsigned int hash_mem(const void *data, unsigned int len)
|
||||
{
|
||||
unsigned int h;
|
||||
unsigned int i;
|
||||
|
||||
h = len;
|
||||
|
||||
for (i = 0; i < len; ++i)
|
||||
h = ((h >> 27) ^ (h << 5)) ^ ((const unsigned char *)data)[i];
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
unsigned int hash_string(const char *value)
|
||||
{
|
||||
return hash_mem(value, strlen(value));
|
||||
}
|
||||
|
||||
unsigned int hash_u32(uint32_t value)
|
||||
{
|
||||
return value * 2654435761UL;
|
||||
}
|
||||
|
||||
unsigned int hash_u64(uint64_t value)
|
||||
{
|
||||
return hash_u32(value & 0xffffffff) ^ hash_u32(value >> 32);
|
||||
}
|
||||
|
||||
unsigned int hash_pointer(void *value)
|
||||
{
|
||||
if (sizeof(value) == sizeof(uint64_t))
|
||||
return hash_u64((long)value);
|
||||
return hash_u32((long)value);
|
||||
}
|
||||
12
src/hash.h
12
src/hash.h
@@ -1,12 +0,0 @@
|
||||
#ifndef _HASH_H_
|
||||
#define _HASH_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
unsigned int hash_mem(const void *data, unsigned int len);
|
||||
unsigned int hash_string(const char *value);
|
||||
unsigned int hash_u32(uint32_t value);
|
||||
unsigned int hash_u64(uint64_t value);
|
||||
unsigned int hash_pointer(void *value);
|
||||
|
||||
#endif
|
||||
130
src/list.h
130
src/list.h
@@ -1,130 +0,0 @@
|
||||
#ifndef _LIST_H_
|
||||
#define _LIST_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifndef offsetof
|
||||
#define offsetof(type, md) ((size_t)&((type *)0)->md)
|
||||
#endif
|
||||
|
||||
#ifndef container_of
|
||||
#define container_of(ptr, type, member) \
|
||||
((type *)((char *)(ptr) - offsetof(type, member)))
|
||||
#endif
|
||||
|
||||
struct list_item {
|
||||
struct list_item *next;
|
||||
struct list_item *prev;
|
||||
};
|
||||
|
||||
struct list {
|
||||
struct list_item *head;
|
||||
struct list_item *tail;
|
||||
};
|
||||
|
||||
#define LIST_INIT(name) { 0, 0 }
|
||||
|
||||
#define LIST(name) \
|
||||
struct list name = LIST_INIT(name)
|
||||
|
||||
#define list_entry(ptr, type, member) \
|
||||
container_of(ptr, type, member)
|
||||
|
||||
static inline void list_init(struct list *list)
|
||||
{
|
||||
list->head = 0;
|
||||
list->tail = 0;
|
||||
}
|
||||
|
||||
static inline void list_append(struct list *list, struct list_item *item)
|
||||
{
|
||||
item->next = 0;
|
||||
item->prev = list->tail;
|
||||
if (list->tail != 0)
|
||||
list->tail->next = item;
|
||||
else
|
||||
list->head = item;
|
||||
list->tail = item;
|
||||
}
|
||||
|
||||
static inline void list_prepend(struct list *list, struct list_item *item)
|
||||
{
|
||||
item->prev = 0;
|
||||
item->next = list->head;
|
||||
if (list->head == 0)
|
||||
list->tail = item;
|
||||
list->head = item;
|
||||
}
|
||||
|
||||
static inline void list_insert(struct list *list, struct list_item *after, struct list_item *item)
|
||||
{
|
||||
if (after == 0) {
|
||||
list_prepend(list, item);
|
||||
return;
|
||||
}
|
||||
item->prev = after;
|
||||
item->next = after->next;
|
||||
after->next = item;
|
||||
if (item->next)
|
||||
item->next->prev = item;
|
||||
if (list->tail == after)
|
||||
list->tail = item;
|
||||
}
|
||||
|
||||
static inline void list_remove(struct list *list, struct list_item *item)
|
||||
{
|
||||
if (item->next)
|
||||
item->next->prev = item->prev;
|
||||
if (list->head == item) {
|
||||
list->head = item->next;
|
||||
if (list->head == 0)
|
||||
list->tail = 0;
|
||||
} else {
|
||||
item->prev->next = item->next;
|
||||
if (list->tail == item)
|
||||
list->tail = item->prev;
|
||||
}
|
||||
item->prev = item->next = 0;
|
||||
}
|
||||
|
||||
static inline struct list_item *list_pop(struct list *list)
|
||||
{
|
||||
struct list_item *item;
|
||||
item = list->head;
|
||||
if (item == 0)
|
||||
return 0;
|
||||
list_remove(list, item);
|
||||
return item;
|
||||
}
|
||||
|
||||
static inline struct list_item *list_last(struct list *list)
|
||||
{
|
||||
return list->tail;
|
||||
}
|
||||
|
||||
static inline struct list_item *list_first(struct list *list)
|
||||
{
|
||||
return list->head;
|
||||
}
|
||||
|
||||
|
||||
static inline struct list_item *list_next(struct list_item *item)
|
||||
{
|
||||
return item->next;
|
||||
}
|
||||
|
||||
#define list_push list_append
|
||||
|
||||
#define list_for_each(_list, _iter) \
|
||||
for (_iter = (_list)->head; (_iter) != 0; _iter = (_iter)->next)
|
||||
|
||||
#define list_for_each_after(_node, _iter) \
|
||||
for (_iter = (_node)->next; (_iter) != 0; _iter = (_iter)->next)
|
||||
|
||||
#define list_for_each_safe(_list, _iter, _bkup) \
|
||||
for (_iter = (_list)->head; (_iter) != 0 && ((_bkup = (_iter)->next) || 1); _iter = (_bkup))
|
||||
|
||||
#define list_for_each_safe_after(_node, _iter, _bkup) \
|
||||
for (_iter = (_node)->next; (_iter) != 0 && ((_bkup = (_iter)->next) || 1); _iter = (_bkup))
|
||||
|
||||
#endif
|
||||
@@ -13,7 +13,6 @@
|
||||
|
||||
#include "logging.h"
|
||||
#include "ns.h"
|
||||
#include "util.h"
|
||||
|
||||
#define DIAG_SERVICE 4097
|
||||
|
||||
|
||||
233
src/map.c
233
src/map.c
@@ -1,233 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2008-2009, Courtney Cavin
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "map.h"
|
||||
|
||||
struct map_entry {
|
||||
struct map_item *item;
|
||||
};
|
||||
|
||||
/* Marker for deleted items */
|
||||
static struct map_item deleted;
|
||||
|
||||
void map_destroy(struct map *map)
|
||||
{
|
||||
free(map->data);
|
||||
}
|
||||
|
||||
void map_clear(struct map *map, void (*release)(struct map_item *))
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < map->size; ++i){
|
||||
if (!map->data[i].item)
|
||||
continue;
|
||||
if (map->data[i].item != &deleted)
|
||||
(* release)(map->data[i].item);
|
||||
map->data[i].item = NULL;
|
||||
}
|
||||
map->count = 0;
|
||||
}
|
||||
|
||||
int map_create(struct map *map)
|
||||
{
|
||||
map->size = 0;
|
||||
map->data = 0;
|
||||
map->count = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int map_hash(struct map *map, unsigned int key)
|
||||
{
|
||||
struct map_entry *e;
|
||||
int idx, i;
|
||||
|
||||
if (map->count == map->size)
|
||||
return -1;
|
||||
|
||||
idx = key % map->size;
|
||||
|
||||
for (i = 0; i < map->size; ++i) {
|
||||
e = &map->data[idx];
|
||||
if (!e->item || e->item == &deleted) {
|
||||
++map->count;
|
||||
return idx;
|
||||
}
|
||||
if (e->item->key == key)
|
||||
return idx;
|
||||
|
||||
idx = (idx + 1) % map->size;
|
||||
}
|
||||
|
||||
return -2;
|
||||
}
|
||||
|
||||
static int map_rehash(struct map *map);
|
||||
|
||||
int map_reput(struct map *map, unsigned int key, struct map_item *value,
|
||||
struct map_item **old)
|
||||
{
|
||||
int rc;
|
||||
|
||||
while ((rc = map_hash(map, key)) < 0) {
|
||||
if ((rc = map_rehash(map)) < 0)
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (old) {
|
||||
if (map->data[rc].item == &deleted)
|
||||
*old = NULL;
|
||||
else
|
||||
*old = map->data[rc].item;
|
||||
}
|
||||
map->data[rc].item = value;
|
||||
if (value)
|
||||
map->data[rc].item->key = key;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int map_put(struct map *map, unsigned int key, struct map_item *value)
|
||||
{
|
||||
return map_reput(map, key, value, NULL);
|
||||
}
|
||||
|
||||
static int map_rehash(struct map *map)
|
||||
{
|
||||
struct map_entry *oldt, *newt;
|
||||
int o_size, i;
|
||||
int rc;
|
||||
|
||||
newt = calloc(sizeof(struct map_entry), map->size + 256);
|
||||
if (!newt)
|
||||
return -1;
|
||||
|
||||
oldt = map->data;
|
||||
map->data = newt;
|
||||
|
||||
o_size = map->size;
|
||||
map->size += 256;
|
||||
map->count = 0;
|
||||
|
||||
for (i = 0; i < o_size; ++i){
|
||||
if (!oldt[i].item || oldt[i].item == &deleted)
|
||||
continue;
|
||||
rc = map_put(map, oldt[i].item->key, oldt[i].item);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
}
|
||||
|
||||
free(oldt);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct map_entry *map_find(const struct map *map, unsigned int key)
|
||||
{
|
||||
struct map_entry *e;
|
||||
int idx, i;
|
||||
|
||||
if (map->size == 0)
|
||||
return NULL;
|
||||
|
||||
idx = key % map->size;
|
||||
|
||||
for (i = 0; i < map->size; ++i) {
|
||||
e = &map->data[idx];
|
||||
idx = (idx + 1) % map->size;
|
||||
|
||||
if (!e->item)
|
||||
break;
|
||||
if (e->item == &deleted)
|
||||
continue;
|
||||
if (e->item->key == key)
|
||||
return e;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int map_contains(const struct map *map, unsigned int key)
|
||||
{
|
||||
return (map_find(map, key) == NULL) ? 0 : 1;
|
||||
}
|
||||
|
||||
struct map_item *map_get(const struct map *map, unsigned int key)
|
||||
{
|
||||
struct map_entry *e;
|
||||
|
||||
e = map_find(map, key);
|
||||
if (e == NULL)
|
||||
return NULL;
|
||||
return e->item;
|
||||
}
|
||||
|
||||
int map_remove(struct map *map, unsigned int key)
|
||||
{
|
||||
struct map_entry *e;
|
||||
|
||||
e = map_find(map, key);
|
||||
if (e) {
|
||||
e->item = &deleted;
|
||||
--map->count;
|
||||
}
|
||||
return !e;
|
||||
}
|
||||
|
||||
unsigned int map_length(struct map *map)
|
||||
{
|
||||
return map ? map->count : 0;
|
||||
}
|
||||
|
||||
static struct map_entry *map_iter_from(const struct map *map, unsigned int start)
|
||||
{
|
||||
unsigned int i = start;
|
||||
|
||||
for (; i < map->size; ++i) {
|
||||
if (map->data[i].item && map->data[i].item != &deleted)
|
||||
return &map->data[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct map_entry *map_iter_next(const struct map *map, struct map_entry *iter)
|
||||
{
|
||||
if (iter == NULL)
|
||||
return NULL;
|
||||
|
||||
return map_iter_from(map, (iter - map->data) + 1);
|
||||
}
|
||||
|
||||
struct map_entry *map_iter_first(const struct map *map)
|
||||
{
|
||||
return map_iter_from(map, 0);
|
||||
}
|
||||
|
||||
|
||||
struct map_item *map_iter_item(struct map_entry *iter)
|
||||
{
|
||||
return iter->item;
|
||||
}
|
||||
38
src/map.h
38
src/map.h
@@ -1,38 +0,0 @@
|
||||
#ifndef _MAP_H_
|
||||
#define _MAP_H_
|
||||
|
||||
struct map_item {
|
||||
unsigned int key;
|
||||
};
|
||||
|
||||
struct map_entry;
|
||||
|
||||
struct map {
|
||||
unsigned int size;
|
||||
unsigned int count;
|
||||
struct map_entry *data;
|
||||
};
|
||||
|
||||
int map_create(struct map *map);
|
||||
void map_destroy(struct map *map);
|
||||
void map_clear(struct map *map, void (*release)(struct map_item *));
|
||||
|
||||
int map_put(struct map *map, unsigned int key, struct map_item *v);
|
||||
int map_reput(struct map *map, unsigned int key, struct map_item *v,
|
||||
struct map_item **old);
|
||||
int map_contains(const struct map *map, unsigned int key);
|
||||
struct map_item *map_get(const struct map *map, unsigned int key);
|
||||
int map_remove(struct map *map, unsigned int key);
|
||||
unsigned int map_length(struct map *map);
|
||||
|
||||
struct map_entry *map_iter_first(const struct map *map);
|
||||
struct map_entry *map_iter_next(const struct map *map, struct map_entry *iter);
|
||||
struct map_item *map_iter_item(struct map_entry *iter);
|
||||
|
||||
#define map_for_each(map, iter) \
|
||||
for (iter = map_iter_first(map); iter; iter = map_iter_next(map, iter))
|
||||
|
||||
#define map_iter_data(iter, type, member) \
|
||||
container_of(map_iter_item(iter), type, member)
|
||||
|
||||
#endif
|
||||
@@ -1,30 +1,15 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cfg_srcs = ['addr.c',
|
||||
'cfg.c',
|
||||
'hash.c']
|
||||
'cfg.c']
|
||||
executable('qrtr-cfg',
|
||||
cfg_srcs,
|
||||
link_with : libqrtr,
|
||||
include_directories : inc,
|
||||
install : true)
|
||||
|
||||
if with_qrtr_ns.enabled()
|
||||
ns_srcs = ['addr.c',
|
||||
'hash.c',
|
||||
'map.c',
|
||||
'ns.c',
|
||||
'util.c',
|
||||
'waiter.c']
|
||||
executable('qrtr-ns',
|
||||
ns_srcs,
|
||||
link_with : libqrtr,
|
||||
include_directories : inc,
|
||||
install : true)
|
||||
endif
|
||||
|
||||
executable('qrtr-lookup',
|
||||
'lookup.c',
|
||||
link_with : libqrtr,
|
||||
include_directories : inc,
|
||||
install : true)
|
||||
install : true)
|
||||
|
||||
807
src/ns.c
807
src/ns.c
@@ -1,807 +0,0 @@
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <libqrtr.h>
|
||||
#include <libgen.h>
|
||||
#include <limits.h>
|
||||
#include <linux/qrtr.h>
|
||||
#include <linux/netlink.h>
|
||||
#include <linux/rtnetlink.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "addr.h"
|
||||
#include "hash.h"
|
||||
#include "list.h"
|
||||
#include "map.h"
|
||||
#include "ns.h"
|
||||
#include "util.h"
|
||||
#include "waiter.h"
|
||||
|
||||
#include "logging.h"
|
||||
|
||||
static const char *ctrl_pkt_strings[] = {
|
||||
[QRTR_TYPE_HELLO] = "hello",
|
||||
[QRTR_TYPE_BYE] = "bye",
|
||||
[QRTR_TYPE_NEW_SERVER] = "new-server",
|
||||
[QRTR_TYPE_DEL_SERVER] = "del-server",
|
||||
[QRTR_TYPE_DEL_CLIENT] = "del-client",
|
||||
[QRTR_TYPE_RESUME_TX] = "resume-tx",
|
||||
[QRTR_TYPE_EXIT] = "exit",
|
||||
[QRTR_TYPE_PING] = "ping",
|
||||
[QRTR_TYPE_NEW_LOOKUP] = "new-lookup",
|
||||
[QRTR_TYPE_DEL_LOOKUP] = "del-lookup",
|
||||
};
|
||||
|
||||
#define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0]))
|
||||
|
||||
struct context {
|
||||
int sock;
|
||||
|
||||
int local_node;
|
||||
|
||||
struct sockaddr_qrtr bcast_sq;
|
||||
|
||||
struct list lookups;
|
||||
};
|
||||
|
||||
struct server_filter {
|
||||
unsigned int service;
|
||||
unsigned int instance;
|
||||
unsigned int ifilter;
|
||||
};
|
||||
|
||||
struct lookup {
|
||||
unsigned int service;
|
||||
unsigned int instance;
|
||||
|
||||
struct sockaddr_qrtr sq;
|
||||
struct list_item li;
|
||||
};
|
||||
|
||||
struct server {
|
||||
unsigned int service;
|
||||
unsigned int instance;
|
||||
|
||||
unsigned int node;
|
||||
unsigned int port;
|
||||
struct map_item mi;
|
||||
struct list_item qli;
|
||||
};
|
||||
|
||||
struct node {
|
||||
unsigned int id;
|
||||
|
||||
struct map_item mi;
|
||||
struct map services;
|
||||
};
|
||||
|
||||
static struct map nodes;
|
||||
|
||||
static void server_mi_free(struct map_item *mi);
|
||||
|
||||
static struct node *node_get(unsigned int node_id)
|
||||
{
|
||||
struct map_item *mi;
|
||||
struct node *node;
|
||||
int rc;
|
||||
|
||||
mi = map_get(&nodes, hash_u32(node_id));
|
||||
if (mi)
|
||||
return container_of(mi, struct node, mi);
|
||||
|
||||
node = calloc(1, sizeof(*node));
|
||||
if (!node)
|
||||
return NULL;
|
||||
|
||||
node->id = node_id;
|
||||
|
||||
rc = map_create(&node->services);
|
||||
if (rc)
|
||||
LOGE_AND_EXIT("unable to create map");
|
||||
|
||||
rc = map_put(&nodes, hash_u32(node_id), &node->mi);
|
||||
if (rc) {
|
||||
map_destroy(&node->services);
|
||||
free(node);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
static int server_match(const struct server *srv, const struct server_filter *f)
|
||||
{
|
||||
unsigned int ifilter = f->ifilter;
|
||||
|
||||
if (f->service != 0 && srv->service != f->service)
|
||||
return 0;
|
||||
if (!ifilter && f->instance)
|
||||
ifilter = ~0;
|
||||
return (srv->instance & ifilter) == f->instance;
|
||||
}
|
||||
|
||||
static int server_query(const struct server_filter *f, struct list *list)
|
||||
{
|
||||
struct map_entry *node_me;
|
||||
struct map_entry *me;
|
||||
struct node *node;
|
||||
int count = 0;
|
||||
|
||||
list_init(list);
|
||||
map_for_each(&nodes, node_me) {
|
||||
node = map_iter_data(node_me, struct node, mi);
|
||||
|
||||
map_for_each(&node->services, me) {
|
||||
struct server *srv;
|
||||
|
||||
srv = map_iter_data(me, struct server, mi);
|
||||
if (!server_match(srv, f))
|
||||
continue;
|
||||
|
||||
list_append(list, &srv->qli);
|
||||
++count;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static int service_announce_new(struct context *ctx,
|
||||
struct sockaddr_qrtr *dest,
|
||||
struct server *srv)
|
||||
{
|
||||
struct qrtr_ctrl_pkt cmsg;
|
||||
int rc;
|
||||
|
||||
LOGD("advertising new server [%u:%x]@[%u:%u]\n",
|
||||
srv->service, srv->instance, srv->node, srv->port);
|
||||
|
||||
cmsg.cmd = cpu_to_le32(QRTR_TYPE_NEW_SERVER);
|
||||
cmsg.server.service = cpu_to_le32(srv->service);
|
||||
cmsg.server.instance = cpu_to_le32(srv->instance);
|
||||
cmsg.server.node = cpu_to_le32(srv->node);
|
||||
cmsg.server.port = cpu_to_le32(srv->port);
|
||||
|
||||
rc = sendto(ctx->sock, &cmsg, sizeof(cmsg), 0,
|
||||
(struct sockaddr *)dest, sizeof(*dest));
|
||||
if (rc < 0)
|
||||
PLOGW("sendto()");
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int service_announce_del(struct context *ctx,
|
||||
struct sockaddr_qrtr *dest,
|
||||
struct server *srv)
|
||||
{
|
||||
struct qrtr_ctrl_pkt cmsg;
|
||||
int rc;
|
||||
|
||||
LOGD("advertising removal of server [%u:%x]@[%u:%u]\n",
|
||||
srv->service, srv->instance, srv->node, srv->port);
|
||||
|
||||
cmsg.cmd = cpu_to_le32(QRTR_TYPE_DEL_SERVER);
|
||||
cmsg.server.service = cpu_to_le32(srv->service);
|
||||
cmsg.server.instance = cpu_to_le32(srv->instance);
|
||||
cmsg.server.node = cpu_to_le32(srv->node);
|
||||
cmsg.server.port = cpu_to_le32(srv->port);
|
||||
|
||||
rc = sendto(ctx->sock, &cmsg, sizeof(cmsg), 0,
|
||||
(struct sockaddr *)dest, sizeof(*dest));
|
||||
if (rc < 0)
|
||||
PLOGW("sendto()");
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int lookup_notify(struct context *ctx, struct sockaddr_qrtr *to,
|
||||
struct server *srv, bool new)
|
||||
{
|
||||
struct qrtr_ctrl_pkt pkt = {};
|
||||
int rc;
|
||||
|
||||
pkt.cmd = new ? QRTR_TYPE_NEW_SERVER : QRTR_TYPE_DEL_SERVER;
|
||||
if (srv) {
|
||||
pkt.server.service = cpu_to_le32(srv->service);
|
||||
pkt.server.instance = cpu_to_le32(srv->instance);
|
||||
pkt.server.node = cpu_to_le32(srv->node);
|
||||
pkt.server.port = cpu_to_le32(srv->port);
|
||||
}
|
||||
|
||||
rc = sendto(ctx->sock, &pkt, sizeof(pkt), 0,
|
||||
(struct sockaddr *)to, sizeof(*to));
|
||||
if (rc < 0)
|
||||
PLOGW("send lookup result failed");
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int annouce_servers(struct context *ctx, struct sockaddr_qrtr *sq)
|
||||
{
|
||||
struct map_entry *me;
|
||||
struct server *srv;
|
||||
struct node *node;
|
||||
int rc;
|
||||
|
||||
node = node_get(ctx->local_node);
|
||||
if (!node)
|
||||
return 0;
|
||||
|
||||
map_for_each(&node->services, me) {
|
||||
srv = map_iter_data(me, struct server, mi);
|
||||
|
||||
rc = service_announce_new(ctx, sq, srv);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct server *server_add(unsigned int service, unsigned int instance,
|
||||
unsigned int node_id, unsigned int port)
|
||||
{
|
||||
struct map_item *mi;
|
||||
struct server *srv;
|
||||
struct node *node;
|
||||
int rc;
|
||||
|
||||
if (!service || !port)
|
||||
return NULL;
|
||||
|
||||
srv = calloc(1, sizeof(*srv));
|
||||
if (srv == NULL)
|
||||
return NULL;
|
||||
|
||||
srv->service = service;
|
||||
srv->instance = instance;
|
||||
srv->node = node_id;
|
||||
srv->port = port;
|
||||
|
||||
node = node_get(node_id);
|
||||
if (!node)
|
||||
goto err;
|
||||
|
||||
rc = map_reput(&node->services, hash_u32(port), &srv->mi, &mi);
|
||||
if (rc)
|
||||
goto err;
|
||||
|
||||
LOGD("add server [%u:%x]@[%u:%u]\n", srv->service, srv->instance,
|
||||
srv->node, srv->port);
|
||||
|
||||
if (mi) { /* we replaced someone */
|
||||
struct server *old = container_of(mi, struct server, mi);
|
||||
free(old);
|
||||
}
|
||||
|
||||
return srv;
|
||||
|
||||
err:
|
||||
free(srv);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int server_del(struct context *ctx, struct node *node, unsigned int port)
|
||||
{
|
||||
struct lookup *lookup;
|
||||
struct list_item *li;
|
||||
struct map_item *mi;
|
||||
struct server *srv;
|
||||
|
||||
mi = map_get(&node->services, hash_u32(port));
|
||||
if (!mi)
|
||||
return -ENOENT;
|
||||
|
||||
srv = container_of(mi, struct server, mi);
|
||||
map_remove(&node->services, srv->mi.key);
|
||||
|
||||
/* Broadcast the removal of local services */
|
||||
if (srv->node == ctx->local_node)
|
||||
service_announce_del(ctx, &ctx->bcast_sq, srv);
|
||||
|
||||
/* Announce the service's disappearance to observers */
|
||||
list_for_each(&ctx->lookups, li) {
|
||||
lookup = container_of(li, struct lookup, li);
|
||||
if (lookup->service && lookup->service != srv->service)
|
||||
continue;
|
||||
if (lookup->instance && lookup->instance != srv->instance)
|
||||
continue;
|
||||
|
||||
lookup_notify(ctx, &lookup->sq, srv, false);
|
||||
}
|
||||
|
||||
free(srv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ctrl_cmd_hello(struct context *ctx, struct sockaddr_qrtr *sq,
|
||||
const void *buf, size_t len)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = sendto(ctx->sock, buf, len, 0, (void *)sq, sizeof(*sq));
|
||||
if (rc > 0)
|
||||
rc = annouce_servers(ctx, sq);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int ctrl_cmd_bye(struct context *ctx, struct sockaddr_qrtr *from)
|
||||
{
|
||||
struct qrtr_ctrl_pkt pkt;
|
||||
struct sockaddr_qrtr sq;
|
||||
struct node *local_node;
|
||||
struct map_entry *me;
|
||||
struct server *srv;
|
||||
struct node *node;
|
||||
int rc;
|
||||
|
||||
node = node_get(from->sq_node);
|
||||
if (!node)
|
||||
return 0;
|
||||
|
||||
map_for_each(&node->services, me) {
|
||||
srv = map_iter_data(me, struct server, mi);
|
||||
|
||||
server_del(ctx, node, srv->port);
|
||||
}
|
||||
|
||||
/* Advertise the removal of this client to all local services */
|
||||
local_node = node_get(ctx->local_node);
|
||||
if (!local_node)
|
||||
return 0;
|
||||
|
||||
memset(&pkt, 0, sizeof(pkt));
|
||||
pkt.cmd = QRTR_TYPE_BYE;
|
||||
pkt.client.node = from->sq_node;
|
||||
|
||||
map_for_each(&local_node->services, me) {
|
||||
srv = map_iter_data(me, struct server, mi);
|
||||
|
||||
sq.sq_family = AF_QIPCRTR;
|
||||
sq.sq_node = srv->node;
|
||||
sq.sq_port = srv->port;
|
||||
|
||||
rc = sendto(ctx->sock, &pkt, sizeof(pkt), 0,
|
||||
(struct sockaddr *)&sq, sizeof(sq));
|
||||
if (rc < 0)
|
||||
PLOGW("bye propagation failed");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ctrl_cmd_del_client(struct context *ctx, struct sockaddr_qrtr *from,
|
||||
unsigned node_id, unsigned port)
|
||||
{
|
||||
struct qrtr_ctrl_pkt pkt;
|
||||
struct sockaddr_qrtr sq;
|
||||
struct node *local_node;
|
||||
struct list_item *tmp;
|
||||
struct lookup *lookup;
|
||||
struct list_item *li;
|
||||
struct map_entry *me;
|
||||
struct server *srv;
|
||||
struct node *node;
|
||||
int rc;
|
||||
|
||||
/* Don't accept spoofed messages */
|
||||
if (from->sq_node != node_id)
|
||||
return -EINVAL;
|
||||
|
||||
/* Local DEL_CLIENT messages comes from the port being closed */
|
||||
if (from->sq_node == ctx->local_node && from->sq_port != port)
|
||||
return -EINVAL;
|
||||
|
||||
/* Remove any lookups by this client */
|
||||
list_for_each_safe(&ctx->lookups, li, tmp) {
|
||||
lookup = container_of(li, struct lookup, li);
|
||||
if (lookup->sq.sq_node != node_id)
|
||||
continue;
|
||||
if (lookup->sq.sq_port != port)
|
||||
continue;
|
||||
|
||||
list_remove(&ctx->lookups, &lookup->li);
|
||||
free(lookup);
|
||||
}
|
||||
|
||||
/* Remove the server belonging to this port*/
|
||||
node = node_get(node_id);
|
||||
if (node)
|
||||
server_del(ctx, node, port);
|
||||
|
||||
/* Advertise the removal of this client to all local services */
|
||||
local_node = node_get(ctx->local_node);
|
||||
if (!local_node)
|
||||
return 0;
|
||||
|
||||
pkt.cmd = QRTR_TYPE_DEL_CLIENT;
|
||||
pkt.client.node = node_id;
|
||||
pkt.client.port = port;
|
||||
|
||||
map_for_each(&local_node->services, me) {
|
||||
srv = map_iter_data(me, struct server, mi);
|
||||
|
||||
sq.sq_family = AF_QIPCRTR;
|
||||
sq.sq_node = srv->node;
|
||||
sq.sq_port = srv->port;
|
||||
|
||||
rc = sendto(ctx->sock, &pkt, sizeof(pkt), 0,
|
||||
(struct sockaddr *)&sq, sizeof(sq));
|
||||
if (rc < 0)
|
||||
PLOGW("del_client propagation failed");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ctrl_cmd_new_server(struct context *ctx, struct sockaddr_qrtr *from,
|
||||
unsigned int service, unsigned int instance,
|
||||
unsigned int node_id, unsigned int port)
|
||||
{
|
||||
struct lookup *lookup;
|
||||
struct list_item *li;
|
||||
struct server *srv;
|
||||
int rc = 0;
|
||||
|
||||
/* Ignore specified node and port for local servers*/
|
||||
if (from->sq_node == ctx->local_node) {
|
||||
node_id = from->sq_node;
|
||||
port = from->sq_port;
|
||||
}
|
||||
|
||||
/* Don't accept spoofed messages */
|
||||
if (from->sq_node != node_id)
|
||||
return -EINVAL;
|
||||
|
||||
srv = server_add(service, instance, node_id, port);
|
||||
if (!srv)
|
||||
return -EINVAL;
|
||||
|
||||
if (srv->node == ctx->local_node)
|
||||
rc = service_announce_new(ctx, &ctx->bcast_sq, srv);
|
||||
|
||||
list_for_each(&ctx->lookups, li) {
|
||||
lookup = container_of(li, struct lookup, li);
|
||||
if (lookup->service && lookup->service != service)
|
||||
continue;
|
||||
if (lookup->instance && lookup->instance != instance)
|
||||
continue;
|
||||
|
||||
lookup_notify(ctx, &lookup->sq, srv, true);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int ctrl_cmd_del_server(struct context *ctx, struct sockaddr_qrtr *from,
|
||||
unsigned int service, unsigned int instance,
|
||||
unsigned int node_id, unsigned int port)
|
||||
{
|
||||
struct node *node;
|
||||
|
||||
/* Ignore specified node and port for local servers*/
|
||||
if (from->sq_node == ctx->local_node) {
|
||||
node_id = from->sq_node;
|
||||
port = from->sq_port;
|
||||
}
|
||||
|
||||
/* Don't accept spoofed messages */
|
||||
if (from->sq_node != node_id)
|
||||
return -EINVAL;
|
||||
|
||||
/* Local servers may only unregister themselves */
|
||||
if (from->sq_node == ctx->local_node && from->sq_port != port)
|
||||
return -EINVAL;
|
||||
|
||||
node = node_get(node_id);
|
||||
if (!node)
|
||||
return -ENOENT;
|
||||
|
||||
return server_del(ctx, node, port);
|
||||
}
|
||||
|
||||
static int ctrl_cmd_new_lookup(struct context *ctx, struct sockaddr_qrtr *from,
|
||||
unsigned int service, unsigned int instance)
|
||||
{
|
||||
struct server_filter filter;
|
||||
struct list reply_list;
|
||||
struct lookup *lookup;
|
||||
struct list_item *li;
|
||||
struct server *srv;
|
||||
|
||||
/* Accept only local observers */
|
||||
if (from->sq_node != ctx->local_node)
|
||||
return -EINVAL;
|
||||
|
||||
lookup = calloc(1, sizeof(*lookup));
|
||||
if (!lookup)
|
||||
return -EINVAL;
|
||||
|
||||
lookup->sq = *from;
|
||||
lookup->service = service;
|
||||
lookup->instance = instance;
|
||||
list_append(&ctx->lookups, &lookup->li);
|
||||
|
||||
memset(&filter, 0, sizeof(filter));
|
||||
filter.service = service;
|
||||
filter.instance = instance;
|
||||
|
||||
server_query(&filter, &reply_list);
|
||||
list_for_each(&reply_list, li) {
|
||||
srv = container_of(li, struct server, qli);
|
||||
|
||||
lookup_notify(ctx, from, srv, true);
|
||||
}
|
||||
|
||||
lookup_notify(ctx, from, NULL, true);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ctrl_cmd_del_lookup(struct context *ctx, struct sockaddr_qrtr *from,
|
||||
unsigned int service, unsigned int instance)
|
||||
{
|
||||
struct lookup *lookup;
|
||||
struct list_item *tmp;
|
||||
struct list_item *li;
|
||||
|
||||
list_for_each_safe(&ctx->lookups, li, tmp) {
|
||||
lookup = container_of(li, struct lookup, li);
|
||||
if (lookup->sq.sq_node != from->sq_node)
|
||||
continue;
|
||||
if (lookup->sq.sq_port != from->sq_port)
|
||||
continue;
|
||||
if (lookup->service != service)
|
||||
continue;
|
||||
if (lookup->instance && lookup->instance != instance)
|
||||
continue;
|
||||
|
||||
list_remove(&ctx->lookups, &lookup->li);
|
||||
free(lookup);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ctrl_port_fn(void *vcontext, struct waiter_ticket *tkt)
|
||||
{
|
||||
struct context *ctx = vcontext;
|
||||
struct sockaddr_qrtr sq;
|
||||
int sock = ctx->sock;
|
||||
struct qrtr_ctrl_pkt *msg;
|
||||
unsigned int cmd;
|
||||
char buf[4096];
|
||||
socklen_t sl;
|
||||
ssize_t len;
|
||||
int rc;
|
||||
|
||||
sl = sizeof(sq);
|
||||
len = recvfrom(sock, buf, sizeof(buf), 0, (void *)&sq, &sl);
|
||||
if (len <= 0) {
|
||||
PLOGW("recvfrom()");
|
||||
close(sock);
|
||||
ctx->sock = -1;
|
||||
goto out;
|
||||
}
|
||||
msg = (void *)buf;
|
||||
|
||||
if (len < 4) {
|
||||
LOGW("short packet from %u:%u", sq.sq_node, sq.sq_port);
|
||||
goto out;
|
||||
}
|
||||
|
||||
cmd = le32_to_cpu(msg->cmd);
|
||||
if (cmd < ARRAY_SIZE(ctrl_pkt_strings) && ctrl_pkt_strings[cmd])
|
||||
LOGD("%s from %u:%u\n", ctrl_pkt_strings[cmd], sq.sq_node, sq.sq_port);
|
||||
else
|
||||
LOGD("UNK (%08x) from %u:%u\n", cmd, sq.sq_node, sq.sq_port);
|
||||
|
||||
rc = 0;
|
||||
switch (cmd) {
|
||||
case QRTR_TYPE_HELLO:
|
||||
rc = ctrl_cmd_hello(ctx, &sq, buf, len);
|
||||
break;
|
||||
case QRTR_TYPE_BYE:
|
||||
rc = ctrl_cmd_bye(ctx, &sq);
|
||||
break;
|
||||
case QRTR_TYPE_DEL_CLIENT:
|
||||
rc = ctrl_cmd_del_client(ctx, &sq,
|
||||
le32_to_cpu(msg->client.node),
|
||||
le32_to_cpu(msg->client.port));
|
||||
break;
|
||||
case QRTR_TYPE_NEW_SERVER:
|
||||
rc = ctrl_cmd_new_server(ctx, &sq,
|
||||
le32_to_cpu(msg->server.service),
|
||||
le32_to_cpu(msg->server.instance),
|
||||
le32_to_cpu(msg->server.node),
|
||||
le32_to_cpu(msg->server.port));
|
||||
break;
|
||||
case QRTR_TYPE_DEL_SERVER:
|
||||
rc = ctrl_cmd_del_server(ctx, &sq,
|
||||
le32_to_cpu(msg->server.service),
|
||||
le32_to_cpu(msg->server.instance),
|
||||
le32_to_cpu(msg->server.node),
|
||||
le32_to_cpu(msg->server.port));
|
||||
break;
|
||||
case QRTR_TYPE_EXIT:
|
||||
case QRTR_TYPE_PING:
|
||||
case QRTR_TYPE_RESUME_TX:
|
||||
break;
|
||||
case QRTR_TYPE_NEW_LOOKUP:
|
||||
rc = ctrl_cmd_new_lookup(ctx, &sq,
|
||||
le32_to_cpu(msg->server.service),
|
||||
le32_to_cpu(msg->server.instance));
|
||||
break;
|
||||
case QRTR_TYPE_DEL_LOOKUP:
|
||||
rc = ctrl_cmd_del_lookup(ctx, &sq,
|
||||
le32_to_cpu(msg->server.service),
|
||||
le32_to_cpu(msg->server.instance));
|
||||
break;
|
||||
}
|
||||
|
||||
if (rc < 0)
|
||||
LOGW("failed while handling packet from %u:%u",
|
||||
sq.sq_node, sq.sq_port);
|
||||
out:
|
||||
waiter_ticket_clear(tkt);
|
||||
}
|
||||
|
||||
static int say_hello(struct context *ctx)
|
||||
{
|
||||
struct qrtr_ctrl_pkt pkt;
|
||||
int rc;
|
||||
|
||||
memset(&pkt, 0, sizeof(pkt));
|
||||
pkt.cmd = cpu_to_le32(QRTR_TYPE_HELLO);
|
||||
|
||||
rc = sendto(ctx->sock, &pkt, sizeof(pkt), 0,
|
||||
(struct sockaddr *)&ctx->bcast_sq, sizeof(ctx->bcast_sq));
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void server_mi_free(struct map_item *mi)
|
||||
{
|
||||
free(container_of(mi, struct server, mi));
|
||||
}
|
||||
|
||||
static void node_mi_free(struct map_item *mi)
|
||||
{
|
||||
struct node *node = container_of(mi, struct node, mi);
|
||||
|
||||
map_clear(&node->services, server_mi_free);
|
||||
map_destroy(&node->services);
|
||||
|
||||
free(node);
|
||||
}
|
||||
|
||||
static void go_dormant(int sock)
|
||||
{
|
||||
close(sock);
|
||||
|
||||
for (;;)
|
||||
sleep(UINT_MAX);
|
||||
}
|
||||
|
||||
static void usage(const char *progname)
|
||||
{
|
||||
fprintf(stderr, "%s [-f] [-s] [<node-id>]\n", progname);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
struct waiter_ticket *tkt;
|
||||
struct sockaddr_qrtr sq;
|
||||
struct context ctx;
|
||||
unsigned long addr = (unsigned long)-1;
|
||||
struct waiter *w;
|
||||
socklen_t sl = sizeof(sq);
|
||||
bool foreground = false;
|
||||
bool use_syslog = false;
|
||||
bool verbose_log = false;
|
||||
char *ep;
|
||||
int opt;
|
||||
int rc;
|
||||
const char *progname = basename(argv[0]);
|
||||
|
||||
while ((opt = getopt(argc, argv, "fsv")) != -1) {
|
||||
switch (opt) {
|
||||
case 'f':
|
||||
foreground = true;
|
||||
break;
|
||||
case 's':
|
||||
use_syslog = true;
|
||||
break;
|
||||
case 'v':
|
||||
verbose_log = true;
|
||||
break;
|
||||
default:
|
||||
usage(progname);
|
||||
}
|
||||
}
|
||||
|
||||
qlog_setup(progname, use_syslog);
|
||||
if (verbose_log)
|
||||
qlog_set_min_priority(LOG_DEBUG);
|
||||
|
||||
if (optind < argc) {
|
||||
addr = strtoul(argv[optind], &ep, 10);
|
||||
if (argv[1][0] == '\0' || *ep != '\0' || addr >= UINT_MAX)
|
||||
usage(progname);
|
||||
|
||||
qrtr_set_address(addr);
|
||||
optind++;
|
||||
}
|
||||
|
||||
if (optind != argc)
|
||||
usage(progname);
|
||||
|
||||
w = waiter_create();
|
||||
if (w == NULL)
|
||||
LOGE_AND_EXIT("unable to create waiter");
|
||||
|
||||
list_init(&ctx.lookups);
|
||||
|
||||
rc = map_create(&nodes);
|
||||
if (rc)
|
||||
LOGE_AND_EXIT("unable to create node map");
|
||||
|
||||
ctx.sock = socket(AF_QIPCRTR, SOCK_DGRAM, 0);
|
||||
if (ctx.sock < 0)
|
||||
PLOGE_AND_EXIT("unable to create control socket");
|
||||
|
||||
rc = getsockname(ctx.sock, (void*)&sq, &sl);
|
||||
if (rc < 0)
|
||||
PLOGE_AND_EXIT("getsockname()");
|
||||
sq.sq_port = QRTR_PORT_CTRL;
|
||||
ctx.local_node = sq.sq_node;
|
||||
|
||||
rc = bind(ctx.sock, (void *)&sq, sizeof(sq));
|
||||
if (rc < 0) {
|
||||
if (errno == EADDRINUSE) {
|
||||
PLOGE("nameserver already running, going dormant");
|
||||
go_dormant(ctx.sock);
|
||||
}
|
||||
|
||||
PLOGE_AND_EXIT("bind control socket");
|
||||
}
|
||||
|
||||
ctx.bcast_sq.sq_family = AF_QIPCRTR;
|
||||
ctx.bcast_sq.sq_node = QRTR_NODE_BCAST;
|
||||
ctx.bcast_sq.sq_port = QRTR_PORT_CTRL;
|
||||
|
||||
rc = say_hello(&ctx);
|
||||
if (rc)
|
||||
PLOGE_AND_EXIT("unable to say hello");
|
||||
|
||||
/* If we're going to background, fork and exit parent */
|
||||
if (!foreground && fork() != 0) {
|
||||
close(ctx.sock);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
tkt = waiter_add_fd(w, ctx.sock);
|
||||
waiter_ticket_callback(tkt, ctrl_port_fn, &ctx);
|
||||
|
||||
while (ctx.sock >= 0)
|
||||
waiter_wait(w);
|
||||
|
||||
puts("exiting cleanly");
|
||||
|
||||
waiter_destroy(w);
|
||||
|
||||
map_clear(&nodes, node_mi_free);
|
||||
map_destroy(&nodes);
|
||||
|
||||
return 0;
|
||||
}
|
||||
18
src/util.c
18
src/util.c
@@ -1,18 +0,0 @@
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include "util.h"
|
||||
|
||||
uint64_t time_ms(void)
|
||||
{
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
return (uint64_t)tv.tv_sec*1000 + tv.tv_usec/1000;
|
||||
}
|
||||
|
||||
void util_sleep(int ms)
|
||||
{
|
||||
usleep(ms * 1000);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
#ifndef __UTIL_H_
|
||||
#define __UTIL_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
uint64_t time_ms(void);
|
||||
void util_sleep(int ms);
|
||||
|
||||
#endif
|
||||
378
src/waiter.c
378
src/waiter.c
@@ -1,378 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2014, Sony Mobile Communications Inc.
|
||||
* Copyright (c) 2014, Courtney Cavin
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* - Neither the name of the organization nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <poll.h>
|
||||
|
||||
#include "list.h"
|
||||
#include "waiter.h"
|
||||
#include "util.h"
|
||||
|
||||
struct pollset {
|
||||
int nfds;
|
||||
int cause;
|
||||
};
|
||||
|
||||
static struct pollset *pollset_create(int count)
|
||||
{
|
||||
struct pollset *ps;
|
||||
|
||||
ps = calloc(1, sizeof(*ps) + sizeof(struct pollfd) * count);
|
||||
if (ps == NULL)
|
||||
return NULL;
|
||||
|
||||
return ps;
|
||||
}
|
||||
|
||||
static void pollset_destroy(struct pollset *ps)
|
||||
{
|
||||
free(ps);
|
||||
}
|
||||
|
||||
static void pollset_reset(struct pollset *ps)
|
||||
{
|
||||
ps->nfds = 0;
|
||||
}
|
||||
|
||||
static void pollset_add_fd(struct pollset *ps, int fd)
|
||||
{
|
||||
struct pollfd *pfd = (struct pollfd *)(ps + 1);
|
||||
pfd[ps->nfds].fd = fd;
|
||||
pfd[ps->nfds].events = POLLERR | POLLIN;
|
||||
ps->nfds++;
|
||||
}
|
||||
|
||||
static int pollset_wait(struct pollset *ps, int ms)
|
||||
{
|
||||
struct pollfd *pfd = (struct pollfd *)(ps + 1);
|
||||
int rc;
|
||||
int i;
|
||||
|
||||
rc = poll(pfd, ps->nfds, ms);
|
||||
if (rc <= 0)
|
||||
return rc;
|
||||
|
||||
ps->cause = -1;
|
||||
for (i = 0; i < ps->nfds; ++i) {
|
||||
if (pfd[i].revents & (POLLERR | POLLIN)) {
|
||||
ps->cause = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
|
||||
}
|
||||
|
||||
static int pollset_cause_fd(struct pollset *ps, int fd)
|
||||
{
|
||||
struct pollfd *pfd = (struct pollfd *)(ps + 1);
|
||||
return (ps->cause >= 0 && pfd[ps->cause].fd == fd);
|
||||
}
|
||||
|
||||
enum waiter_type {
|
||||
WATCH_TYPE_NULL,
|
||||
WATCH_TYPE_FD,
|
||||
WATCH_TYPE_TIMEOUT,
|
||||
};
|
||||
|
||||
struct waiter_ticket {
|
||||
enum waiter_type type;
|
||||
union {
|
||||
int filedes;
|
||||
unsigned int event;
|
||||
unsigned int interval;
|
||||
};
|
||||
struct {
|
||||
void (* fn)(void *data, struct waiter_ticket *);
|
||||
void *data;
|
||||
} callback;
|
||||
|
||||
uint64_t start;
|
||||
int updated;
|
||||
struct waiter *waiter;
|
||||
struct list_item list_item;
|
||||
};
|
||||
|
||||
struct waiter {
|
||||
struct list tickets;
|
||||
struct pollset *pollset;
|
||||
int count;
|
||||
};
|
||||
|
||||
struct waiter *waiter_create(void)
|
||||
{
|
||||
struct waiter *w;
|
||||
|
||||
w = calloc(1, sizeof(*w));
|
||||
if (w == NULL)
|
||||
return NULL;
|
||||
|
||||
list_init(&w->tickets);
|
||||
return w;
|
||||
}
|
||||
|
||||
void waiter_destroy(struct waiter *w)
|
||||
{
|
||||
struct waiter_ticket *ticket;
|
||||
struct list_item *safe;
|
||||
struct list_item *node;
|
||||
|
||||
list_for_each_safe(&w->tickets, node, safe) {
|
||||
ticket = list_entry(node, struct waiter_ticket, list_item);
|
||||
free(ticket);
|
||||
}
|
||||
|
||||
if (w->pollset)
|
||||
pollset_destroy(w->pollset);
|
||||
free(w);
|
||||
}
|
||||
|
||||
void waiter_synchronize(struct waiter *w)
|
||||
{
|
||||
struct waiter_ticket *oticket;
|
||||
struct waiter_ticket *ticket;
|
||||
struct list_item *node;
|
||||
|
||||
list_for_each(&w->tickets, node) {
|
||||
struct list_item *onode;
|
||||
ticket = list_entry(node, struct waiter_ticket, list_item);
|
||||
|
||||
if (ticket->type != WATCH_TYPE_TIMEOUT)
|
||||
continue;
|
||||
|
||||
list_for_each_after(node, onode) {
|
||||
oticket = list_entry(onode, struct waiter_ticket, list_item);
|
||||
if (oticket->type != WATCH_TYPE_TIMEOUT)
|
||||
continue;
|
||||
|
||||
if (oticket->interval == ticket->interval) {
|
||||
oticket->start = ticket->start;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void waiter_wait(struct waiter *w)
|
||||
{
|
||||
struct pollset *ps = w->pollset;
|
||||
struct waiter_ticket *ticket;
|
||||
struct list_item *node;
|
||||
uint64_t term_time;
|
||||
uint64_t now;
|
||||
int rc;
|
||||
|
||||
pollset_reset(ps);
|
||||
|
||||
term_time = (uint64_t)-1;
|
||||
list_for_each(&w->tickets, node) {
|
||||
ticket = list_entry(node, struct waiter_ticket, list_item);
|
||||
switch (ticket->type) {
|
||||
case WATCH_TYPE_TIMEOUT:
|
||||
if (ticket->start + ticket->interval < term_time)
|
||||
term_time = ticket->start + ticket->interval;
|
||||
break;
|
||||
case WATCH_TYPE_FD:
|
||||
pollset_add_fd(ps, ticket->filedes);
|
||||
break;
|
||||
case WATCH_TYPE_NULL:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (term_time == (uint64_t)-1) { /* wait forever */
|
||||
rc = pollset_wait(ps, -1);
|
||||
} else {
|
||||
now = time_ms();
|
||||
if (now >= term_time) { /* already past timeout, skip poll */
|
||||
rc = 0;
|
||||
} else {
|
||||
uint64_t delta;
|
||||
|
||||
delta = term_time - now;
|
||||
if (delta > ((1u << 31) - 1))
|
||||
delta = ((1u << 31) - 1);
|
||||
rc = pollset_wait(ps, (int)delta);
|
||||
}
|
||||
}
|
||||
|
||||
if (rc < 0)
|
||||
return;
|
||||
|
||||
now = time_ms();
|
||||
list_for_each(&w->tickets, node) {
|
||||
int fresh = 0;
|
||||
|
||||
ticket = list_entry(node, struct waiter_ticket, list_item);
|
||||
switch (ticket->type) {
|
||||
case WATCH_TYPE_TIMEOUT:
|
||||
if (now >= ticket->start + ticket->interval) {
|
||||
ticket->start = now;
|
||||
fresh = !ticket->updated;
|
||||
}
|
||||
break;
|
||||
case WATCH_TYPE_FD:
|
||||
if (rc == 0) /* timed-out */
|
||||
break;
|
||||
if (pollset_cause_fd(ps, ticket->filedes))
|
||||
fresh = !ticket->updated;
|
||||
break;
|
||||
case WATCH_TYPE_NULL:
|
||||
break;
|
||||
}
|
||||
if (fresh) {
|
||||
ticket->updated = 1;
|
||||
if (ticket->callback.fn)
|
||||
(* ticket->callback.fn)(
|
||||
ticket->callback.data,
|
||||
ticket
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int waiter_wait_timeout(struct waiter *w, unsigned int ms)
|
||||
{
|
||||
struct waiter_ticket ticket;
|
||||
int rc;
|
||||
|
||||
memset(&ticket, 0, sizeof(ticket));
|
||||
waiter_ticket_set_timeout(&ticket, ms);
|
||||
list_append(&w->tickets, &ticket.list_item);
|
||||
w->count++;
|
||||
|
||||
waiter_wait(w);
|
||||
rc = waiter_ticket_check(&ticket);
|
||||
|
||||
list_remove(&w->tickets, &ticket.list_item);
|
||||
w->count--;
|
||||
|
||||
return -!rc;
|
||||
}
|
||||
|
||||
void waiter_ticket_set_null(struct waiter_ticket *ticket)
|
||||
{
|
||||
ticket->type = WATCH_TYPE_NULL;
|
||||
}
|
||||
|
||||
void waiter_ticket_set_fd(struct waiter_ticket *ticket, int fd)
|
||||
{
|
||||
ticket->type = WATCH_TYPE_FD;
|
||||
ticket->filedes = fd;
|
||||
}
|
||||
|
||||
void waiter_ticket_set_timeout(struct waiter_ticket *ticket, unsigned int ms)
|
||||
{
|
||||
ticket->type = WATCH_TYPE_TIMEOUT;
|
||||
ticket->interval = ms;
|
||||
ticket->start = time_ms();
|
||||
}
|
||||
|
||||
struct waiter_ticket *waiter_add_null(struct waiter *w)
|
||||
{
|
||||
struct waiter_ticket *ticket;
|
||||
|
||||
ticket = calloc(1, sizeof(*ticket));
|
||||
if (ticket == NULL)
|
||||
return NULL;
|
||||
ticket->waiter = w;
|
||||
|
||||
list_append(&w->tickets, &ticket->list_item);
|
||||
if ((w->count % 32) == 0) {
|
||||
if (w->pollset)
|
||||
pollset_destroy(w->pollset);
|
||||
w->pollset = pollset_create(w->count + 33);
|
||||
if (w->pollset == NULL)
|
||||
return NULL;
|
||||
}
|
||||
w->count++;
|
||||
|
||||
waiter_ticket_set_null(ticket);
|
||||
|
||||
return ticket;
|
||||
}
|
||||
|
||||
struct waiter_ticket *waiter_add_fd(struct waiter *w, int fd)
|
||||
{
|
||||
struct waiter_ticket *ticket;
|
||||
|
||||
ticket = waiter_add_null(w);
|
||||
if (ticket == NULL)
|
||||
return NULL;
|
||||
|
||||
waiter_ticket_set_fd(ticket, fd);
|
||||
|
||||
return ticket;
|
||||
}
|
||||
|
||||
struct waiter_ticket *waiter_add_timeout(struct waiter *w, unsigned int ms)
|
||||
{
|
||||
struct waiter_ticket *ticket;
|
||||
|
||||
ticket = waiter_add_null(w);
|
||||
if (ticket == NULL)
|
||||
return NULL;
|
||||
|
||||
waiter_ticket_set_timeout(ticket, ms);
|
||||
|
||||
return ticket;
|
||||
}
|
||||
|
||||
void waiter_ticket_delete(struct waiter_ticket *ticket)
|
||||
{
|
||||
struct waiter *w = ticket->waiter;
|
||||
list_remove(&w->tickets, &ticket->list_item);
|
||||
w->count--;
|
||||
free(ticket);
|
||||
}
|
||||
|
||||
void waiter_ticket_callback(struct waiter_ticket *ticket, waiter_ticket_cb_t cb_fn, void *data)
|
||||
{
|
||||
ticket->callback.fn = cb_fn;
|
||||
ticket->callback.data = data;
|
||||
}
|
||||
|
||||
int waiter_ticket_check(const struct waiter_ticket *ticket)
|
||||
{
|
||||
return -(ticket->updated == 0);
|
||||
}
|
||||
|
||||
int waiter_ticket_clear(struct waiter_ticket *ticket)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = waiter_ticket_check(ticket);
|
||||
ticket->updated = 0;
|
||||
|
||||
return ret;
|
||||
}
|
||||
103
src/waiter.h
103
src/waiter.h
@@ -1,103 +0,0 @@
|
||||
#ifndef _WAITER_H_
|
||||
#define _WAITER_H_
|
||||
|
||||
/** Waiter type. */
|
||||
struct waiter;
|
||||
|
||||
/** Create a new waiter.
|
||||
* @return Newly created waiter on success, NULL on failure.
|
||||
*/
|
||||
struct waiter *waiter_create(void);
|
||||
|
||||
/** Destroy existing waiter.
|
||||
* @param w waiter to destroy.
|
||||
*/
|
||||
void waiter_destroy(struct waiter *w);
|
||||
|
||||
/** Wait for next ticket.
|
||||
* @param w waiter.
|
||||
*/
|
||||
void waiter_wait(struct waiter *w);
|
||||
|
||||
/** Wait for next ticket or timeout.
|
||||
* @param w waiter.
|
||||
* @param ms timeout in milliseconds.
|
||||
* @return 0 on ticket; !0 on timeout.
|
||||
*/
|
||||
int waiter_wait_timeout(struct waiter *w, unsigned int ms);
|
||||
|
||||
/** Synchronize timer-based tickets.
|
||||
* @param w waiter.
|
||||
*/
|
||||
void waiter_synchronize(struct waiter *w);
|
||||
|
||||
/** Waiter ticket type. */
|
||||
struct waiter_ticket;
|
||||
|
||||
/** Add a null wait ticket to pool.
|
||||
* @param w waiter
|
||||
* @return wait ticket on success; NULL on failure.
|
||||
*/
|
||||
struct waiter_ticket *waiter_add_null(struct waiter *w);
|
||||
|
||||
/** Add a file descriptor to the pool.
|
||||
* @param w waiter.
|
||||
* @param fd file descriptor.
|
||||
* @return wait ticket on success; NULL on failure.
|
||||
*/
|
||||
struct waiter_ticket *waiter_add_fd(struct waiter *w, int fd);
|
||||
|
||||
/** Add a timeout to the pool.
|
||||
* @param w waiter.
|
||||
* @param ms duration of timeout in milliseconds.
|
||||
* @return wait ticket on success; NULL on failure.
|
||||
*/
|
||||
struct waiter_ticket *waiter_add_timeout(struct waiter *w, unsigned int ms);
|
||||
|
||||
/** Set ticket type to null.
|
||||
* @param tkt wait ticket.
|
||||
*/
|
||||
void waiter_ticket_set_null(struct waiter_ticket *tkt);
|
||||
|
||||
/** Set ticket type to file descriptor.
|
||||
* @param tkt wait ticket.
|
||||
* @param fd file descriptor.
|
||||
*/
|
||||
void waiter_ticket_set_fd(struct waiter_ticket *tkt, int fd);
|
||||
|
||||
/** Set ticket type to timeout.
|
||||
* @param tkt wait ticket.
|
||||
* @param ms timeout in milliseconds.
|
||||
*/
|
||||
void waiter_ticket_set_timeout(struct waiter_ticket *tkt, unsigned int ms);
|
||||
|
||||
/** Destroy ticket.
|
||||
* @param tkt wait ticket.
|
||||
*/
|
||||
void waiter_ticket_delete(struct waiter_ticket *tkt);
|
||||
|
||||
/** Check to see if ticket has triggered.
|
||||
* @param tkt wait ticket.
|
||||
* @return 0 if triggered, !0 otherwise.
|
||||
*/
|
||||
int waiter_ticket_check(const struct waiter_ticket *tkt);
|
||||
|
||||
|
||||
/** Clear ticket trigger status.
|
||||
* @param tkt wait ticket.
|
||||
* @return 0 if triggered, !0 otherwise.
|
||||
*/
|
||||
int waiter_ticket_clear(struct waiter_ticket *tkt);
|
||||
|
||||
/** Wait ticket callback function type. */
|
||||
typedef void (* waiter_ticket_cb_t)(void *, struct waiter_ticket *);
|
||||
|
||||
/** Register callback function for ticket trigger.
|
||||
* @param tkt wait ticket.
|
||||
* @param cb_fn callback function.
|
||||
* @param data private data to pass to callback function.
|
||||
*/
|
||||
void waiter_ticket_callback(struct waiter_ticket *tkt,
|
||||
waiter_ticket_cb_t cb_fn, void *data);
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user