blob: d68142d239964cc9d7196384aeda52d86b28c935 [file] [log] [blame]
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* mbimcli -- Command line interface to control MBIM devices
*
* Copyright (C) 2013 - 2014 Aleksander Morgado <aleksander@aleksander.es>
*/
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <locale.h>
#include <string.h>
#include <errno.h>
#include <glib.h>
#include <glib/gprintf.h>
#include <gio/gio.h>
#include <glib-unix.h>
#include <libmbim-glib.h>
#include "mbimcli.h"
#include "mbimcli-helpers.h"
#define PROGRAM_NAME "mbimcli"
#define PROGRAM_VERSION PACKAGE_VERSION
/* Globals */
static GMainLoop *loop;
static GCancellable *cancellable;
static MbimDevice *device;
static MbimService service;
static gboolean operation_status;
/* Main options */
static gchar *device_str;
static gboolean device_open_proxy_flag;
static gboolean device_open_ms_mbimex_v2_flag;
static gboolean device_open_ms_mbimex_v3_flag;
static gchar *no_open_str;
static gboolean no_close_flag;
static gboolean noop_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]"
},
{ "device-open-proxy", 'p', 0, G_OPTION_ARG_NONE, &device_open_proxy_flag,
"Request to use the 'mbim-proxy' proxy",
NULL
},
{ "device-open-ms-mbimex-v2", 0, 0, G_OPTION_ARG_NONE, &device_open_ms_mbimex_v2_flag,
"Request to enable Microsoft MBIMEx v2.0 support",
NULL
},
{ "device-open-ms-mbimex-v3", 0, 0, G_OPTION_ARG_NONE, &device_open_ms_mbimex_v3_flag,
"Request to enable Microsoft MBIMEx v3.0 support",
NULL
},
{ "no-open", 0, 0, G_OPTION_ARG_STRING, &no_open_str,
"Do not explicitly open the MBIM device before running the command",
"[Transaction ID]"
},
{ "no-close", 0, 0, G_OPTION_ARG_NONE, &no_close_flag,
"Do not close the MBIM device after running the command",
NULL
},
{ "noop", 0, 0, G_OPTION_ARG_NONE, &noop_flag,
"Don't run any command",
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 (gpointer psignum)
{
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 */
g_unix_signal_add (GPOINTER_TO_INT (psignum), (GSourceFunc) signals_handler, psignum);
return FALSE;
}
}
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 FALSE;
}
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_LEVEL_ERROR:
log_level_str = "-Error **";
err = TRUE;
break;
case G_LOG_LEVEL_DEBUG:
log_level_str = "[Debug]";
break;
case G_LOG_LEVEL_MESSAGE:
case G_LOG_LEVEL_INFO:
log_level_str = "";
break;
case G_LOG_FLAG_FATAL:
case G_LOG_LEVEL_MASK:
case G_LOG_FLAG_RECURSION:
default:
g_assert_not_reached ();
}
if (!verbose_flag && !err)
return;
g_fprintf (err ? stderr : stdout,
"[%s] %s %s\n",
time_str,
log_level_str,
message);
}
G_GNUC_NORETURN
static void
print_version_and_exit (void)
{
g_print (PROGRAM_NAME " " PROGRAM_VERSION "\n"
"Copyright (C) 2013-2021 Aleksander Morgado\n"
"License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl-2.0.html>\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);
}
/*****************************************************************************/
/* Running asynchronously */
static void
device_close_ready (MbimDevice *dev,
GAsyncResult *res)
{
GError *error = NULL;
if (!mbim_device_close_finish (dev, res, &error)) {
g_printerr ("error: couldn't close device: %s\n", error->message);
g_error_free (error);
} else
g_debug ("Device closed");
/* If we left the device open, dump next transaction id */
if (no_close_flag) {
guint transaction_id;
g_object_get (dev,
MBIM_DEVICE_TRANSACTION_ID, &transaction_id,
NULL);
g_print ("[%s] Session not closed:\n"
"\t TRID: '%u'\n",
mbim_device_get_path_display (dev),
transaction_id);
}
g_main_loop_quit (loop);
}
void
mbimcli_async_operation_done (gboolean reported_operation_status)
{
/* Keep the result of the operation */
operation_status = reported_operation_status;
/* Cleanup cancellation */
g_clear_object (&cancellable);
/* Set the in-session setup */
g_object_set (device,
MBIM_DEVICE_IN_SESSION, no_close_flag,
NULL);
/* Close the device */
mbim_device_close (device,
15,
cancellable,
(GAsyncReadyCallback) device_close_ready,
NULL);
}
static void
device_open_ready (MbimDevice *dev,
GAsyncResult *res)
{
GError *error = NULL;
if (!mbim_device_open_finish (dev, res, &error)) {
g_printerr ("error: couldn't open the MbimDevice: %s\n",
error->message);
exit (EXIT_FAILURE);
}
g_debug ("MBIM Device at '%s' ready",
mbim_device_get_path_display (dev));
/* If no operation requested, finish */
if (noop_flag) {
mbimcli_async_operation_done (TRUE);
return;
}
/* Link management action? */
if (mbimcli_link_management_options_enabled ()) {
mbimcli_link_management_run (dev, cancellable);
return;
}
/* Run the service-specific action */
switch (service) {
case MBIM_SERVICE_BASIC_CONNECT:
mbimcli_basic_connect_run (dev, cancellable);
return;
case MBIM_SERVICE_PHONEBOOK:
mbimcli_phonebook_run (dev, cancellable);
return;
case MBIM_SERVICE_DSS:
mbimcli_dss_run (dev, cancellable);
return;
case MBIM_SERVICE_MS_FIRMWARE_ID:
mbimcli_ms_firmware_id_run (dev, cancellable);
return;
case MBIM_SERVICE_MS_HOST_SHUTDOWN:
mbimcli_ms_host_shutdown_run (dev, cancellable);
return;
case MBIM_SERVICE_MS_SAR:
mbimcli_ms_sar_run (dev, cancellable);
return;
case MBIM_SERVICE_ATDS:
mbimcli_atds_run (dev, cancellable);
return;
case MBIM_SERVICE_INTEL_FIRMWARE_UPDATE:
mbimcli_intel_firmware_update_run (dev, cancellable);
return;
case MBIM_SERVICE_MS_BASIC_CONNECT_EXTENSIONS:
mbimcli_ms_basic_connect_extensions_run (dev, cancellable);
return;
case MBIM_SERVICE_SMS:
case MBIM_SERVICE_USSD:
case MBIM_SERVICE_STK:
case MBIM_SERVICE_AUTH:
case MBIM_SERVICE_PROXY_CONTROL:
case MBIM_SERVICE_QMI:
case MBIM_SERVICE_QDU:
case MBIM_SERVICE_MS_UICC_LOW_LEVEL_ACCESS:
/* unsupported actions in the CLI */
case MBIM_SERVICE_INVALID:
default:
g_assert_not_reached ();
}
}
static void
device_new_ready (GObject *unused,
GAsyncResult *res)
{
GError *error = NULL;
MbimDeviceOpenFlags open_flags = MBIM_DEVICE_OPEN_FLAGS_NONE;
device = mbim_device_new_finish (res, &error);
if (!device) {
g_printerr ("error: couldn't create MbimDevice: %s\n",
error->message);
exit (EXIT_FAILURE);
}
/* Set the in-session setup */
if (no_open_str) {
guint transaction_id;
if (!mbimcli_read_uint_from_string (no_open_str, &transaction_id)) {
g_printerr ("error: invalid transaction ID specified: %s\n",
no_open_str);
exit (EXIT_FAILURE);
}
g_object_set (device,
MBIM_DEVICE_IN_SESSION, TRUE,
MBIM_DEVICE_TRANSACTION_ID, transaction_id,
NULL);
}
/* Setup device open flags */
if (device_open_proxy_flag)
open_flags |= MBIM_DEVICE_OPEN_FLAGS_PROXY;
if (device_open_ms_mbimex_v2_flag)
open_flags |= MBIM_DEVICE_OPEN_FLAGS_MS_MBIMEX_V2;
if (device_open_ms_mbimex_v3_flag)
open_flags |= MBIM_DEVICE_OPEN_FLAGS_MS_MBIMEX_V3;
/* Open the device */
mbim_device_open_full (device,
open_flags,
30,
cancellable,
(GAsyncReadyCallback) device_open_ready,
NULL);
}
/*****************************************************************************/
static void
parse_actions (void)
{
guint actions_enabled = 0;
if (mbimcli_link_management_options_enabled ())
actions_enabled++;
if (mbimcli_basic_connect_options_enabled ()) {
service = MBIM_SERVICE_BASIC_CONNECT;
actions_enabled++;
}
if (mbimcli_phonebook_options_enabled ()) {
service = MBIM_SERVICE_PHONEBOOK;
actions_enabled++;
}
if (mbimcli_dss_options_enabled ()) {
service = MBIM_SERVICE_DSS;
actions_enabled++;
}
if (mbimcli_ms_firmware_id_options_enabled ()) {
service = MBIM_SERVICE_MS_FIRMWARE_ID;
actions_enabled++;
}
if (mbimcli_ms_host_shutdown_options_enabled ()) {
service = MBIM_SERVICE_MS_HOST_SHUTDOWN;
actions_enabled++;
}
if (mbimcli_ms_sar_options_enabled ()) {
service = MBIM_SERVICE_MS_SAR;
actions_enabled++;
}
if (mbimcli_atds_options_enabled ()) {
service = MBIM_SERVICE_ATDS;
actions_enabled++;
}
if (mbimcli_intel_firmware_update_options_enabled ()) {
service = MBIM_SERVICE_INTEL_FIRMWARE_UPDATE;
actions_enabled++;
}
if (mbimcli_ms_basic_connect_extensions_options_enabled ()) {
service = MBIM_SERVICE_MS_BASIC_CONNECT_EXTENSIONS;
actions_enabled++;
}
/* Noop */
if (noop_flag)
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);
}
if (device_open_ms_mbimex_v2_flag && device_open_ms_mbimex_v3_flag) {
g_printerr ("error: cannot request both MBIMEx v2.0 and 3.0 at the same time\n");
exit (EXIT_FAILURE);
}
/* Go on! */
}
int main (int argc, char **argv)
{
g_autoptr(GError) error = NULL;
g_autoptr(GFile) file = NULL;
g_autoptr(GOptionContext) context = NULL;
setlocale (LC_ALL, "");
/* Setup option context, process it and destroy it */
context = g_option_context_new ("- Control MBIM devices");
g_option_context_add_group (context, mbimcli_basic_connect_get_option_group ());
g_option_context_add_group (context, mbimcli_phonebook_get_option_group ());
g_option_context_add_group (context, mbimcli_dss_get_option_group ());
g_option_context_add_group (context, mbimcli_ms_firmware_id_get_option_group ());
g_option_context_add_group (context, mbimcli_ms_host_shutdown_get_option_group ());
g_option_context_add_group (context, mbimcli_ms_sar_get_option_group ());
g_option_context_add_group (context, mbimcli_atds_get_option_group ());
g_option_context_add_group (context, mbimcli_intel_firmware_update_get_option_group ());
g_option_context_add_group (context, mbimcli_ms_basic_connect_extensions_get_option_group ());
g_option_context_add_group (context, mbimcli_link_management_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);
}
if (version_flag)
print_version_and_exit ();
g_log_set_handler (NULL, G_LOG_LEVEL_MASK, log_handler, NULL);
g_log_set_handler ("Mbim", G_LOG_LEVEL_MASK, log_handler, NULL);
if (verbose_flag)
mbim_utils_set_traces_enabled (TRUE);
/* 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, GUINT_TO_POINTER (SIGINT));
g_unix_signal_add (SIGHUP, (GSourceFunc)signals_handler, GUINT_TO_POINTER (SIGHUP));
g_unix_signal_add (SIGTERM, (GSourceFunc)signals_handler, GUINT_TO_POINTER (SIGTERM));
/* Launch MbimDevice creation */
mbim_device_new (file, cancellable, (GAsyncReadyCallback)device_new_ready, NULL);
g_main_loop_run (loop);
if (cancellable)
g_object_unref (cancellable);
if (device)
g_object_unref (device);
g_main_loop_unref (loop);
return (operation_status ? EXIT_SUCCESS : EXIT_FAILURE);
}