Compare commits

...

5 Commits

Author SHA1 Message Date
Slava Monich
b5ed6d16db [ril] First fix permissions on top-level directories. JB#39961
... and then descend into subdirectories. Even though it doesn't
really matter since we are starting as root, it does seem to be
a bit more logical.
2017-10-13 18:32:28 +03:00
Slava Monich
fd0a1a9adb [ril] Fix storage directory permissions at startup. Fixes JB#39961
Also, made the identity configurable and got rid of hardcoded radio
uid and gid, those are now queried at runtime.
2017-10-13 18:32:28 +03:00
Slava Monich
49efa5bd18 [test] Added test-ril_util
Conflicts:
	ofono/unit/coverage
2017-10-13 18:32:26 +03:00
Slava Monich
e4a08ddb3f [ril] Added ril_parse_int utility 2017-10-13 18:31:48 +03:00
Slava Monich
b92ddd870e [ril] Don't mix slice and default allocators in ril_plugin.c
And it generally doesn't make sense to use slice allocator for allocating
the structures that are a) large and b) allocated at startup and not freed
until the program exits.
2017-10-13 18:31:48 +03:00
10 changed files with 359 additions and 36 deletions

1
ofono/.gitignore vendored
View File

@@ -42,6 +42,7 @@ unit/test-mux
unit/test-caif
unit/test-stkutil
unit/test-cdmasms
unit/test-ril_util
unit/test-rilmodem-cb
unit/test-rilmodem-cs
unit/test-rilmodem-gprs

View File

@@ -917,6 +917,14 @@ endif
if RILMODEM
if SAILFISH_RILMODEM
unit_test_ril_util_SOURCES = unit/test-ril_util.c drivers/ril/ril_util.c \
src/log.c
unit_test_ril_util_CFLAGS = $(COVERAGE_OPT) $(AM_CFLAGS)
unit_test_ril_util_LDADD = @GLIB_LIBS@ -ldl
unit_objects += $(unit_test_ril_util_OBJECTS)
unit_tests += unit/test-ril_util
else
unit_tests += unit/test-rilmodem-cs \
unit/test-rilmodem-cs \
unit/test-rilmodem-sms \

View File

