/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* qmicli -- Command line interface to control QMI devices
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* Copyright (C) 2012-2017 Aleksander Morgado
*/
#include "config.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#if defined MBIM_QMUX_ENABLED
#include
#endif
#include "qmicli.h"
#include "qmicli-helpers.h"
#define PROGRAM_NAME "qmicli"
#define PROGRAM_VERSION PACKAGE_VERSION
/* Globals */
static GMainLoop *loop;
static GCancellable *cancellable;
static QmiDevice *device;
static QmiClient *client;
static QmiService service;
static gboolean operation_status;
/* Main options */
static gchar *device_str;
static gboolean get_service_version_info_flag;
static gboolean get_wwan_iface_flag;
static gboolean get_expected_data_format_flag;
static gchar *set_expected_data_format_str;
static gchar *device_set_instance_id_str;
static gboolean device_open_version_info_flag;
static gboolean device_open_sync_flag;
static gchar *device_open_net_str;
static gboolean device_open_proxy_flag;
static gboolean device_open_qmi_flag;
static gboolean device_open_mbim_flag;
static gboolean device_open_auto_flag;
static gchar *client_cid_str;
static gboolean client_no_release_cid_flag;
static gboolean verbose_flag;
static gboolean silent_flag;
static gboolean version_flag;
static GOptionEntry main_entries[] = {
{ "device", 'd', 0, G_OPTION_ARG_STRING, &device_str,
"Specify device path",
"[PATH]"
},
{ "get-wwan-iface", 'w', 0, G_OPTION_ARG_NONE, &get_wwan_iface_flag,
"Get the WWAN iface name associated with this control port",
NULL
},
{ "get-expected-data-format", 'e', 0, G_OPTION_ARG_NONE, &get_expected_data_format_flag,
"Get the expected data format in the WWAN iface",
NULL
},
{ "set-expected-data-format", 'E', 0, G_OPTION_ARG_STRING, &set_expected_data_format_str,
"Set the expected data format in the WWAN iface",
"[802-3|raw-ip]"
},
{ "get-service-version-info", 0, 0, G_OPTION_ARG_NONE, &get_service_version_info_flag,
"Get service version info",
NULL
},
{ "device-set-instance-id", 0, 0, G_OPTION_ARG_STRING, &device_set_instance_id_str,
"Set instance ID",
"[Instance ID]"
},
{ "device-open-version-info", 0, 0, G_OPTION_ARG_NONE, &device_open_version_info_flag,
"Run version info check when opening device",
NULL
},
{ "device-open-sync", 0, 0, G_OPTION_ARG_NONE, &device_open_sync_flag,
"Run sync operation when opening device",
NULL
},
{ "device-open-proxy", 'p', 0, G_OPTION_ARG_NONE, &device_open_proxy_flag,
"Request to use the 'qmi-proxy' proxy",
NULL
},
{ "device-open-qmi", 0, 0, G_OPTION_ARG_NONE, &device_open_qmi_flag,
"Open a cdc-wdm device explicitly in QMI mode",
NULL
},
{ "device-open-mbim", 0, 0, G_OPTION_ARG_NONE, &device_open_mbim_flag,
"Open a cdc-wdm device explicitly in MBIM mode",
NULL
},
{ "device-open-auto", 0, 0, G_OPTION_ARG_NONE, &device_open_auto_flag,
"Open a cdc-wdm device in either QMI or MBIM mode (default)",
NULL
},
{ "device-open-net", 0, 0, G_OPTION_ARG_STRING, &device_open_net_str,
"Open device with specific link protocol and QoS flags",
"[net-802-3|net-raw-ip|net-qos-header|net-no-qos-header]"
},
{ "client-cid", 0, 0, G_OPTION_ARG_STRING, &client_cid_str,
"Use the given CID, don't allocate a new one",
"[CID]"
},
{ "client-no-release-cid", 0, 0, G_OPTION_ARG_NONE, &client_no_release_cid_flag,
"Do not release the CID when exiting",
NULL
},
{ "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose_flag,
"Run action with verbose logs, including the debug ones",
NULL
},
{ "silent", 0, 0, G_OPTION_ARG_NONE, &silent_flag,
"Run action with no logs; not even the error/warning ones",
NULL
},
{ "version", 'V', 0, G_OPTION_ARG_NONE, &version_flag,
"Print version",
NULL
},
{ NULL }
};
static gboolean
signals_handler (void)
{
if (cancellable) {
/* Ignore consecutive requests of cancellation */
if (!g_cancellable_is_cancelled (cancellable)) {
g_printerr ("cancelling the operation...\n");
g_cancellable_cancel (cancellable);
/* Re-set the signal handler to allow main loop cancellation on
* second signal */
return G_SOURCE_CONTINUE;
}
}
if (loop && g_main_loop_is_running (loop)) {
g_printerr ("cancelling the main loop...\n");
g_idle_add ((GSourceFunc) g_main_loop_quit, loop);
}
return G_SOURCE_REMOVE;
}
static void
log_handler (const gchar *log_domain,
GLogLevelFlags log_level,
const gchar *message,
gpointer user_data)
{
const gchar *log_level_str;
time_t now;
gchar time_str[64];
struct tm *local_time;
gboolean err;
/* Nothing to do if we're silent */
if (silent_flag)
return;
now = time ((time_t *) NULL);
local_time = localtime (&now);
strftime (time_str, 64, "%d %b %Y, %H:%M:%S", local_time);
err = FALSE;
switch (log_level) {
case G_LOG_LEVEL_WARNING:
log_level_str = "-Warning **";
err = TRUE;
break;
case G_LOG_LEVEL_CRITICAL:
case G_LOG_FLAG_FATAL:
case G_LOG_LEVEL_ERROR:
log_level_str = "-Error **";
err = TRUE;
break;
case G_LOG_LEVEL_DEBUG:
log_level_str = "[Debug]";
break;
default:
log_level_str = "";
break;
}
if (!verbose_flag && !err)
return;
g_fprintf (err ? stderr : stdout,
"[%s] %s %s\n",
time_str,
log_level_str,
message);
}
static void
print_version_and_exit (void)
{
g_print ("\n"
PROGRAM_NAME " " PROGRAM_VERSION "\n"
"Copyright (C) 2012-2017 Aleksander Morgado\n"
"License GPLv2+: GNU GPL version 2 or later \n"
"This is free software: you are free to change and redistribute it.\n"
"There is NO WARRANTY, to the extent permitted by law.\n"
"\n");
exit (EXIT_SUCCESS);
}
static gboolean
generic_options_enabled (void)
{
static guint n_actions = 0;
static gboolean checked = FALSE;
if (checked)
return !!n_actions;
n_actions = (!!device_set_instance_id_str +
get_service_version_info_flag +
get_wwan_iface_flag +
get_expected_data_format_flag +
!!set_expected_data_format_str);
if (n_actions > 1) {
g_printerr ("error: too many generic actions requested\n");
exit (EXIT_FAILURE);
}
checked = TRUE;
return !!n_actions;
}
/*****************************************************************************/
/* Running asynchronously */
static void
close_ready (QmiDevice *dev,
GAsyncResult *res)
{
GError *error = NULL;
if (!qmi_device_close_finish (dev, res, &error)) {
g_printerr ("error: couldn't close: %s\n", error->message);
g_error_free (error);
} else
g_debug ("Closed");
g_main_loop_quit (loop);
}
static void
release_client_ready (QmiDevice *dev,
GAsyncResult *res)
{
GError *error = NULL;
if (!qmi_device_release_client_finish (dev, res, &error)) {
g_printerr ("error: couldn't release client: %s\n", error->message);
g_error_free (error);
} else
g_debug ("Client released");
qmi_device_close_async (dev, 10, NULL, (GAsyncReadyCallback) close_ready, NULL);
}
void
qmicli_async_operation_done (gboolean reported_operation_status,
gboolean skip_cid_release)
{
QmiDeviceReleaseClientFlags flags = QMI_DEVICE_RELEASE_CLIENT_FLAGS_NONE;
/* Keep the result of the operation */
operation_status = reported_operation_status;
/* Cleanup cancellation */
g_clear_object (&cancellable);
/* If no client was allocated (e.g. generic action), just quit */
if (!client) {
g_main_loop_quit (loop);
return;
}
if (skip_cid_release)
g_debug ("Skipped CID release");
else if (!client_no_release_cid_flag)
flags |= QMI_DEVICE_RELEASE_CLIENT_FLAGS_RELEASE_CID;
else
g_print ("[%s] Client ID not released:\n"
"\tService: '%s'\n"
"\t CID: '%u'\n",
qmi_device_get_path_display (device),
qmi_service_get_string (service),
qmi_client_get_cid (client));
qmi_device_release_client (device,
client,
flags,
10,
NULL,
(GAsyncReadyCallback)release_client_ready,
NULL);
}
static void
allocate_client_ready (QmiDevice *dev,
GAsyncResult *res)
{
GError *error = NULL;
client = qmi_device_allocate_client_finish (dev, res, &error);
if (!client) {
g_printerr ("error: couldn't create client for the '%s' service: %s\n",
qmi_service_get_string (service),
error->message);
exit (EXIT_FAILURE);
}
/* Run the service-specific action */
switch (service) {
case QMI_SERVICE_DMS:
qmicli_dms_run (dev, QMI_CLIENT_DMS (client), cancellable);
return;
case QMI_SERVICE_NAS:
qmicli_nas_run (dev, QMI_CLIENT_NAS (client), cancellable);
return;
case QMI_SERVICE_WDS:
qmicli_wds_run (dev, QMI_CLIENT_WDS (client), cancellable);
return;
case QMI_SERVICE_PBM:
qmicli_pbm_run (dev, QMI_CLIENT_PBM (client), cancellable);
return;
case QMI_SERVICE_PDC:
qmicli_pdc_run (dev, QMI_CLIENT_PDC (client), cancellable);
return;
case QMI_SERVICE_UIM:
qmicli_uim_run (dev, QMI_CLIENT_UIM (client), cancellable);
return;
case QMI_SERVICE_WMS:
qmicli_wms_run (dev, QMI_CLIENT_WMS (client), cancellable);
return;
case QMI_SERVICE_WDA:
qmicli_wda_run (dev, QMI_CLIENT_WDA (client), cancellable);
return;
case QMI_SERVICE_VOICE:
qmicli_voice_run (dev, QMI_CLIENT_VOICE (client), cancellable);
return;
default:
g_assert_not_reached ();
}
}
static void
device_allocate_client (QmiDevice *dev)
{
guint8 cid = QMI_CID_NONE;
if (client_cid_str) {
guint32 cid32;
cid32 = atoi (client_cid_str);
if (!cid32 || cid32 > G_MAXUINT8) {
g_printerr ("error: invalid CID given '%s'\n",
client_cid_str);
exit (EXIT_FAILURE);
}
cid = (guint8)cid32;
g_debug ("Reusing CID '%u'", cid);
}
/* As soon as we get the QmiDevice, create a client for the requested
* service */
qmi_device_allocate_client (dev,
service,
cid,
10,
cancellable,
(GAsyncReadyCallback)allocate_client_ready,
NULL);
}
static void
set_instance_id_ready (QmiDevice *dev,
GAsyncResult *res)
{
GError *error = NULL;
guint16 link_id;
if (!qmi_device_set_instance_id_finish (dev, res, &link_id, &error)) {
g_printerr ("error: couldn't set instance ID: %s\n",
error->message);
exit (EXIT_FAILURE);
}
g_print ("[%s] Instance ID set:\n"
"\tLink ID: '%" G_GUINT16_FORMAT "'\n",
qmi_device_get_path_display (dev),
link_id);
/* We're done now */
qmicli_async_operation_done (TRUE, FALSE);
}
static void
device_set_instance_id (QmiDevice *dev)
{
gint instance_id;
if (g_str_equal (device_set_instance_id_str, "0"))
instance_id = 0;
else {
instance_id = atoi (device_set_instance_id_str);
if (instance_id == 0) {
g_printerr ("error: invalid instance ID given: '%s'\n", device_set_instance_id_str);
exit (EXIT_FAILURE);
} else if (instance_id < 0 || instance_id > G_MAXUINT8) {
g_printerr ("error: given instance ID is out of range [0,%u]: '%s'\n",
G_MAXUINT8,
device_set_instance_id_str);
exit (EXIT_FAILURE);
}
}
g_debug ("Setting instance ID '%d'...", instance_id);
qmi_device_set_instance_id (dev,
(guint8)instance_id,
10,
cancellable,
(GAsyncReadyCallback)set_instance_id_ready,
NULL);
}
static void
get_service_version_info_ready (QmiDevice *dev,
GAsyncResult *res)
{
GError *error = NULL;
GArray *services;
guint i;
services = qmi_device_get_service_version_info_finish (dev, res, &error);
if (!services) {
g_printerr ("error: couldn't get service version info: %s\n",
error->message);
exit (EXIT_FAILURE);
}
g_print ("[%s] Supported versions:\n",
qmi_device_get_path_display (dev));
for (i = 0; i < services->len; i++) {
QmiDeviceServiceVersionInfo *info;
const gchar *service_str;
info = &g_array_index (services, QmiDeviceServiceVersionInfo, i);
service_str = qmi_service_get_string (info->service);
if (service_str)
g_print ("\t%s (%u.%u)\n",
service_str,
info->major_version,
info->minor_version);
else
g_print ("\tunknown [0x%02x] (%u.%u)\n",
info->service,
info->major_version,
info->minor_version);
}
g_array_unref (services);
/* We're done now */
qmicli_async_operation_done (TRUE, FALSE);
}
static void
device_get_service_version_info (QmiDevice *dev)
{
g_debug ("Getting service version info...");
qmi_device_get_service_version_info (dev,
10,
cancellable,
(GAsyncReadyCallback)get_service_version_info_ready,
NULL);
}
static gboolean
device_set_expected_data_format_cb (QmiDevice *dev)
{
QmiDeviceExpectedDataFormat expected;
GError *error = NULL;
if (!qmicli_read_expected_data_format_from_string (set_expected_data_format_str, &expected) ||
expected == QMI_DEVICE_EXPECTED_DATA_FORMAT_UNKNOWN)
g_printerr ("error: invalid requested data format: %s", set_expected_data_format_str);
else if (!qmi_device_set_expected_data_format (dev, expected, &error)) {
g_printerr ("error: cannot set expected data format: %s\n", error->message);
g_error_free (error);
} else
g_print ("[%s] expected data format set to: %s\n",
qmi_device_get_path_display (dev),
qmi_device_expected_data_format_get_string (expected));
/* We're done now */
qmicli_async_operation_done (!error, FALSE);
g_object_unref (dev);
return FALSE;
}
static void
device_set_expected_data_format (QmiDevice *dev)
{
g_debug ("Setting expected WWAN data format this control port...");
g_idle_add ((GSourceFunc) device_set_expected_data_format_cb, g_object_ref (dev));
}
static gboolean
device_get_expected_data_format_cb (QmiDevice *dev)
{
QmiDeviceExpectedDataFormat expected;
GError *error = NULL;
expected = qmi_device_get_expected_data_format (dev, &error);
if (expected == QMI_DEVICE_EXPECTED_DATA_FORMAT_UNKNOWN) {
g_printerr ("error: cannot get expected data format: %s\n", error->message);
g_error_free (error);
} else
g_print ("%s\n", qmi_device_expected_data_format_get_string (expected));
/* We're done now */
qmicli_async_operation_done (!error, FALSE);
g_object_unref (dev);
return FALSE;
}
static void
device_get_expected_data_format (QmiDevice *dev)
{
g_debug ("Getting expected WWAN data format this control port...");
g_idle_add ((GSourceFunc) device_get_expected_data_format_cb, g_object_ref (dev));
}
static gboolean
device_get_wwan_iface_cb (QmiDevice *dev)
{
const gchar *wwan_iface;
wwan_iface = qmi_device_get_wwan_iface (dev);
if (!wwan_iface)
g_printerr ("error: cannot get WWAN interface name\n");
else
g_print ("%s\n", wwan_iface);
/* We're done now */
qmicli_async_operation_done (!!wwan_iface, FALSE);
g_object_unref (dev);
return FALSE;
}
static void
device_get_wwan_iface (QmiDevice *dev)
{
g_debug ("Getting WWAN iface for this control port...");
g_idle_add ((GSourceFunc) device_get_wwan_iface_cb, g_object_ref (dev));
}
static void
device_open_ready (QmiDevice *dev,
GAsyncResult *res)
{
GError *error = NULL;
if (!qmi_device_open_finish (dev, res, &error)) {
g_printerr ("error: couldn't open the QmiDevice: %s\n",
error->message);
exit (EXIT_FAILURE);
}
g_debug ("QMI Device at '%s' ready",
qmi_device_get_path_display (dev));
if (device_set_instance_id_str)
device_set_instance_id (dev);
else if (get_service_version_info_flag)
device_get_service_version_info (dev);
else if (get_wwan_iface_flag)
device_get_wwan_iface (dev);
else if (get_expected_data_format_flag)
device_get_expected_data_format (dev);
else if (set_expected_data_format_str)
device_set_expected_data_format (dev);
else
device_allocate_client (dev);
}
static void
device_new_ready (GObject *unused,
GAsyncResult *res)
{
QmiDeviceOpenFlags open_flags = QMI_DEVICE_OPEN_FLAGS_NONE;
GError *error = NULL;
device = qmi_device_new_finish (res, &error);
if (!device) {
g_printerr ("error: couldn't create QmiDevice: %s\n",
error->message);
exit (EXIT_FAILURE);
}
if (device_open_mbim_flag + device_open_qmi_flag + device_open_auto_flag > 1) {
g_printerr ("error: cannot specify multiple mode flags to open device\n");
exit (EXIT_FAILURE);
}
/* Setup device open flags */
if (device_open_version_info_flag)
open_flags |= QMI_DEVICE_OPEN_FLAGS_VERSION_INFO;
if (device_open_sync_flag)
open_flags |= QMI_DEVICE_OPEN_FLAGS_SYNC;
if (device_open_proxy_flag)
open_flags |= QMI_DEVICE_OPEN_FLAGS_PROXY;
if (device_open_mbim_flag)
open_flags |= QMI_DEVICE_OPEN_FLAGS_MBIM;
if (device_open_auto_flag || (!device_open_qmi_flag && !device_open_mbim_flag))
open_flags |= QMI_DEVICE_OPEN_FLAGS_AUTO;
if (device_open_net_str)
if (!qmicli_read_net_open_flags_from_string (device_open_net_str, &open_flags))
exit (EXIT_FAILURE);
/* Open the device */
qmi_device_open (device,
open_flags,
15,
cancellable,
(GAsyncReadyCallback)device_open_ready,
NULL);
}
/*****************************************************************************/
static void
parse_actions (void)
{
guint actions_enabled = 0;
/* Generic options? */
if (generic_options_enabled ()) {
service = QMI_SERVICE_CTL;
actions_enabled++;
}
/* DMS options? */
if (qmicli_dms_options_enabled ()) {
service = QMI_SERVICE_DMS;
actions_enabled++;
}
/* NAS options? */
if (qmicli_nas_options_enabled ()) {
service = QMI_SERVICE_NAS;
actions_enabled++;
}
/* WDS options? */
if (qmicli_wds_options_enabled ()) {
service = QMI_SERVICE_WDS;
actions_enabled++;
}
/* PBM options? */
if (qmicli_pbm_options_enabled ()) {
service = QMI_SERVICE_PBM;
actions_enabled++;
}
/* PDC options? */
if (qmicli_pdc_options_enabled ()) {
service = QMI_SERVICE_PDC;
actions_enabled++;
}
/* UIM options? */
if (qmicli_uim_options_enabled ()) {
service = QMI_SERVICE_UIM;
actions_enabled++;
}
/* WMS options? */
if (qmicli_wms_options_enabled ()) {
service = QMI_SERVICE_WMS;
actions_enabled++;
}
/* WDA options? */
if (qmicli_wda_options_enabled ()) {
service = QMI_SERVICE_WDA;
actions_enabled++;
}
/* VOICE options? */
if (qmicli_voice_options_enabled ()) {
service = QMI_SERVICE_VOICE;
actions_enabled++;
}
/* Cannot mix actions from different services */
if (actions_enabled > 1) {
g_printerr ("error: cannot execute multiple actions of different services\n");
exit (EXIT_FAILURE);
}
/* No options? */
if (actions_enabled == 0) {
g_printerr ("error: no actions specified\n");
exit (EXIT_FAILURE);
}
/* Go on! */
}
int main (int argc, char **argv)
{
GError *error = NULL;
GFile *file;
GOptionContext *context;
setlocale (LC_ALL, "");
/* Setup option context, process it and destroy it */
context = g_option_context_new ("- Control QMI devices");
g_option_context_add_group (context,
qmicli_dms_get_option_group ());
g_option_context_add_group (context,
qmicli_nas_get_option_group ());
g_option_context_add_group (context,
qmicli_wds_get_option_group ());
g_option_context_add_group (context,
qmicli_pbm_get_option_group ());
g_option_context_add_group (context,
qmicli_pdc_get_option_group ());
g_option_context_add_group (context,
qmicli_uim_get_option_group ());
g_option_context_add_group (context,
qmicli_wms_get_option_group ());
g_option_context_add_group (context,
qmicli_wda_get_option_group ());
g_option_context_add_group (context,
qmicli_voice_get_option_group ());
g_option_context_add_main_entries (context, main_entries, NULL);
if (!g_option_context_parse (context, &argc, &argv, &error)) {
g_printerr ("error: %s\n",
error->message);
exit (EXIT_FAILURE);
}
g_option_context_free (context);
if (version_flag)
print_version_and_exit ();
g_log_set_handler (NULL, G_LOG_LEVEL_MASK, log_handler, NULL);
g_log_set_handler ("Qmi", G_LOG_LEVEL_MASK, log_handler, NULL);
if (verbose_flag)
qmi_utils_set_traces_enabled (TRUE);
#if defined MBIM_QMUX_ENABLED
/* libmbim logging */
g_log_set_handler ("Mbim", G_LOG_LEVEL_MASK, log_handler, NULL);
if (verbose_flag)
mbim_utils_set_traces_enabled (TRUE);
#endif
/* No device path given? */
if (!device_str) {
g_printerr ("error: no device path specified\n");
exit (EXIT_FAILURE);
}
/* Build new GFile from the commandline arg */
file = g_file_new_for_commandline_arg (device_str);
parse_actions ();
/* Create requirements for async options */
cancellable = g_cancellable_new ();
loop = g_main_loop_new (NULL, FALSE);
/* Setup signals */
g_unix_signal_add (SIGINT, (GSourceFunc) signals_handler, NULL);
g_unix_signal_add (SIGHUP, (GSourceFunc) signals_handler, NULL);
g_unix_signal_add (SIGTERM, (GSourceFunc) signals_handler, NULL);
/* Launch QmiDevice creation */
qmi_device_new (file,
cancellable,
(GAsyncReadyCallback)device_new_ready,
NULL);
g_main_loop_run (loop);
if (cancellable)
g_object_unref (cancellable);
if (client)
g_object_unref (client);
if (device)
g_object_unref (device);
g_main_loop_unref (loop);
g_object_unref (file);
return (operation_status ? EXIT_SUCCESS : EXIT_FAILURE);
}