ble-gatt-server-example: add sample application to create a BLE GATT server
https://jira.digi.com/browse/DEL-6649 Signed-off-by: Arturo Buzarra <arturo.buzarra@digi.com>
This commit is contained in:
parent
a821bc2f7c
commit
844d13fec0
|
|
@ -0,0 +1,39 @@
|
|||
#
|
||||
# Copyright (c) 2019 Digi International Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and/or distribute this software for any
|
||||
# purpose with or without fee is hereby granted, provided that the above
|
||||
# copyright notice and this permission notice appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
# PERFORMANCE OF THIS SOFTWARE.
|
||||
#
|
||||
#
|
||||
|
||||
PROGRAM := ble-gatt-server-example
|
||||
|
||||
CFLAGS += -Wall
|
||||
|
||||
LDLIBS += -lshared-mainloop -lbluetooth-internal
|
||||
|
||||
all: $(PROGRAM)
|
||||
|
||||
SRCS := $(wildcard *.c)
|
||||
OBJS := $(SRCS:.c=.o)
|
||||
|
||||
$(PROGRAM): $(OBJS)
|
||||
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
|
||||
|
||||
.PHONY: install
|
||||
install: $(PROGRAM)
|
||||
install -d $(DESTDIR)/usr/bin
|
||||
install -m 0755 $(PROGRAM) $(DESTDIR)/usr/bin/
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
-rm -f *.o $(PROGRAM)
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
BLE GATT Server Example Application
|
||||
===================================
|
||||
|
||||
Example application to create a BLE GATT server.
|
||||
|
||||
This application enables a BLE GATT server on the board. After that, the
|
||||
application starts listening for BLE connections to provide custom BLE
|
||||
GATT services and characteristics.
|
||||
|
||||
|
||||
Running the application
|
||||
-----------------------
|
||||
Once the binary is in the target, you must configure the bluetooth
|
||||
interface with the following commands:
|
||||
|
||||
```
|
||||
~# btmgmt -i hci0 le on
|
||||
hci0 Set Low Energy complete, settings: powered connectable bondable ssp br/edr le advertising secure-conn
|
||||
~# btmgmt -i hci0 connectable on
|
||||
hci0 Set Connectable complete, settings: powered connectable bondable ssp br/edr le advertising secure-conn
|
||||
~# btmgmt -i hci0 pairable on
|
||||
hci0 Set Bondable complete, settings: powered connectable bondable ssp br/edr le advertising secure-conn
|
||||
~# btmgmt -i hci0 advertising on
|
||||
hci0 Set Advertising complete, settings: powered connectable bondable ssp br/edr le advertising secure-conn
|
||||
~#
|
||||
```
|
||||
|
||||
After you initialize the bluetooth interface, you can start the sample application:
|
||||
|
||||
```
|
||||
~# ./ble-gatt-server-example -h
|
||||
Usage:
|
||||
ble-gatt-server-app [options]
|
||||
Options:
|
||||
-i, --index <id> Specify adapter index, e.g. hci0
|
||||
-t, --threshold <temp> The temperature threshold to send notification
|
||||
-m, --mtu <mtu> The ATT MTU to use
|
||||
-v, --verbose Enable extra logging
|
||||
-h, --help Display help
|
||||
~#
|
||||
~# ./ble-gatt-server-example --index hci0 --threshold 50
|
||||
Running GATT server
|
||||
Started listening on ATT channel. Waiting for connections
|
||||
Connect from XX:XX:XX:XX:XX:XX
|
||||
|
||||
```
|
||||
|
||||
Compiling the application
|
||||
-------------------------
|
||||
This demo can be compiled using a Digi Embedded Yocto based toolchain. Make
|
||||
sure to source the corresponding toolchain of the platform you are using,
|
||||
for example, for ConnectCore 8X:
|
||||
|
||||
```
|
||||
~$ . <DEY-toolchain-path>/environment-setup-aarch64-dey-linux
|
||||
~$ make
|
||||
```
|
||||
|
||||
For more information, see the [Digi Embedded Yocto online documentation](https://github.com/digi-embedded/meta-digi).
|
||||
|
||||
License
|
||||
-------
|
||||
Copyright 2019, Digi International Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose
|
||||
with or without fee is hereby granted, provided that the above copyright notice
|
||||
and this permission notice appears in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
THIS SOFTWARE.
|
||||
|
|
@ -0,0 +1,885 @@
|
|||
/*
|
||||
* BlueZ - Bluetooth protocol stack for Linux
|
||||
*
|
||||
* Copyright (C) 2014 Google Inc.
|
||||
* Copyright (C) 2019 Digi International Inc.
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE /* for asprintf */
|
||||
|
||||
#include <ctype.h>
|
||||
#include <getopt.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <syslog.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "bluetooth/bluetooth.h"
|
||||
#include "bluetooth/hci.h"
|
||||
#include "bluetooth/hci_lib.h"
|
||||
#include "bluetooth/l2cap.h"
|
||||
|
||||
#include "bluetooth-internal/uuid.h"
|
||||
#include "bluetooth-internal/mainloop.h"
|
||||
#include "bluetooth-internal/util.h"
|
||||
#include "bluetooth-internal/att.h"
|
||||
#include "bluetooth-internal/queue.h"
|
||||
#include "bluetooth-internal/timeout.h"
|
||||
#include "bluetooth-internal/gatt-db.h"
|
||||
#include "bluetooth-internal/gatt-server.h"
|
||||
|
||||
#include "subprocess.h"
|
||||
#include "ble-gatt-server-app.h"
|
||||
|
||||
|
||||
#define ATT_CID 4
|
||||
#define NOTIFICATION_TIMEOUT_MS 1000
|
||||
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
|
||||
|
||||
static bool verbose = false;
|
||||
static bool keep_running = true;
|
||||
static int temp_threshold = 50;
|
||||
|
||||
struct server {
|
||||
int fd;
|
||||
struct bt_att *att;
|
||||
struct gatt_db *db;
|
||||
struct bt_gatt_server *gatt;
|
||||
bool svc_chngd_enabled;
|
||||
uint16_t cell_handle;
|
||||
int *timeout_handlers;
|
||||
int n_characteristics;
|
||||
};
|
||||
|
||||
struct notify_cb_t {
|
||||
uint16_t handle;
|
||||
struct server *server;
|
||||
};
|
||||
|
||||
void destroy_notify_data(void *user_data)
|
||||
{
|
||||
free(user_data);
|
||||
}
|
||||
|
||||
static char *get_cmd_output(char *cmd)
|
||||
{
|
||||
FILE *in;
|
||||
char buff[512], *pbuf = NULL;
|
||||
int ret_value;
|
||||
|
||||
in = popen(cmd, "r");
|
||||
if (!in) {
|
||||
syslog(LOG_WARNING, "execution of cmd '%s' failed\n", cmd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pbuf = fgets(buff, ARRAY_SIZE(buff), in);
|
||||
if (pbuf)
|
||||
buff[strlen(buff) - 1] = '\0';
|
||||
|
||||
ret_value = pclose(in);
|
||||
|
||||
syslog(LOG_DEBUG, "cmd '%s' returned: '%s' (return value: %d)\n", cmd, buff, ret_value);
|
||||
if (pbuf)
|
||||
return strdup(buff);
|
||||
|
||||
return pbuf;
|
||||
}
|
||||
|
||||
static void debug_cb(const char *str, void *user_data)
|
||||
{
|
||||
const char *prefix = user_data;
|
||||
|
||||
syslog(LOG_DEBUG ,"%s%s\n", prefix, str);
|
||||
}
|
||||
|
||||
static void att_disconnect_cb(int err, void *user_data)
|
||||
{
|
||||
printf("Device disconnected: %s\n", strerror(err));
|
||||
|
||||
mainloop_quit();
|
||||
}
|
||||
|
||||
static void confirm_write(struct gatt_db_attribute *attr, int err,
|
||||
void *user_data)
|
||||
{
|
||||
if (!err)
|
||||
return;
|
||||
|
||||
syslog(LOG_DEBUG, "Error caching attribute %p - err: %d\n", attr, err);
|
||||
}
|
||||
|
||||
|
||||
/* Generic Access Service (GAP) */
|
||||
|
||||
static void gap_device_name_read_cb(struct gatt_db_attribute *attrib,
|
||||
unsigned int id, uint16_t offset,
|
||||
uint8_t opcode, struct bt_att *att,
|
||||
void *user_data)
|
||||
{
|
||||
uint8_t error = 0;
|
||||
size_t len = 0;
|
||||
uint8_t *value = NULL;
|
||||
char *name = NULL;
|
||||
|
||||
name = get_cmd_output("cat /etc/hostname");
|
||||
if (!name) {
|
||||
error = BT_ATT_ERROR_INVALID_HANDLE;
|
||||
goto done;
|
||||
}
|
||||
|
||||
len = strlen(name) + 1;
|
||||
if (offset > len) {
|
||||
error = BT_ATT_ERROR_INVALID_HANDLE;
|
||||
goto done;
|
||||
}
|
||||
|
||||
len -= offset;
|
||||
value = len ? (uint8_t *)&name[offset] : NULL;
|
||||
|
||||
done:
|
||||
syslog(LOG_DEBUG, "%s called, return: %s (error: 0x%x)\n", __func__, value, error);
|
||||
gatt_db_attribute_read_result(attrib, id, error, value, len);
|
||||
free(name);
|
||||
}
|
||||
|
||||
static void populate_gap_service(struct server *server)
|
||||
{
|
||||
bt_uuid_t uuid;
|
||||
struct gatt_db_attribute *service, *tmp;
|
||||
uint16_t appearance;
|
||||
|
||||
/* Add the GAP service */
|
||||
bt_uuid16_create(&uuid, UUID_GAP_SERVICE);
|
||||
service = gatt_db_add_service(server->db, &uuid, true, 6);
|
||||
|
||||
/*
|
||||
* Device Name characteristic. Make the value dynamically read and
|
||||
* written via callbacks.
|
||||
*/
|
||||
bt_uuid16_create(&uuid, GATT_CHARAC_DEVICE_NAME);
|
||||
gatt_db_service_add_characteristic(service, &uuid,
|
||||
BT_ATT_PERM_READ,
|
||||
BT_GATT_CHRC_PROP_READ |
|
||||
BT_GATT_CHRC_PROP_EXT_PROP,
|
||||
gap_device_name_read_cb,
|
||||
NULL,
|
||||
server);
|
||||
|
||||
bt_uuid16_create(&uuid, GATT_CHARAC_EXT_PROPER_UUID);
|
||||
|
||||
/*
|
||||
* Device Appearance characteristic. Reads and writes should obtain the
|
||||
* value from the database.
|
||||
*/
|
||||
bt_uuid16_create(&uuid, GATT_CHARAC_APPEARANCE);
|
||||
tmp = gatt_db_service_add_characteristic(service, &uuid,
|
||||
BT_ATT_PERM_READ,
|
||||
BT_GATT_CHRC_PROP_READ,
|
||||
NULL, NULL, server);
|
||||
|
||||
/*
|
||||
* Write the appearance value to the database, since we're not using a
|
||||
* callback.
|
||||
*/
|
||||
put_le16(128, &appearance);
|
||||
gatt_db_attribute_write(tmp, 0, (void *) &appearance,
|
||||
sizeof(appearance),
|
||||
BT_ATT_OP_WRITE_REQ,
|
||||
NULL, confirm_write,
|
||||
NULL);
|
||||
|
||||
gatt_db_service_set_active(service, true);
|
||||
}
|
||||
|
||||
|
||||
/* Generic Attribute Profile Service (GATT) */
|
||||
|
||||
static void gatt_service_changed_cb(struct gatt_db_attribute *attrib,
|
||||
unsigned int id, uint16_t offset,
|
||||
uint8_t opcode, struct bt_att *att,
|
||||
void *user_data)
|
||||
{
|
||||
uint8_t error = 0;
|
||||
|
||||
syslog(LOG_DEBUG, "Service Changed Read called\n");
|
||||
|
||||
gatt_db_attribute_read_result(attrib, id, error, NULL, 0);
|
||||
}
|
||||
|
||||
static void gatt_svc_chngd_ccc_read_cb(struct gatt_db_attribute *attrib,
|
||||
unsigned int id, uint16_t offset,
|
||||
uint8_t opcode, struct bt_att *att,
|
||||
void *user_data)
|
||||
{
|
||||
struct server *server = user_data;
|
||||
uint8_t error = 0;
|
||||
uint8_t value[2];
|
||||
|
||||
syslog(LOG_DEBUG, "Service Changed CCC Read called\n");
|
||||
|
||||
value[0] = server->svc_chngd_enabled ? 0x02 : 0x00;
|
||||
value[1] = 0x00;
|
||||
|
||||
gatt_db_attribute_read_result(attrib, id, error, value, sizeof(value));
|
||||
}
|
||||
|
||||
static void gatt_svc_chngd_ccc_write_cb(struct gatt_db_attribute *attrib,
|
||||
unsigned int id, uint16_t offset,
|
||||
const uint8_t *value, size_t len,
|
||||
uint8_t opcode, struct bt_att *att,
|
||||
void *user_data)
|
||||
{
|
||||
struct server *server = user_data;
|
||||
uint8_t error = 0;
|
||||
|
||||
syslog(LOG_DEBUG, "Service Changed CCC Write called\n");
|
||||
|
||||
if (!value || len != 2) {
|
||||
error = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (offset) {
|
||||
error = BT_ATT_ERROR_INVALID_OFFSET;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (value[0] == 0x00)
|
||||
server->svc_chngd_enabled = false;
|
||||
else if (value[0] == 0x02)
|
||||
server->svc_chngd_enabled = true;
|
||||
else
|
||||
error = BT_ATT_ERROR_INVALID_HANDLE;
|
||||
|
||||
syslog(LOG_DEBUG, "Service Changed Enabled: %s\n",
|
||||
server->svc_chngd_enabled ? "true" : "false");
|
||||
|
||||
done:
|
||||
gatt_db_attribute_write_result(attrib, id, error);
|
||||
}
|
||||
|
||||
static void populate_gatt_service(struct server *server)
|
||||
{
|
||||
bt_uuid_t uuid;
|
||||
struct gatt_db_attribute *service, *svc_chngd;
|
||||
|
||||
/* Add the GATT service */
|
||||
bt_uuid16_create(&uuid, UUID_GATT_SERVICE);
|
||||
service = gatt_db_add_service(server->db, &uuid, true, 4);
|
||||
|
||||
bt_uuid16_create(&uuid, GATT_CHARAC_SERVICE_CHANGED);
|
||||
svc_chngd = gatt_db_service_add_characteristic(service, &uuid,
|
||||
BT_ATT_PERM_READ,
|
||||
BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_INDICATE,
|
||||
gatt_service_changed_cb,
|
||||
NULL, server);
|
||||
gatt_db_attribute_get_handle(svc_chngd);
|
||||
|
||||
bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID);
|
||||
gatt_db_service_add_descriptor(service, &uuid,
|
||||
BT_ATT_PERM_READ | BT_ATT_PERM_WRITE,
|
||||
gatt_svc_chngd_ccc_read_cb,
|
||||
gatt_svc_chngd_ccc_write_cb, server);
|
||||
|
||||
gatt_db_service_set_active(service, true);
|
||||
}
|
||||
|
||||
|
||||
/* Device Information Service */
|
||||
|
||||
static void temperature_read_cb(struct gatt_db_attribute *attrib,
|
||||
unsigned int id, uint16_t offset,
|
||||
uint8_t opcode, struct bt_att *att,
|
||||
void *user_data)
|
||||
{
|
||||
uint8_t error = 0;
|
||||
size_t len = 0;
|
||||
uint8_t *value = NULL;
|
||||
int degrees;
|
||||
char *temp = NULL;
|
||||
|
||||
temp = get_cmd_output("cat /sys/devices/virtual/thermal/thermal_zone0/temp");
|
||||
if (!temp) {
|
||||
error = BT_ATT_ERROR_INVALID_HANDLE;
|
||||
goto done;
|
||||
}
|
||||
|
||||
degrees = atoi(temp)/1000;
|
||||
value = (uint8_t *) °rees;
|
||||
len = 1;
|
||||
|
||||
done:
|
||||
syslog(LOG_DEBUG, "%s called, return: %s (error: 0x%x)\n", __func__, value, error);
|
||||
gatt_db_attribute_read_result(attrib, id, error, value, len);
|
||||
free(temp);
|
||||
}
|
||||
|
||||
static bool temperature_notify_cb(void *user_data)
|
||||
{
|
||||
struct notify_cb_t *notify_data = user_data;
|
||||
struct server *server = notify_data->server;
|
||||
char *temp = NULL;
|
||||
uint8_t notify_value;
|
||||
int current_temp = 0;
|
||||
|
||||
temp = get_cmd_output("cat /sys/devices/virtual/thermal/thermal_zone0/temp");
|
||||
if (!temp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
current_temp = atoi(temp)/1000;
|
||||
notify_value = (current_temp >= temp_threshold) ? 0x01 : 0x00;
|
||||
free(temp);
|
||||
|
||||
/* If the Temperatue is higher than a threshold send notification */
|
||||
if (notify_value) {
|
||||
printf("Send Temperature notification: %d degrees\n", current_temp);
|
||||
bt_gatt_server_send_notification(server->gatt,
|
||||
notify_data->handle,
|
||||
¬ify_value,
|
||||
sizeof(notify_value));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void current_time_read_cb(struct gatt_db_attribute *attrib,
|
||||
unsigned int id, uint16_t offset,
|
||||
uint8_t opcode, struct bt_att *att,
|
||||
void *user_data)
|
||||
{
|
||||
uint8_t error;
|
||||
uint8_t *value = NULL;
|
||||
size_t len = 0;
|
||||
char *timestamp = NULL;
|
||||
|
||||
timestamp = get_cmd_output("date +%s");
|
||||
if (!timestamp) {
|
||||
error = BT_ATT_ERROR_INVALID_HANDLE;
|
||||
goto done;
|
||||
}
|
||||
|
||||
len = strlen(timestamp) + 1;
|
||||
if (offset > len) {
|
||||
error = BT_ATT_ERROR_INVALID_OFFSET;
|
||||
goto done;
|
||||
}
|
||||
|
||||
len -= offset;
|
||||
value = len ? (uint8_t *) ×tamp[offset] : NULL;
|
||||
error = 0;
|
||||
|
||||
done:
|
||||
syslog(LOG_DEBUG, "%s called, return: %s (error: 0x%x)\n", __func__, value, error);
|
||||
gatt_db_attribute_read_result(attrib, id, error, value, len);
|
||||
free(timestamp);
|
||||
}
|
||||
|
||||
static void current_time_write_cb(struct gatt_db_attribute *attrib,
|
||||
unsigned int id, uint16_t offset,
|
||||
const uint8_t *value, size_t len,
|
||||
uint8_t opcode, struct bt_att *att,
|
||||
void *user_data)
|
||||
{
|
||||
uint8_t error = 0;
|
||||
int status = 0;
|
||||
char *new_timestamp = NULL;
|
||||
char *cmd = NULL;
|
||||
|
||||
/* Do not allow to partially write the Time (offset must be 0) */
|
||||
if (offset) {
|
||||
error = BT_ATT_ERROR_INVALID_OFFSET;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*
|
||||
* We need to terminate the string, unfortunately 'value' is read only,
|
||||
* so copy it all over.
|
||||
*/
|
||||
new_timestamp = malloc(len + 1);
|
||||
if (!new_timestamp) {
|
||||
error = BT_ATT_ERROR_INVALID_HANDLE;
|
||||
goto done;
|
||||
}
|
||||
memcpy(new_timestamp, value, len);
|
||||
new_timestamp[len] = '\0';
|
||||
|
||||
if (asprintf(&cmd, "echo %s", new_timestamp) < 0 ||
|
||||
safe_execute(cmd, &status) < 0 || status) {
|
||||
syslog(LOG_ERR, "Failed to set Time, error(%d)\n", status);
|
||||
error = BT_ATT_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
done:
|
||||
syslog(LOG_DEBUG, "%s called, received: %s (error: 0x%x)\n", __func__, new_timestamp, error);
|
||||
gatt_db_attribute_write_result(attrib, id, error);
|
||||
free(cmd);
|
||||
free(new_timestamp);
|
||||
}
|
||||
|
||||
|
||||
struct {
|
||||
uint16_t uuid;
|
||||
const char *description;
|
||||
const uint8_t *format;
|
||||
gatt_db_read_t read_cb;
|
||||
gatt_db_write_t write_cb;
|
||||
timeout_func_t notify_cb;
|
||||
} const bt_characteristics[] = {
|
||||
{
|
||||
UUID_GATT_CHARACTERISTIC_CURRENT_TIME,
|
||||
"Time in String format",
|
||||
FORMAT_UTF_8,
|
||||
current_time_read_cb,
|
||||
current_time_write_cb,
|
||||
NULL
|
||||
},
|
||||
{
|
||||
UUID_GATT_CHARACTERISTIC_TEMPERATURE_MEASUREMENT,
|
||||
"Temperature in Integer format",
|
||||
FORMAT_8_BIT_INT,
|
||||
temperature_read_cb,
|
||||
NULL,
|
||||
temperature_notify_cb
|
||||
},
|
||||
};
|
||||
|
||||
/*
|
||||
* For each characteristic we create 4 handles:
|
||||
* - Characteristic Declaration
|
||||
* - Characteristic Value Declaration
|
||||
* - Descriptor Declaration:
|
||||
* - Characteristic User Description
|
||||
* - Characteristic Presentation Format
|
||||
*/
|
||||
#define HANDLES_PER_CHARACTERISTIC 4
|
||||
|
||||
static void populate_device_information_service(struct server *server)
|
||||
{
|
||||
bt_uuid_t uuid;
|
||||
struct gatt_db_attribute *service;
|
||||
int i,num_handles;
|
||||
|
||||
/* Add Device Information Service */
|
||||
server->n_characteristics = ARRAY_SIZE(bt_characteristics);
|
||||
bt_uuid16_create(&uuid, UUID_DEVICE_INFORMATION_SERVICE);
|
||||
/* Add one handle for the Service Declaration */
|
||||
num_handles = (server->n_characteristics * HANDLES_PER_CHARACTERISTIC) + 1;
|
||||
service = gatt_db_add_service(server->db, &uuid, true, num_handles);
|
||||
|
||||
server->cell_handle = gatt_db_attribute_get_handle(service);
|
||||
server->timeout_handlers = calloc(server->n_characteristics, sizeof(server->timeout_handlers[0]));
|
||||
if (!server->timeout_handlers) {
|
||||
syslog(LOG_ERR, "Out of memory, calloc failed!");
|
||||
exit(2);
|
||||
}
|
||||
|
||||
/* Add all characteristics */
|
||||
for (i = 0; i < server->n_characteristics; i++) {
|
||||
uint32_t permissions = 0;
|
||||
uint8_t properties = 0;
|
||||
struct gatt_db_attribute *desc_att, *gatt_handle;
|
||||
|
||||
bt_uuid16_create(&uuid, bt_characteristics[i].uuid);
|
||||
|
||||
if (bt_characteristics[i].read_cb)
|
||||
properties |= BT_GATT_CHRC_PROP_READ;
|
||||
|
||||
if (bt_characteristics[i].write_cb) {
|
||||
properties |= BT_GATT_CHRC_PROP_WRITE;
|
||||
permissions |= BT_ATT_PERM_WRITE;
|
||||
}
|
||||
|
||||
if (bt_characteristics[i].notify_cb)
|
||||
properties |= BT_GATT_CHRC_PROP_NOTIFY;
|
||||
|
||||
permissions |= BT_ATT_PERM_READ;
|
||||
|
||||
/* Characteristic */
|
||||
gatt_handle = gatt_db_service_add_characteristic(
|
||||
service,
|
||||
&uuid,
|
||||
permissions,
|
||||
properties,
|
||||
bt_characteristics[i].read_cb,
|
||||
bt_characteristics[i].write_cb,
|
||||
server);
|
||||
|
||||
/* Characteristic User Description */
|
||||
bt_uuid16_create(&uuid, GATT_CHARAC_USER_DESC_UUID);
|
||||
desc_att = gatt_db_service_add_descriptor(service,
|
||||
&uuid,
|
||||
BT_ATT_PERM_READ,
|
||||
NULL,
|
||||
NULL,
|
||||
server);
|
||||
gatt_db_attribute_write(desc_att,
|
||||
0,
|
||||
(const uint8_t *) bt_characteristics[i].description,
|
||||
strlen(bt_characteristics[i].description) + 1,
|
||||
BT_ATT_OP_READ_REQ,
|
||||
NULL,
|
||||
confirm_write,
|
||||
NULL);
|
||||
|
||||
/* Charateristic Presentation Format */
|
||||
bt_uuid16_create(&uuid, GATT_CHARAC_FMT_UUID);
|
||||
desc_att = gatt_db_service_add_descriptor(service,
|
||||
&uuid,
|
||||
BT_ATT_PERM_READ,
|
||||
NULL,
|
||||
NULL,
|
||||
server);
|
||||
|
||||
gatt_db_attribute_write(desc_att,
|
||||
0,
|
||||
bt_characteristics[i].format,
|
||||
FORMAT_BYTES,
|
||||
BT_ATT_OP_READ_REQ,
|
||||
NULL,
|
||||
confirm_write,
|
||||
NULL);
|
||||
|
||||
/* Notification timed function */
|
||||
if (bt_characteristics[i].notify_cb) {
|
||||
struct notify_cb_t *notify_data = malloc(sizeof(*notify_data));
|
||||
|
||||
if (!notify_data) {
|
||||
syslog(LOG_ERR, "Out of memory, calloc failed!");
|
||||
exit(2);
|
||||
}
|
||||
notify_data->server = server;
|
||||
notify_data->handle = gatt_db_attribute_get_handle(gatt_handle);
|
||||
|
||||
server->timeout_handlers[i] = timeout_add(NOTIFICATION_TIMEOUT_MS, bt_characteristics[i].notify_cb, notify_data, destroy_notify_data);
|
||||
|
||||
/* Manually call the notify once to cache a first value */
|
||||
bt_characteristics[i].notify_cb(notify_data);
|
||||
}
|
||||
}
|
||||
|
||||
gatt_db_service_set_active(service, true);
|
||||
}
|
||||
|
||||
|
||||
/* Main program */
|
||||
static void populate_db(struct server *server)
|
||||
{
|
||||
populate_gap_service(server);
|
||||
populate_gatt_service(server);
|
||||
populate_device_information_service(server);
|
||||
}
|
||||
|
||||
static struct server *server_create(int fd, uint16_t mtu)
|
||||
{
|
||||
struct server *server;
|
||||
|
||||
server = new0(struct server, 1);
|
||||
if (!server) {
|
||||
syslog(LOG_ERR, "Failed to allocate memory for server\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
server->att = bt_att_new(fd, false);
|
||||
if (!server->att) {
|
||||
syslog(LOG_ERR, "Failed to initialze ATT transport layer\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!bt_att_set_close_on_unref(server->att, true)) {
|
||||
syslog(LOG_ERR, "Failed to set up ATT transport layer\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!bt_att_register_disconnect(server->att, att_disconnect_cb, NULL,
|
||||
NULL)) {
|
||||
syslog(LOG_ERR, "Failed to set ATT disconnect handler\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
server->fd = fd;
|
||||
server->db = gatt_db_new();
|
||||
if (!server->db) {
|
||||
syslog(LOG_ERR, "Failed to create GATT database\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
server->gatt = bt_gatt_server_new(server->db, server->att, mtu, 0);
|
||||
if (!server->gatt) {
|
||||
syslog(LOG_ERR, "Failed to create GATT server\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (verbose) {
|
||||
bt_att_set_debug(server->att, debug_cb, "att: ", NULL);
|
||||
bt_gatt_server_set_debug(server->gatt, debug_cb,
|
||||
"server: ", NULL);
|
||||
}
|
||||
|
||||
/* bt_gatt_server already holds a reference */
|
||||
populate_db(server);
|
||||
|
||||
return server;
|
||||
|
||||
fail:
|
||||
gatt_db_unref(server->db);
|
||||
bt_att_unref(server->att);
|
||||
free(server);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void server_destroy(struct server *server)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < server->n_characteristics; i++)
|
||||
if (server->timeout_handlers[i])
|
||||
timeout_remove(server->timeout_handlers[i]);
|
||||
|
||||
free(server->timeout_handlers);
|
||||
|
||||
bt_gatt_server_unref(server->gatt);
|
||||
gatt_db_unref(server->db);
|
||||
bt_att_unref(server->att);
|
||||
free(server);
|
||||
}
|
||||
|
||||
static void usage(void)
|
||||
{
|
||||
printf("ble-gatt-server-app\n");
|
||||
printf("Usage:\n\tble-gatt-server-app [options]\n");
|
||||
|
||||
printf("Options:\n"
|
||||
"\t-i, --index <id>\t\tSpecify adapter index, e.g. hci0\n"
|
||||
"\t-t, --threshold <temp>\t\tThe temperature threshold to send notification\n"
|
||||
"\t-m, --mtu <mtu>\t\t\tThe ATT MTU to use\n"
|
||||
"\t-v, --verbose\t\t\tEnable extra logging\n"
|
||||
"\t-h, --help\t\t\tDisplay help\n");
|
||||
}
|
||||
|
||||
static struct option main_options[] = {
|
||||
{ "index", 1, 0, 'i' },
|
||||
{ "threshold", 1, 0, 't' },
|
||||
{ "mtu", 1, 0, 'm' },
|
||||
{ "verbose", 0, 0, 'v' },
|
||||
{ "help", 0, 0, 'h' },
|
||||
{ }
|
||||
};
|
||||
|
||||
static int l2cap_le_att_listen_and_accept(bdaddr_t *src, int sec,
|
||||
uint8_t src_type)
|
||||
{
|
||||
int sk, nsk;
|
||||
struct sockaddr_l2 srcaddr, addr;
|
||||
socklen_t optlen;
|
||||
struct bt_security btsec;
|
||||
char ba[18];
|
||||
|
||||
sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
|
||||
if (sk < 0) {
|
||||
syslog(LOG_ERR, "Failed to create L2CAP socket");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Set up source address */
|
||||
memset(&srcaddr, 0, sizeof(srcaddr));
|
||||
srcaddr.l2_family = AF_BLUETOOTH;
|
||||
srcaddr.l2_cid = htobs(ATT_CID);
|
||||
srcaddr.l2_bdaddr_type = src_type;
|
||||
bacpy(&srcaddr.l2_bdaddr, src);
|
||||
|
||||
if (bind(sk, (struct sockaddr *) &srcaddr, sizeof(srcaddr)) < 0) {
|
||||
syslog(LOG_ERR, "Failed to bind L2CAP socket");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Set the security level */
|
||||
memset(&btsec, 0, sizeof(btsec));
|
||||
btsec.level = sec;
|
||||
if (setsockopt(sk, SOL_BLUETOOTH, BT_SECURITY, &btsec,
|
||||
sizeof(btsec)) != 0) {
|
||||
syslog(LOG_ERR, "Failed to set L2CAP security level\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (listen(sk, 10) < 0) {
|
||||
syslog(LOG_ERR, "Listening on socket failed");
|
||||
goto fail;
|
||||
}
|
||||
printf("Started listening on ATT channel. Waiting for connections\n");
|
||||
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
optlen = sizeof(addr);
|
||||
|
||||
nsk = accept(sk, (struct sockaddr *) &addr, &optlen);
|
||||
if (nsk < 0) {
|
||||
syslog(LOG_ERR, "Accept failed");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ba2str(&addr.l2_bdaddr, ba);
|
||||
printf("Connect from %s\n", ba);
|
||||
close(sk);
|
||||
|
||||
return nsk;
|
||||
|
||||
fail:
|
||||
close(sk);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void signal_cb(int signum, void *user_data)
|
||||
{
|
||||
switch (signum) {
|
||||
case SIGINT:
|
||||
case SIGTERM:
|
||||
keep_running = false;
|
||||
mainloop_quit();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
const int sec = BT_SECURITY_LOW;
|
||||
const uint8_t src_type = BDADDR_LE_PUBLIC;
|
||||
int opt;
|
||||
bdaddr_t src_addr;
|
||||
int dev_id = -1;
|
||||
uint16_t mtu = 0;
|
||||
sigset_t mask;
|
||||
|
||||
setlogmask (LOG_UPTO (LOG_NOTICE));
|
||||
|
||||
while ((opt = getopt_long(argc, argv, "+hvrm:t:i:",
|
||||
main_options, NULL)) != -1) {
|
||||
switch (opt) {
|
||||
case 'h':
|
||||
usage();
|
||||
return EXIT_SUCCESS;
|
||||
case 'v':
|
||||
verbose = true;
|
||||
setlogmask (LOG_UPTO (LOG_DEBUG));
|
||||
break;
|
||||
case 'm': {
|
||||
int arg;
|
||||
|
||||
arg = atoi(optarg);
|
||||
if (arg <= 0) {
|
||||
syslog(LOG_ERR, "Invalid MTU: %d\n", arg);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (arg > UINT16_MAX) {
|
||||
syslog(LOG_ERR, "MTU too large: %d\n", arg);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
mtu = (uint16_t) arg;
|
||||
break;
|
||||
}
|
||||
case 't': {
|
||||
int arg;
|
||||
|
||||
arg = atoi(optarg);
|
||||
if (arg <= 0) {
|
||||
syslog(LOG_ERR, "Invalid Temperature threshold: %d\n", arg);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (arg > UINT8_MAX) {
|
||||
syslog(LOG_ERR, "Temperature threshold too large: %d\n", arg);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
temp_threshold = arg;
|
||||
break;
|
||||
}
|
||||
case 'i':
|
||||
dev_id = hci_devid(optarg);
|
||||
if (dev_id < 0) {
|
||||
syslog(LOG_ERR, "Invalid adapter");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
syslog(LOG_ERR, "Invalid option: %c\n", opt);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
argc -= optind;
|
||||
argv -= optind;
|
||||
optind = 0;
|
||||
|
||||
openlog("ble-gatt-server-app", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL1);
|
||||
printf("Running GATT server\n");
|
||||
|
||||
if (argc) {
|
||||
usage();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
while (keep_running == true) {
|
||||
int fd;
|
||||
struct server *server;
|
||||
|
||||
if (dev_id == -1) {
|
||||
bacpy(&src_addr, BDADDR_ANY);
|
||||
}
|
||||
else if (hci_devba(dev_id, &src_addr) < 0) {
|
||||
syslog(LOG_ERR, "Adapter not available");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
fd = l2cap_le_att_listen_and_accept(&src_addr, sec, src_type);
|
||||
if (fd < 0) {
|
||||
syslog(LOG_ERR, "Failed to accept L2CAP ATT connection\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
mainloop_init();
|
||||
|
||||
server = server_create(fd, mtu);
|
||||
if (!server) {
|
||||
close(fd);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
sigemptyset(&mask);
|
||||
sigaddset(&mask, SIGINT);
|
||||
sigaddset(&mask, SIGTERM);
|
||||
|
||||
mainloop_set_signal(&mask, signal_cb, NULL, NULL);
|
||||
mainloop_run();
|
||||
|
||||
/* remove the application signals */
|
||||
sigdelset(&mask, SIGTERM);
|
||||
sigdelset(&mask, SIGINT);
|
||||
sigemptyset(&mask);
|
||||
|
||||
server_destroy(server);
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* BlueZ - Bluetooth protocol stack for Linux
|
||||
*
|
||||
* Copyright (C) 2014 Google Inc.
|
||||
* Copyright (C) 2019 Digi International Inc.
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* A full list of officially adopted BLE services can be seen on
|
||||
* https://www.bluetooth.com/specifications/gatt/services/
|
||||
*/
|
||||
#define UUID_GAP_SERVICE 0x1800 /* Generic Access Service (GAP) */
|
||||
#define UUID_GATT_SERVICE 0x1801 /* Generic Attribute Profile Service (GATT) */
|
||||
#define UUID_DEVICE_INFORMATION_SERVICE 0x180A /* Device Information Service */
|
||||
|
||||
/*
|
||||
* See BT specification to list all official characteristics UUID
|
||||
* from https://www.bluetooth.com/specifications/gatt/characteristics/
|
||||
*/
|
||||
#define UUID_GATT_CHARACTERISTIC_CURRENT_TIME 0x2A2B
|
||||
#define UUID_GATT_CHARACTERISTIC_TEMPERATURE_MEASUREMENT 0x2A1C
|
||||
|
||||
/*
|
||||
* See BT specification to list all GATT Bluetooth Format types
|
||||
* from https://www.bluetooth.com/specifications/assigned-numbers/format-types/
|
||||
*/
|
||||
#define BLE_GATT_CPF_FORMAT_UINT8 0x04 /* Unsigned 8-bit integer */
|
||||
#define BLE_GATT_CPF_FORMAT_UTF8S 0x19 /* UTF-8 string */
|
||||
|
||||
/*
|
||||
* See BT specification to list all GATT Bluetooth Units
|
||||
* from https://www.bluetooth.com/specifications/assigned-numbers/units/
|
||||
*/
|
||||
#define BLE_GATT_CPF_UNIT_DEGREE_CELSIUS 0x27,0x2F /* Celsius temperature (degree Celsius) */
|
||||
#define BLE_GATT_CPF_UNIT_TIME_SECONDS 0x27,0x03 /* Time (second) */
|
||||
|
||||
/*
|
||||
* See BT specification to list all GATT Bluetooth Namespace Descriptions
|
||||
* from https://www.bluetooth.com/specifications/assigned-numbers/gatt-namespace-descriptors/
|
||||
*/
|
||||
#define BLE_GATT_SIG_NAMESPACE 0x01
|
||||
#define BLE_GATT_SIG_DESCRIPTOR_UNKNOWN 0x00,0x00
|
||||
#define BLE_GATT_SIG_DESCRIPTOR_INTERNAL 0x01,0x0F
|
||||
|
||||
/*
|
||||
* See BT specification 4.2, section 3.3.3.5
|
||||
* for Characteristic Presentation Format descriptor:
|
||||
* - Format (1 octet)
|
||||
* - Exponent (1 octet)
|
||||
* - Unit (2 octet)
|
||||
* - Name Space (1 octet)
|
||||
* - Description (2 octet)
|
||||
*/
|
||||
#define FORMAT_BYTES 7
|
||||
|
||||
const uint8_t FORMAT_8_BIT_INT[] = {
|
||||
BLE_GATT_CPF_FORMAT_UINT8,
|
||||
0x00,
|
||||
BLE_GATT_CPF_UNIT_DEGREE_CELSIUS,
|
||||
BLE_GATT_SIG_NAMESPACE,
|
||||
BLE_GATT_SIG_DESCRIPTOR_INTERNAL
|
||||
};
|
||||
|
||||
const uint8_t FORMAT_UTF_8[] = {
|
||||
BLE_GATT_CPF_FORMAT_UTF8S,
|
||||
0x00,
|
||||
BLE_GATT_CPF_UNIT_TIME_SECONDS,
|
||||
BLE_GATT_SIG_NAMESPACE,
|
||||
BLE_GATT_SIG_DESCRIPTOR_UNKNOWN
|
||||
};
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
* Copyright (c) 2017 Digi International Inc.
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <syslog.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "subprocess.h"
|
||||
|
||||
/**
|
||||
* Convert a command string to an array of pointers to null-terminated strings
|
||||
*
|
||||
* @param command the command to tokenize
|
||||
* @return strings array on sucess, NULL on error
|
||||
*/
|
||||
static char **tokenize_command(char *command)
|
||||
{
|
||||
const char *delim = " ";
|
||||
char **argv;
|
||||
char *token, *buf;
|
||||
int c = 1;
|
||||
|
||||
argv = calloc(c, sizeof(char *));
|
||||
if (!argv)
|
||||
goto done;
|
||||
|
||||
/* 'strtok' changes the original string, so use a copy we can free */
|
||||
buf = strdup(command);
|
||||
|
||||
token = strtok(buf, delim);
|
||||
argv[c - 1] = token ? strdup(token) : NULL;
|
||||
while (argv[c - 1]) {
|
||||
void *tmp;
|
||||
c++;
|
||||
tmp = realloc(argv, c * sizeof(char *));
|
||||
if (!tmp)
|
||||
free(argv);
|
||||
argv = tmp;
|
||||
if (!argv)
|
||||
break;
|
||||
token = strtok(NULL, delim);
|
||||
argv[c - 1] = token ? strdup(token) : NULL;
|
||||
}
|
||||
free(buf);
|
||||
|
||||
done:
|
||||
return argv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely fork a child process and execute the given command with PATH lookup.
|
||||
*
|
||||
* @param command the command to execute
|
||||
* @param child_status return child status of executable
|
||||
* @return 0 on success, -1 on error with errno set
|
||||
*/
|
||||
int safe_execute(char *command, int *const child_status)
|
||||
{
|
||||
int status = 0, i;
|
||||
int sub_status = 0;
|
||||
pid_t pid = 0;
|
||||
pid_t sub_pid = 0;
|
||||
struct sigaction const action_ignore = {.sa_handler = SIG_IGN };
|
||||
|
||||
char **argv = tokenize_command(command);
|
||||
|
||||
if ((NULL == command) || (NULL == argv) || (NULL == child_status)) {
|
||||
status = EINVAL;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Initialize child_status */
|
||||
*child_status = -1;
|
||||
|
||||
/*
|
||||
* Fork a child process to do the execvp() call because that call will not
|
||||
* return if it works. We're doing it this way to try to make this
|
||||
* function as "nice" to the calling application as possible by not
|
||||
* inhibiting interrupts as can happen if the system() call is used (plus
|
||||
* that utility can have weird side-effects if the calling application has
|
||||
* suid or sgid privileges set).
|
||||
*/
|
||||
pid = fork();
|
||||
if (pid < 0) {
|
||||
status = errno;
|
||||
syslog(LOG_ERR, "[%s:%d] could not fork process: %s (%d)\n",
|
||||
__func__, __LINE__, strerror(errno), errno);
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we're now the child process, do the execvp(), replacing our process
|
||||
* with the called process. SIGINT and SIGTERM are ignored in child process
|
||||
* as we don't have any good ways of cleaning up the subprocess after a
|
||||
* signal.
|
||||
*/
|
||||
if (0 == pid) {
|
||||
if (sigaction(SIGINT, &action_ignore, NULL) < 0) {
|
||||
status = errno;
|
||||
syslog
|
||||
(LOG_ERR, "[%s:%d] error ignoring SIGINT: %s (%d)\n\n",
|
||||
__func__, __LINE__, strerror(errno), errno);
|
||||
}
|
||||
|
||||
if (sigaction(SIGTERM, &action_ignore, NULL) < 0) {
|
||||
status = errno;
|
||||
syslog
|
||||
(LOG_ERR, "[%s:%d] error ignoring SIGTERM: %s (%d)\n\n",
|
||||
__func__, __LINE__, strerror(errno), errno);
|
||||
}
|
||||
|
||||
if (execvp(argv[0], (char *const *)argv) < 0) {
|
||||
status = errno;
|
||||
syslog
|
||||
(LOG_ERR, "[%s:%d] error executing child process: %s (%d)\n",
|
||||
__func__, __LINE__, strerror(errno), errno);
|
||||
}
|
||||
|
||||
_exit(127);
|
||||
}
|
||||
|
||||
/*
|
||||
* Otherwise, if we're the parent, wait for the child process to terminate.
|
||||
* If waitpid returns due to a signal (EINTR), ignore the error and
|
||||
* continue to wait.
|
||||
*/
|
||||
do {
|
||||
errno = 0;
|
||||
sub_pid = waitpid(pid, &sub_status, 0);
|
||||
if ((sub_pid < 0) && (EINTR != errno)) {
|
||||
status = errno;
|
||||
syslog
|
||||
(LOG_ERR, "[%s:%d] error returned by waitpid: %s (%d)\n",
|
||||
__func__, __LINE__, strerror(errno), errno);
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (WIFEXITED(sub_status)) {
|
||||
*child_status = WEXITSTATUS(sub_status);
|
||||
}
|
||||
} while ((errno == EINTR) || (!WIFEXITED(sub_status)));
|
||||
|
||||
done:
|
||||
/* Free resources */
|
||||
i = 0;
|
||||
while (argv && argv[i]) {
|
||||
free(argv[i]);
|
||||
i++;
|
||||
}
|
||||
free(argv);
|
||||
|
||||
errno = status;
|
||||
|
||||
return (status == 0) ? 0 : -1;
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2017 Digi International Inc.
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef SUBPROCESS_H_
|
||||
#define SUBPROCESS_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Safely fork a child process and execute the given command with PATH lookup.
|
||||
*
|
||||
* @param command the command to execute
|
||||
* @param child_status return child status of executable
|
||||
* @return 0 on success, -1 on error with errno set
|
||||
*/
|
||||
int safe_execute(char *command, int *const child_status);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SUBPROCESS_H_ */
|
||||
Loading…
Reference in New Issue