@@ -14,6 +14,7 @@
*/
#include "ril_config.h"
#include "ril_util.h"
#include "ril_log.h"
#include <gutil_intarray.h>
@@ -190,12 +191,10 @@ GUtilInts *ril_config_get_ints(GKeyFile *file, const char *group,
GUtilIntArray *array = gutil_int_array_new();
while (*ptr) {
const char *str = *ptr++;
char *end = NULL;
long ival = strtol(str, &end, 0);
int val;
if (str[0] && !end[0]) {
gutil_int_array_append(array, ival);
if (ril_parse_int(*ptr++, 0, &val)) {
gutil_int_array_append(array, val);
}
}

View File

@@ -208,8 +208,14 @@ static gboolean ril_network_parse_response(struct ril_network *self,
reg->max_calls = 2;
}
reg->lac = slac ? strtol(slac, NULL, 16) : -1;
reg->ci = sci ? strtol(sci, NULL, 16) : -1;
if (!ril_parse_int(slac, 16, &reg->lac)) {
reg->lac = -1;
}
if (!ril_parse_int(sci, 16, &reg->ci)) {
reg->ci = -1;
}
reg->access_tech = ril_parse_tech(stech, &reg->ril_tech);
DBG_(self, "%s,%s,%s,%d,%s,%s,%s",

View File

@@ -38,10 +38,14 @@
#include <sys/types.h>
#include <sys/syscall.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#define OFONO_API_SUBJECT_TO_CHANGE
#include <ofono/plugin.h>
#include "ofono.h"
#include "storage.h"
#define OFONO_RADIO_ACCESS_MODE_ALL (OFONO_RADIO_ACCESS_MODE_GSM |\
OFONO_RADIO_ACCESS_MODE_UMTS |\
@@ -49,11 +53,10 @@
#define RIL_DEVICE_IDENTITY_RETRIES_LAST 2
#define RADIO_GID 1001
#define RADIO_UID 1001
#define RIL_SUB_SIZE 4
#define RILMODEM_CONF_FILE CONFIGDIR "/ril_subscription.conf"
#define RILMODEM_DEFAULT_IDENTITY "radio:radio"
#define RILMODEM_DEFAULT_SOCK "/dev/socket/rild"
#define RILMODEM_DEFAULT_SOCK2 "/dev/socket/rild2"
#define RILMODEM_DEFAULT_SUB "SUB1"
@@ -77,6 +80,7 @@
* with lower case.
*/
#define RILCONF_SETTINGS_EMPTY "EmptyConfig"
#define RILCONF_SETTINGS_IDENTITY "Identity"
#define RILCONF_SETTINGS_3GHANDOVER "3GLTEHandover"
#define RILCONF_SETTINGS_SET_RADIO_CAP "SetRadioCapability"
@@ -130,9 +134,15 @@ enum ril_set_radio_cap_opt {
RIL_SET_RADIO_CAP_DISABLED
};
struct ril_plugin_identity {
uid_t uid;
gid_t gid;
};
struct ril_plugin_settings {
int dm_flags;
enum ril_set_radio_cap_opt set_radio_cap;
struct ril_plugin_identity identity;
};
typedef struct sailfish_slot_manager_impl {
@@ -142,6 +152,7 @@ typedef struct sailfish_slot_manager_impl {
struct ril_plugin_settings settings;
gulong caps_manager_event_id;
guint start_timeout_id;
MceDisplay *display;
GSList *slots;
} ril_plugin;
@@ -196,6 +207,7 @@ static void ril_debug_dump_notify(struct ofono_debug_desc *desc);
static void ril_debug_grilio_notify(struct ofono_debug_desc *desc);
static void ril_debug_mce_notify(struct ofono_debug_desc *desc);
static void ril_plugin_debug_notify(struct ofono_debug_desc *desc);
static void ril_plugin_drop_orphan_slots(ril_plugin *plugin);
static void ril_plugin_retry_init_io(ril_slot *slot);
static void ril_plugin_check_modem(ril_slot *slot);
@@ -759,6 +771,19 @@ static void ril_plugin_radio_caps_cb(const struct ril_radio_capability *cap,
}
}
static void ril_plugin_manager_started(ril_plugin *plugin)
{
ril_plugin_drop_orphan_slots(plugin);
sailfish_slot_manager_started(plugin->handle);
/*
* We no longer need this MceDisplay reference, the slots
* (if there are any) are holding references of their own.
*/
mce_display_unref(plugin->display);
plugin->display = NULL;
}
static void ril_plugin_all_slots_started_cb(ril_slot *slot, void *param)
{
if (!slot->handle) {
@@ -778,7 +803,7 @@ static void ril_plugin_check_if_started(ril_plugin* plugin)
g_source_remove(plugin->start_timeout_id);
/* id is zeroed by ril_plugin_manager_start_done */
GASSERT(!plugin->start_timeout_id);
sailfish_slot_manager_started(plugin->handle);
ril_plugin_manager_started(plugin);
}
}
}
@@ -995,7 +1020,7 @@ static void ril_slot_free(ril_slot *slot)
g_free(slot->sockpath);
g_free(slot->sub);
g_free(slot->ecclist_file);
g_slice_free(ril_slot, slot);
g_free(slot);
}
static gboolean ril_plugin_slot_start_timeout(gpointer user_data)
@@ -1323,6 +1348,60 @@ static guint ril_plugin_find_unused_slot(GSList *slots)
return number;
}
static void ril_plugin_parse_identity(struct ril_plugin_identity *identity,
const char *value)
{
char *sep = strchr(value, ':');
const char *user = value;
const char *group = NULL;
char *tmp_user = NULL;
const struct passwd *pw = NULL;
const struct group *gr = NULL;
if (sep) {
/* Group */
group = sep + 1;
gr = getgrnam(group);
user = tmp_user = g_strndup(value, sep - value);
if (!gr) {
int n;
/* Try numeric */
if (ril_parse_int(group, 0, &n)) {
gr = getgrgid(n);
}
}
}
/* User */
pw = getpwnam(user);
if (!pw) {
int n;
/* Try numeric */
if (ril_parse_int(user, 0, &n)) {
pw = getpwuid(n);
}
}
if (pw) {
DBG("User %s -> %d", user, pw->pw_uid);
identity->uid = pw->pw_uid;
} else {
ofono_warn("Invalid user '%s'", user);
}
if (gr) {
DBG("Group %s -> %d", group, gr->gr_gid);
identity->gid = gr->gr_gid;
} else if (group) {
ofono_warn("Invalid group '%s'", group);
}
g_free(tmp_user);
}
static GSList *ril_plugin_parse_config_file(GKeyFile *file,
struct ril_plugin_settings *ps)
{
@@ -1343,6 +1422,7 @@ static GSList *ril_plugin_parse_config_file(GKeyFile *file,
} else if (!strcmp(group, RILCONF_SETTINGS_GROUP)) {
/* Plugin configuration */
int ival;
char *sval;
/* 3GLTEHandover */
ril_config_get_flag(file, group,
@@ -1358,6 +1438,14 @@ static GSList *ril_plugin_parse_config_file(GKeyFile *file,
"off", RIL_SET_RADIO_CAP_DISABLED, NULL)) {
ps->set_radio_cap = ival;
}
/* Identity */
sval = g_key_file_get_string(file, group,
RILCONF_SETTINGS_IDENTITY, NULL);
if (sval) {
ril_plugin_parse_identity(&ps->identity, sval);
g_free(sval);
}
}
}
@@ -1412,18 +1500,69 @@ static GSList *ril_plugin_load_config(const char *path,
return list;
}
/* RIL expects user radio */
static void ril_plugin_switch_user()
static void ril_plugin_set_perm(const char *path, mode_t mode,
const struct ril_plugin_identity *id)
{
if (chmod(path, mode)) {
ofono_error("chmod(%s,%o) failed: %s", path, mode,
strerror(errno));
}
if (chown(path, id->uid, id->gid)) {
ofono_error("chown(%s,%d,%d) failed: %s", path, id->uid,
id->gid, strerror(errno));
}
}
/* Recursively updates file and directory ownership and permissions */
static void ril_plugin_set_storage_perm(const char *path,
const struct ril_plugin_identity *id)
{
DIR *d;
const mode_t dir_mode = S_IRUSR | S_IWUSR | S_IXUSR;
const mode_t file_mode = S_IRUSR | S_IWUSR;
ril_plugin_set_perm(path, dir_mode, id);
d = opendir(path);
if (d) {
const struct dirent *p;
while ((p = readdir(d)) != NULL) {
char *buf;
struct stat st;
if (!strcmp(p->d_name, ".") ||
!strcmp(p->d_name, "..")) {
continue;
}
buf = g_strdup_printf("%s/%s", path, p->d_name);
if (!stat(buf, &st)) {
mode_t mode;
if (S_ISDIR(st.st_mode)) {
ril_plugin_set_storage_perm(buf, id);
mode = dir_mode;
} else {
mode = file_mode;
}
ril_plugin_set_perm(buf, mode, id);
}
g_free(buf);
}
closedir(d);
}
}
static void ril_plugin_switch_identity(const struct ril_plugin_identity *id)
{
ril_plugin_set_storage_perm(STORAGEDIR, id);
if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) < 0) {
ofono_error("prctl(PR_SET_KEEPCAPS) failed: %s",
strerror(errno));
} else if (setgid(RADIO_GID) < 0) {
ofono_error("setgid(%d) failed: %s", RADIO_GID,
strerror(errno));
} else if (setuid(RADIO_UID) < 0) {
ofono_error("setuid(%d) failed: %s", RADIO_UID,
strerror(errno));
} else if (setgid(id->gid) < 0) {
ofono_error("setgid(%d) failed: %s", id->gid, strerror(errno));
} else if (setuid(id->uid) < 0) {
ofono_error("setuid(%d) failed: %s", id->uid, strerror(errno));
} else {
struct __user_cap_header_struct header;
struct __user_cap_data_struct cap;
@@ -1480,8 +1619,7 @@ static gboolean ril_plugin_manager_start_timeout(gpointer user_data)
DBG("");
plugin->start_timeout_id = 0;
ril_plugin_drop_orphan_slots(plugin);
sailfish_slot_manager_started(plugin->handle);
ril_plugin_manager_started(plugin);
return G_SOURCE_REMOVE;
}
@@ -1499,12 +1637,21 @@ static void ril_plugin_manager_start_done(gpointer user_data)
static ril_plugin *ril_plugin_manager_create(struct sailfish_slot_manager *m)
{
ril_plugin *plugin = g_slice_new0(ril_plugin);
ril_plugin *plugin = g_new0(ril_plugin, 1);
struct ril_plugin_settings *ps = &plugin->settings;
DBG("");
/*
* Create the MCE client instance early so that connection
* to the system bus gets established before we switch the
* identity.
*/
plugin->display = mce_display_new();
plugin->handle = m;
plugin->settings.dm_flags = RILMODEM_DEFAULT_DM_FLAGS;
plugin->settings.set_radio_cap = RIL_SET_RADIO_CAP_AUTO;
ril_plugin_parse_identity(&ps->identity, RILMODEM_DEFAULT_IDENTITY);
ps->dm_flags = RILMODEM_DEFAULT_DM_FLAGS;
ps->set_radio_cap = RIL_SET_RADIO_CAP_AUTO;
return plugin;
}
@@ -1549,6 +1696,10 @@ static guint ril_plugin_manager_start(ril_plugin *plugin)
ril_plugin_foreach_slot_param(plugin, ril_plugin_slot_check_timeout_cb,
&start_timeout);
/* Switch the user to the one RIL expects */
ril_plugin_switch_identity(&ps->identity);
plugin->start_timeout_id = g_timeout_add_full(G_PRIORITY_DEFAULT,
start_timeout, ril_plugin_manager_start_timeout,
plugin, ril_plugin_manager_start_done);
@@ -1564,11 +1715,12 @@ static void ril_plugin_manager_free(ril_plugin *plugin)
{
if (plugin) {
GASSERT(!plugin->slots);
mce_display_unref(plugin->display);
ril_data_manager_unref(plugin->data_manager);
ril_radio_caps_manager_remove_handler(plugin->caps_manager,
plugin->caps_manager_event_id);
ril_radio_caps_manager_unref(plugin->caps_manager);
g_slice_free(ril_plugin, plugin);
g_free(plugin);
}
}
@@ -1638,9 +1790,6 @@ static gboolean ril_plugin_start(gpointer user_data)
DBG("");
ril_driver_init_id = 0;
/* Switch the user to the one RIL expects */
ril_plugin_switch_user();
/* Register the driver */
ril_driver = sailfish_slot_driver_register(&ril_slot_driver);
return G_SOURCE_REMOVE;

View File

@@ -21,6 +21,13 @@
#
#EmptyConfig=false
# User and group for the ofono process. RIL clients are typically
# expected to run under radio:radio.
#
# Default is radio:radio
#
#Identity=radio:radio
# If the phone has more than one SIM slot, the 3G/LTE module may be
# shared by all modems, meaning that only one of the slots can use
# 3G/LTE. In order to "hand 4G over" to the other slot, the modem

View File

@@ -20,6 +20,8 @@
#include <sys/socket.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include "common.h"
#include "netreg.h"
@@ -324,8 +326,7 @@ int ril_parse_tech(const char *stech, int *ril_tech)
{
int access_tech = -1;
int tech = -1;
if (stech && stech[0]) {
tech = atoi(stech);
if (ril_parse_int(stech, 0, &tech)) {
switch (tech) {
case RADIO_TECH_GPRS:
case RADIO_TECH_GSM:
@@ -382,8 +383,8 @@ gboolean ril_parse_mcc_mnc(const char *str, struct ofono_network_operator *op)
if (i == OFONO_MAX_MCC_LENGTH) {
/* Usually 2 but sometimes 3 digit network code */
for (i=0;
i<OFONO_MAX_MNC_LENGTH && *ptr && isdigit(*ptr);
for (i = 0;
i < OFONO_MAX_MNC_LENGTH && *ptr && isdigit(*ptr);
i++) {
op->mnc[i] = *ptr++;
}
@@ -410,6 +411,26 @@ gboolean ril_parse_mcc_mnc(const char *str, struct ofono_network_operator *op)
return FALSE;
}
gboolean ril_parse_int(const char *str, int base, int *value)
{
gboolean ok = FALSE;
if (str && str[0]) {
char *str2 = g_strstrip(g_strdup(str));
char *end = str2;
long l;
errno = 0;
l = strtol(str2, &end, base);
ok = !*end && errno != ERANGE && l >= INT_MIN && l <= INT_MAX;
if (ok && value) {
*value = (int)l;
}
g_free(str2);
}
return ok;
}
/*
* Local Variables:
* mode: C

View File

@@ -1,7 +1,7 @@
/*
* oFono - Open Source Telephony - RIL-based devices
*
* Copyright (C) 2015-2016 Jolla Ltd.
* Copyright (C) 2015-2017 Jolla Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
@@ -26,6 +26,7 @@ const char *ril_unsol_event_to_string(guint event);
const char *ril_radio_state_to_string(int radio_state);
int ril_parse_tech(const char *stech, int *ril_tech);
gboolean ril_parse_mcc_mnc(const char *str, struct ofono_network_operator *op);
gboolean ril_parse_int(const char *str, int base, int *value);
#define ril_error_init_ok(err) \
((err)->error = 0, (err)->type = OFONO_ERROR_TYPE_NO_ERROR)

View File

@@ -17,6 +17,7 @@ TESTS="\
test-sms-root \
test-caif \
test-provision \
test-ril_util \
test-sailfish_cell_info \
test-sailfish_manager \
test-sailfish_sim_info"
@@ -29,13 +30,14 @@ popd > /dev/null
popd > /dev/null
FULL_COV="$TEST_DIR/full.gcov"
DRIVERS_COV="$TEST_DIR/drivers.gcov"
PLUGINS_COV="$TEST_DIR/plugins.gcov"
SRC_COV="$TEST_DIR/src.gcov"
OUT="$TEST_DIR/html"
# Clean everything up
find "$BASE_DIR" -name "*.gcda" -exec rm {} \;
rm -f "$FULL_COV" "$PLUGINS_COV" "$SRC_COV"
rm -f "$FULL_COV" "$DRIVERS_COV" "$PLUGINS_COV" "$SRC_COV"
rm -fr "$OUT"
# Run the tests
@@ -52,8 +54,9 @@ LCOV_OPT="--rc lcov_branch_coverage=1"
GENHTML_OPT="--branch-coverage"
lcov $LCOV_OPT -c -d "$BASE_DIR" -o "$FULL_COV" || exit 1
lcov $LCOV_OPT -e "$FULL_COV" "$BASE_DIR/drivers/*" -o "$DRIVERS_COV" || exit 1
lcov $LCOV_OPT -e "$FULL_COV" "$BASE_DIR/plugins/*" -o "$PLUGINS_COV" || exit 1
lcov $LCOV_OPT -e "$FULL_COV" "$BASE_DIR/src/*" -o "$SRC_COV" || exit 1
genhtml $GENHTML_OPT -t ofono "$PLUGINS_COV" "$SRC_COV" --output-directory "$OUT" || exit 1
genhtml $GENHTML_OPT -t ofono "$DRIVERS_COV" "$PLUGINS_COV" "$SRC_COV" --output-directory "$OUT" || exit 1
echo Coverage report: $OUT/index.html

128
ofono/unit/test-ril_util.c Normal file
View File

@@ -0,0 +1,128 @@
/*
* oFono - Open Source Telephony
*
* Copyright (C) 2017 Jolla Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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.
*/
#include "drivers/ril/ril_util.h"
#include "ofono.h"
#include "common.h"
void test_parse_tech(void)
{
int tech = 0;
g_assert(ril_parse_tech(NULL, NULL) == -1);
g_assert(ril_parse_tech(NULL, &tech) == -1);
g_assert(tech == -1);
g_assert(ril_parse_tech("-1", &tech) == -1);
g_assert(tech == -1);
g_assert(ril_parse_tech("0", &tech) == -1);
g_assert(tech == -1);
g_assert(ril_parse_tech("1", &tech) == ACCESS_TECHNOLOGY_GSM);
g_assert(tech == RADIO_TECH_GPRS);
g_assert(ril_parse_tech("16", &tech) == ACCESS_TECHNOLOGY_GSM);
g_assert(tech == RADIO_TECH_GSM);
g_assert(ril_parse_tech("2", &tech) == ACCESS_TECHNOLOGY_GSM_EGPRS);
g_assert(tech == RADIO_TECH_EDGE);
g_assert(ril_parse_tech("3", &tech) == ACCESS_TECHNOLOGY_UTRAN);
g_assert(tech == RADIO_TECH_UMTS);
g_assert(ril_parse_tech("9", &tech) == ACCESS_TECHNOLOGY_UTRAN_HSDPA);
g_assert(tech == RADIO_TECH_HSDPA);
g_assert(ril_parse_tech("10", &tech) == ACCESS_TECHNOLOGY_UTRAN_HSUPA);
g_assert(tech == RADIO_TECH_HSUPA);
g_assert(ril_parse_tech("11", &tech) ==
ACCESS_TECHNOLOGY_UTRAN_HSDPA_HSUPA);
g_assert(tech == RADIO_TECH_HSPA);
g_assert(ril_parse_tech("15", &tech) ==
ACCESS_TECHNOLOGY_UTRAN_HSDPA_HSUPA);
g_assert(tech == RADIO_TECH_HSPAP);
g_assert(ril_parse_tech("14", &tech) == ACCESS_TECHNOLOGY_EUTRAN);
g_assert(tech == RADIO_TECH_LTE);
}
void test_parse_mcc_mnc(void)
{
struct ofono_network_operator op;
memset(&op, 0, sizeof(op));
g_assert(!ril_parse_mcc_mnc(NULL, &op));
g_assert(!ril_parse_mcc_mnc("", &op));
g_assert(!ril_parse_mcc_mnc("24x", &op));
g_assert(!ril_parse_mcc_mnc("244", &op));
g_assert(!ril_parse_mcc_mnc("244x", &op));
g_assert(ril_parse_mcc_mnc("24412", &op));
g_assert(!strcmp(op.mcc, "244"));
g_assert(!strcmp(op.mnc, "12"));
g_assert(!op.tech);
g_assert(ril_parse_mcc_mnc("25001+", &op));
g_assert(!strcmp(op.mcc, "250"));
g_assert(!strcmp(op.mnc, "01"));
g_assert(!op.tech);
g_assert(ril_parse_mcc_mnc("25503+14", &op));
g_assert(!strcmp(op.mcc, "255"));
g_assert(!strcmp(op.mnc, "03"));
g_assert(op.tech == ACCESS_TECHNOLOGY_EUTRAN);
/* Not sure if this is right but that's now it currently works: */
op.tech = 0;
g_assert(ril_parse_mcc_mnc("3101500", &op));
g_assert(!strcmp(op.mcc, "310"));
g_assert(!strcmp(op.mnc, "150"));
g_assert(!op.tech);
}
void test_parse_int(void)
{
int value;
g_assert(!ril_parse_int(NULL, 0, NULL));
g_assert(!ril_parse_int("", 0, NULL));
g_assert(!ril_parse_int("garbage", 0, NULL));
g_assert(!ril_parse_int("0 trailing garbage", 0, NULL));
g_assert(ril_parse_int("0", 0, NULL));
g_assert(ril_parse_int("0", 0, &value));
g_assert(value == 0);
g_assert(!ril_parse_int("0x10000000000000000", 0, &value));
g_assert(!ril_parse_int("-2147483649", 0, &value));
g_assert(!ril_parse_int("4294967295", 0, &value));
g_assert(ril_parse_int(" 0x7fffffff ", 0, &value));
g_assert(value == 0x7fffffff);
g_assert(ril_parse_int(" 7fffffff ", 16, &value));
g_assert(value == 0x7fffffff);
g_assert(!ril_parse_int("0xffffffff", 0, &value));
}
#define TEST_(name) "/ril_util/" name
int main(int argc, char *argv[])
{
g_test_init(&argc, &argv, NULL);
__ofono_log_init("test-ril_util",
g_test_verbose() ? "*" : NULL,
FALSE, FALSE);
g_test_add_func(TEST_("parse_tech"), test_parse_tech);
g_test_add_func(TEST_("parse_mcc_mnc"), test_parse_mcc_mnc);
g_test_add_func(TEST_("parse_int"), test_parse_int);
return g_test_run();
}
/*
* Local Variables:
* mode: C
* c-basic-offset: 8
* indent-tabs-mode: t
* End:
*/