| /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
| |
| /* |
| * libqmi-glib -- GLib/GIO based library to control QMI devices |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library 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 |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301 USA. |
| * |
| * Copyright (C) 2012-2020 Dan Williams <dcbw@redhat.com> |
| * Copyright (C) 2012-2020 Aleksander Morgado <aleksander@aleksander.es> |
| */ |
| |
| #include <config.h> |
| |
| #define _GNU_SOURCE |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <pwd.h> |
| #include <errno.h> |
| |
| #include "qmi-helpers.h" |
| #include "qmi-error-types.h" |
| |
| /*****************************************************************************/ |
| |
| gboolean |
| qmi_helpers_check_user_allowed (uid_t uid, |
| GError **error) |
| { |
| #ifndef QMI_USERNAME_ENABLED |
| if (uid == 0) |
| return TRUE; |
| #else |
| # ifndef QMI_USERNAME |
| # error QMI username not defined |
| # endif |
| |
| struct passwd *expected_usr = NULL; |
| |
| /* Root user is always allowed, regardless of the specified QMI_USERNAME */ |
| if (uid == 0) |
| return TRUE; |
| |
| expected_usr = getpwnam (QMI_USERNAME); |
| if (!expected_usr) { |
| g_set_error (error, |
| QMI_CORE_ERROR, |
| QMI_CORE_ERROR_FAILED, |
| "Not enough privileges (unknown username %s)", QMI_USERNAME); |
| return FALSE; |
| } |
| |
| if (uid == expected_usr->pw_uid) |
| return TRUE; |
| #endif |
| |
| g_set_error (error, |
| QMI_CORE_ERROR, |
| QMI_CORE_ERROR_FAILED, |
| "Not enough privileges"); |
| return FALSE; |
| } |
| |
| /*****************************************************************************/ |
| |
| gboolean |
| qmi_helpers_string_utf8_validate_printable (const guint8 *utf8, |
| gsize utf8_len) |
| { |
| const gchar *p; |
| const gchar *init; |
| |
| g_assert (utf8); |
| g_assert (utf8_len); |
| |
| /* Ignore all trailing NUL bytes, if any */ |
| while ((utf8_len > 0) && (utf8[utf8_len - 1] == '\0')) |
| utf8_len--; |
| |
| /* An empty string */ |
| if (!utf8_len) |
| return TRUE; |
| |
| /* First check if valid UTF-8 */ |
| init = (const gchar *)utf8; |
| if (!g_utf8_validate (init, utf8_len, NULL)) |
| return FALSE; |
| |
| /* Then check if contents are printable. If one is not, |
| * check fails. */ |
| for (p = init; (gsize)(p - init) < utf8_len; p = g_utf8_next_char (p)) { |
| gunichar unichar; |
| |
| /* Explicitly allow CR and LF even if they're control characters, given |
| * that NMEA traces reported via QMI LOC indications seem to have these |
| * suffixed. |
| * Also, explicitly allow TAB as some manufacturers seem to include it |
| * e.g. in model info strings. */ |
| if (*p == '\r' || *p == '\n' || *p == '\t') |
| continue; |
| |
| unichar = g_utf8_get_char (p); |
| if (!g_unichar_isprint (unichar)) |
| return FALSE; |
| } |
| return TRUE; |
| } |
| |
| /*****************************************************************************/ |
| /* GSM 03.38 encoding conversion stuff, imported from ModemManager */ |
| |
| #define GSM_DEF_ALPHABET_SIZE 128 |
| #define GSM_EXT_ALPHABET_SIZE 10 |
| |
| typedef struct GsmUtf8Mapping { |
| gchar chars[3]; |
| guint8 len; |
| guint8 gsm; /* only used for extended GSM charset */ |
| } GsmUtf8Mapping; |
| |
| #define ONE(a) { {a, 0x00, 0x00}, 1, 0 } |
| #define TWO(a, b) { {a, b, 0x00}, 2, 0 } |
| |
| /* |
| * Mapping from GSM default alphabet to UTF-8. |
| * |
| * ETSI GSM 03.38, version 6.0.1, section 6.2.1; Default alphabet. Mapping to UCS-2. |
| * Mapping according to http://unicode.org/Public/MAPPINGS/ETSI/GSM0338.TXT |
| */ |
| static const GsmUtf8Mapping gsm_def_utf8_alphabet[GSM_DEF_ALPHABET_SIZE] = { |
| /* @ £ $ ¥ */ |
| ONE(0x40), TWO(0xc2, 0xa3), ONE(0x24), TWO(0xc2, 0xa5), |
| /* è é ù ì */ |
| TWO(0xc3, 0xa8), TWO(0xc3, 0xa9), TWO(0xc3, 0xb9), TWO(0xc3, 0xac), |
| /* ò Ç \n Ø */ |
| TWO(0xc3, 0xb2), TWO(0xc3, 0x87), ONE(0x0a), TWO(0xc3, 0x98), |
| /* ø \r Å å */ |
| TWO(0xc3, 0xb8), ONE(0x0d), TWO(0xc3, 0x85), TWO(0xc3, 0xa5), |
| /* Δ _ Φ Γ */ |
| TWO(0xce, 0x94), ONE(0x5f), TWO(0xce, 0xa6), TWO(0xce, 0x93), |
| /* Λ Ω Π Ψ */ |
| TWO(0xce, 0x9b), TWO(0xce, 0xa9), TWO(0xce, 0xa0), TWO(0xce, 0xa8), |
| /* Σ Θ Ξ Escape Code */ |
| TWO(0xce, 0xa3), TWO(0xce, 0x98), TWO(0xce, 0x9e), ONE(0xa0), |
| /* Æ æ ß É */ |
| TWO(0xc3, 0x86), TWO(0xc3, 0xa6), TWO(0xc3, 0x9f), TWO(0xc3, 0x89), |
| /* ' ' ! " # */ |
| ONE(0x20), ONE(0x21), ONE(0x22), ONE(0x23), |
| /* ¤ % & ' */ |
| TWO(0xc2, 0xa4), ONE(0x25), ONE(0x26), ONE(0x27), |
| /* ( ) * + */ |
| ONE(0x28), ONE(0x29), ONE(0x2a), ONE(0x2b), |
| /* , - . / */ |
| ONE(0x2c), ONE(0x2d), ONE(0x2e), ONE(0x2f), |
| /* 0 1 2 3 */ |
| ONE(0x30), ONE(0x31), ONE(0x32), ONE(0x33), |
| /* 4 5 6 7 */ |
| ONE(0x34), ONE(0x35), ONE(0x36), ONE(0x37), |
| /* 8 9 : ; */ |
| ONE(0x38), ONE(0x39), ONE(0x3a), ONE(0x3b), |
| /* < = > ? */ |
| ONE(0x3c), ONE(0x3d), ONE(0x3e), ONE(0x3f), |
| /* ¡ A B C */ |
| TWO(0xc2, 0xa1), ONE(0x41), ONE(0x42), ONE(0x43), |
| /* D E F G */ |
| ONE(0x44), ONE(0x45), ONE(0x46), ONE(0x47), |
| /* H I J K */ |
| ONE(0x48), ONE(0x49), ONE(0x4a), ONE(0x4b), |
| /* L M N O */ |
| ONE(0x4c), ONE(0x4d), ONE(0x4e), ONE(0x4f), |
| /* P Q R S */ |
| ONE(0x50), ONE(0x51), ONE(0x52), ONE(0x53), |
| /* T U V W */ |
| ONE(0x54), ONE(0x55), ONE(0x56), ONE(0x57), |
| /* X Y Z Ä */ |
| ONE(0x58), ONE(0x59), ONE(0x5a), TWO(0xc3, 0x84), |
| /* Ö Ñ Ü § */ |
| TWO(0xc3, 0x96), TWO(0xc3, 0x91), TWO(0xc3, 0x9c), TWO(0xc2, 0xa7), |
| /* ¿ a b c */ |
| TWO(0xc2, 0xbf), ONE(0x61), ONE(0x62), ONE(0x63), |
| /* d e f g */ |
| ONE(0x64), ONE(0x65), ONE(0x66), ONE(0x67), |
| /* h i j k */ |
| ONE(0x68), ONE(0x69), ONE(0x6a), ONE(0x6b), |
| /* l m n o */ |
| ONE(0x6c), ONE(0x6d), ONE(0x6e), ONE(0x6f), |
| /* p q r s */ |
| ONE(0x70), ONE(0x71), ONE(0x72), ONE(0x73), |
| /* t u v w */ |
| ONE(0x74), ONE(0x75), ONE(0x76), ONE(0x77), |
| /* x y z ä */ |
| ONE(0x78), ONE(0x79), ONE(0x7a), TWO(0xc3, 0xa4), |
| /* ö ñ ü à */ |
| TWO(0xc3, 0xb6), TWO(0xc3, 0xb1), TWO(0xc3, 0xbc), TWO(0xc3, 0xa0) |
| }; |
| |
| static guint8 |
| gsm_def_char_to_utf8 (const guint8 gsm, |
| guint8 *out_utf8) |
| { |
| if (gsm >= GSM_DEF_ALPHABET_SIZE) |
| return 0; |
| memcpy (out_utf8, &gsm_def_utf8_alphabet[gsm].chars[0], gsm_def_utf8_alphabet[gsm].len); |
| return gsm_def_utf8_alphabet[gsm].len; |
| } |
| |
| #define EONE(a, g) { {a, 0x00, 0x00}, 1, g } |
| #define ETHR(a, b, c, g) { {a, b, c}, 3, g } |
| |
| /* Mapping from GSM extended alphabet to UTF-8 */ |
| static const GsmUtf8Mapping gsm_ext_utf8_alphabet[GSM_EXT_ALPHABET_SIZE] = { |
| /* form feed ^ { } */ |
| EONE(0x0c, 0x0a), EONE(0x5e, 0x14), EONE(0x7b, 0x28), EONE(0x7d, 0x29), |
| /* \ [ ~ ] */ |
| EONE(0x5c, 0x2f), EONE(0x5b, 0x3c), EONE(0x7e, 0x3d), EONE(0x5d, 0x3e), |
| /* | € */ |
| EONE(0x7c, 0x40), ETHR(0xe2, 0x82, 0xac, 0x65) |
| }; |
| |
| #define GSM_ESCAPE_CHAR 0x1b |
| |
| static guint8 |
| gsm_ext_char_to_utf8 (const guint8 gsm, |
| guint8 *out_utf8) |
| { |
| int i; |
| |
| for (i = 0; i < GSM_EXT_ALPHABET_SIZE; i++) { |
| if (gsm == gsm_ext_utf8_alphabet[i].gsm) { |
| memcpy (out_utf8, &gsm_ext_utf8_alphabet[i].chars[0], gsm_ext_utf8_alphabet[i].len); |
| return gsm_ext_utf8_alphabet[i].len; |
| } |
| } |
| return 0; |
| } |
| |
| static guint8 * |
| charset_gsm_unpack (const guint8 *gsm, |
| guint32 num_septets, |
| guint8 start_offset, /* in _bits_ */ |
| gsize *out_unpacked_len) |
| { |
| GByteArray *unpacked; |
| guint i; |
| |
| unpacked = g_byte_array_sized_new (num_septets + 1); |
| for (i = 0; i < num_septets; i++) { |
| guint8 bits_here, bits_in_next, octet, offset, c; |
| guint32 start_bit; |
| |
| start_bit = start_offset + (i * 7); /* Overall bit offset of char in buffer */ |
| offset = start_bit % 8; /* Offset to start of char in this byte */ |
| bits_here = offset ? (8 - offset) : 7; |
| bits_in_next = 7 - bits_here; |
| |
| /* Grab bits in the current byte */ |
| octet = gsm[start_bit / 8]; |
| c = (octet >> offset) & (0xFF >> (8 - bits_here)); |
| |
| /* Grab any bits that spilled over to next byte */ |
| if (bits_in_next) { |
| octet = gsm[(start_bit / 8) + 1]; |
| c |= (octet & (0xFF >> (8 - bits_in_next))) << bits_here; |
| } |
| g_byte_array_append (unpacked, &c, 1); |
| } |
| |
| *out_unpacked_len = unpacked->len; |
| return g_byte_array_free (unpacked, FALSE); |
| } |
| |
| gchar * |
| qmi_helpers_string_utf8_from_gsm7 (const guint8 *gsm_packed, |
| gsize gsm_packed_len) |
| { |
| GByteArray *utf8; |
| guint8 *gsm_unpacked; |
| gsize gsm_unpacked_len; |
| gsize i; |
| |
| /* unpack operation needs input length in SEPTETS */ |
| gsm_unpacked = charset_gsm_unpack (gsm_packed, gsm_packed_len * 8 / 7, 0, &gsm_unpacked_len); |
| |
| /* worst case initial length */ |
| utf8 = g_byte_array_sized_new (gsm_unpacked_len * 2 + 1); |
| |
| for (i = 0; i < gsm_unpacked_len; i++) { |
| guint8 uchars[4]; |
| guint8 ulen; |
| |
| /* |
| * 0x00 is NULL (when followed only by 0x00 up to the |
| * end of (fixed byte length) message, possibly also up to |
| * FORM FEED. But 0x00 is also the code for COMMERCIAL AT |
| * when some other character (CARRIAGE RETURN if nothing else) |
| * comes after the 0x00. |
| * http://unicode.org/Public/MAPPINGS/ETSI/GSM0338.TXT |
| * |
| * So, if we find a '@' (0x00) and all the next chars after that |
| * are also 0x00, we can consider the string finished already. |
| */ |
| if (gsm_unpacked[i] == 0x00) { |
| gsize j; |
| |
| for (j = i + 1; j < gsm_unpacked_len; j++) { |
| if (gsm_unpacked[j] != 0x00) |
| break; |
| } |
| if (j == gsm_unpacked_len) |
| break; |
| } |
| |
| if (gsm_unpacked[i] == GSM_ESCAPE_CHAR) { |
| /* Extended alphabet, decode next char */ |
| ulen = gsm_ext_char_to_utf8 (gsm_unpacked[i+1], uchars); |
| if (ulen) |
| i += 1; |
| } else { |
| /* Default alphabet */ |
| ulen = gsm_def_char_to_utf8 (gsm_unpacked[i], uchars); |
| } |
| |
| /* Invalid GSM-7, abort */ |
| if (!ulen) { |
| g_free (gsm_unpacked); |
| g_byte_array_unref (utf8); |
| return NULL; |
| } |
| |
| g_byte_array_append (utf8, &uchars[0], ulen); |
| } |
| |
| g_byte_array_append (utf8, (guint8 *) "\0", 1); /* NUL terminator */ |
| g_free (gsm_unpacked); |
| return (gchar *) g_byte_array_free (utf8, FALSE); |
| } |
| |
| /*****************************************************************************/ |
| |
| gchar * |
| qmi_helpers_string_utf8_from_ucs2le (const guint8 *ucs2le, |
| gsize ucs2le_len) |
| { |
| gsize ucs2he_nchars; |
| g_autofree guint16 *ucs2he = NULL; |
| |
| /* UCS2 data length given in bytes must be multiple of 2 */ |
| if (ucs2le_len % 2 != 0) |
| return NULL; |
| |
| /* Convert length from bytes to number of ucs2 characters */ |
| ucs2he_nchars = ucs2le_len / 2; |
| |
| /* We'll attempt to convert UCS2 to UTF-8, using GLib's support to convert |
| * from UTF-16 to UTF-8 (given that UCS2 is a subset of UTF-16). In order |
| * to do so, we need the input string in "host endian", so if we're in a |
| * BE system (where host endian is not little endian), we do an explicit |
| * conversion. |
| * |
| * We don't want to use g_convert() because that would mean requiring |
| * full iconv() support in the system. |
| * |
| * We're using an extra allocation always because we need to increase |
| * alignment (guint8 -> (guint16)gunichar2) |
| */ |
| ucs2he = g_new (guint16, ucs2he_nchars); |
| g_assert (sizeof (guint16) * ucs2he_nchars == ucs2le_len); |
| memcpy (ucs2he, ucs2le, ucs2le_len); |
| #if G_BYTE_ORDER == G_BIG_ENDIAN |
| { |
| guint i; |
| |
| for (i = 0; i < ucs2he_nchars; i++) |
| ucs2he[i] = GUINT16_FROM_LE (ucs2he[i]); |
| } |
| #endif |
| return g_utf16_to_utf8 ((const gunichar2 *)ucs2he, ucs2he_nchars, NULL, NULL, NULL); |
| } |
| /*****************************************************************************/ |
| |
| static gchar * |
| helpers_get_usb_driver (const gchar *device_basename) |
| { |
| static const gchar *subsystems[] = { "usbmisc", "usb" }; |
| guint i; |
| gchar *driver = NULL; |
| |
| for (i = 0; !driver && i < G_N_ELEMENTS (subsystems); i++) { |
| gchar *tmp; |
| gchar *path; |
| |
| /* driver sysfs can be built directly using subsystem and name; e.g. for subsystem |
| * usbmisc and name cdc-wdm0: |
| * $ realpath /sys/class/usbmisc/cdc-wdm0/device/driver |
| * /sys/bus/usb/drivers/qmi_wwan |
| */ |
| tmp = g_strdup_printf ("/sys/class/%s/%s/device/driver", subsystems[i], device_basename); |
| path = realpath (tmp, NULL); |
| g_free (tmp); |
| |
| if (!path) |
| continue; |
| |
| driver = g_path_get_basename (path); |
| g_free (path); |
| } |
| |
| return driver; |
| } |
| |
| QmiHelpersTransportType |
| qmi_helpers_get_transport_type (const gchar *path, |
| GError **error) |
| { |
| g_autofree gchar *device_basename = NULL; |
| g_autofree gchar *usb_driver = NULL; |
| g_autofree gchar *wwan_sysfs_path = NULL; |
| g_autofree gchar *wwan_type_sysfs_path = NULL; |
| g_autofree gchar *smdpkt_sysfs_path = NULL; |
| g_autofree gchar *rpmsg_sysfs_path = NULL; |
| gchar wwan_type[16] = { 0 }; |
| |
| device_basename = qmi_helpers_get_devname (path, error); |
| if (!device_basename) |
| return QMI_HELPERS_TRANSPORT_TYPE_UNKNOWN; |
| |
| /* Most likely case, we have a USB driver */ |
| usb_driver = helpers_get_usb_driver (device_basename); |
| if (usb_driver) { |
| if (!g_strcmp0 (usb_driver, "cdc_mbim")) |
| return QMI_HELPERS_TRANSPORT_TYPE_MBIM; |
| if (!g_strcmp0 (usb_driver, "qmi_wwan")) |
| return QMI_HELPERS_TRANSPORT_TYPE_QMUX; |
| g_set_error (error, QMI_CORE_ERROR, QMI_CORE_ERROR_FAILED, |
| "unexpected usb driver detected: %s", usb_driver); |
| return QMI_HELPERS_TRANSPORT_TYPE_UNKNOWN; |
| } |
| |
| /* WWAN devices have a type attribute or protocol in their name */ |
| wwan_sysfs_path = g_strdup_printf ("/sys/class/wwan/%s", device_basename); |
| if (g_file_test (wwan_sysfs_path, G_FILE_TEST_EXISTS)) { |
| wwan_type_sysfs_path = g_strdup_printf ("/sys/class/wwan/%s/type", device_basename); |
| if (qmi_helpers_read_sysfs_file (wwan_type_sysfs_path, wwan_type, |
| sizeof (wwan_type) - 1, NULL)) { |
| g_strstrip (wwan_type); |
| if (!g_strcmp0 (wwan_type, "QMI")) |
| return QMI_HELPERS_TRANSPORT_TYPE_QMUX; |
| if (!g_strcmp0 (wwan_type, "MBIM")) |
| return QMI_HELPERS_TRANSPORT_TYPE_MBIM; |
| } else { |
| /* "type" attribute exists only on Linux 5.14+, fall back to device name */ |
| if (g_strrstr (device_basename, "QMI")) |
| return QMI_HELPERS_TRANSPORT_TYPE_QMUX; |
| if (g_strrstr (device_basename, "MBIM")) |
| return QMI_HELPERS_TRANSPORT_TYPE_MBIM; |
| } |
| g_set_error (error, QMI_CORE_ERROR, QMI_CORE_ERROR_FAILED, |
| "unsupported wwan port"); |
| return QMI_HELPERS_TRANSPORT_TYPE_UNKNOWN; |
| } |
| |
| /* On Android systems we get access to the QMI control port through |
| * virtual smdcntl devices in the smdpkt subsystem. */ |
| smdpkt_sysfs_path = g_strdup_printf ("/sys/class/smdpkt/%s", device_basename); |
| if (g_file_test (smdpkt_sysfs_path, G_FILE_TEST_EXISTS)) |
| return QMI_HELPERS_TRANSPORT_TYPE_QMUX; |
| |
| /* On mainline kernels this control port is provided by rpmsg */ |
| rpmsg_sysfs_path = g_strdup_printf ("/sys/class/rpmsg/%s", device_basename); |
| if (g_file_test (rpmsg_sysfs_path, G_FILE_TEST_EXISTS)) |
| return QMI_HELPERS_TRANSPORT_TYPE_QMUX; |
| |
| /* Allow libqmi to connect directly to a unix domain socket with a specific file name */ |
| if (g_strrstr (device_basename, QMI_QMUX_SOCKET_FILE_NAME)) |
| return QMI_HELPERS_TRANSPORT_TYPE_QMUX; |
| |
| g_set_error (error, QMI_CORE_ERROR, QMI_CORE_ERROR_FAILED, |
| "unexpected port subsystem"); |
| return QMI_HELPERS_TRANSPORT_TYPE_UNKNOWN; |
| } |
| |
| gchar * |
| qmi_helpers_get_devpath (const gchar *cdc_wdm_path, |
| GError **error) |
| { |
| gchar *aux; |
| |
| if (!g_file_test (cdc_wdm_path, G_FILE_TEST_IS_SYMLINK)) |
| return g_strdup (cdc_wdm_path); |
| |
| aux = realpath (cdc_wdm_path, NULL); |
| if (!aux) { |
| int saved_errno = errno; |
| |
| g_set_error (error, QMI_CORE_ERROR, QMI_CORE_ERROR_FAILED, |
| "Couldn't get realpath: %s", g_strerror (saved_errno)); |
| return NULL; |
| } |
| |
| return aux; |
| } |
| |
| gchar * |
| qmi_helpers_get_devname (const gchar *cdc_wdm_path, |
| GError **error) |
| { |
| gchar *aux; |
| gchar *devname = NULL; |
| |
| aux = qmi_helpers_get_devpath (cdc_wdm_path, error); |
| if (aux) { |
| devname = g_path_get_basename (aux); |
| g_free (aux); |
| } |
| |
| return devname; |
| } |
| |
| gboolean |
| qmi_helpers_read_sysfs_file (const gchar *sysfs_path, |
| gchar *out_value, |
| guint max_read_size, |
| GError **error) |
| { |
| FILE *f; |
| gboolean status = FALSE; |
| |
| if (!(f = fopen (sysfs_path, "r"))) { |
| g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), |
| "Failed to open sysfs file '%s': %s", |
| sysfs_path, g_strerror (errno)); |
| goto out; |
| } |
| |
| if (fread (out_value, 1, max_read_size, f) == 0) { |
| g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), |
| "Failed to read from sysfs file '%s': %s", |
| sysfs_path, g_strerror (errno)); |
| goto out; |
| } |
| |
| status = TRUE; |
| |
| out: |
| if (f) |
| fclose (f); |
| return status; |
| } |
| |
| gboolean |
| qmi_helpers_write_sysfs_file (const gchar *sysfs_path, |
| const gchar *value, |
| GError **error) |
| { |
| gboolean status = FALSE; |
| FILE *f; |
| guint value_len; |
| |
| if (!(f = fopen (sysfs_path, "w"))) { |
| g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), |
| "Failed to open sysfs file '%s' for R/W: %s", |
| sysfs_path, g_strerror (errno)); |
| goto out; |
| } |
| |
| /* we require unbuffered so that we get errors on the fwrite() */ |
| setvbuf (f, NULL, _IONBF, 0); |
| |
| value_len = strlen (value); |
| if ((fwrite (value, 1, value_len, f) != value_len) || ferror (f)) { |
| g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), |
| "Failed to write to sysfs file '%s': %s", |
| sysfs_path, g_strerror (errno)); |
| goto out; |
| } |
| |
| status = TRUE; |
| |
| out: |
| if (f) |
| fclose (f); |
| return status; |
| } |
| |
| /******************************************************************************/ |
| |
| gboolean |
| qmi_helpers_list_links (GFile *sysfs_file, |
| GCancellable *cancellable, |
| GPtrArray *previous_links, |
| GPtrArray **out_links, |
| GError **error) |
| { |
| g_autofree gchar *sysfs_path = NULL; |
| g_autoptr(GFileEnumerator) direnum = NULL; |
| g_autoptr(GPtrArray) links = NULL; |
| |
| direnum = g_file_enumerate_children (sysfs_file, |
| G_FILE_ATTRIBUTE_STANDARD_NAME, |
| G_FILE_QUERY_INFO_NONE, |
| cancellable, |
| error); |
| if (!direnum) |
| return FALSE; |
| |
| sysfs_path = g_file_get_path (sysfs_file); |
| links = g_ptr_array_new_with_free_func (g_free); |
| |
| while (TRUE) { |
| GFileInfo *info; |
| g_autofree gchar *filename = NULL; |
| g_autofree gchar *link_path = NULL; |
| g_autofree gchar *real_path = NULL; |
| g_autofree gchar *basename = NULL; |
| |
| if (!g_file_enumerator_iterate (direnum, &info, NULL, cancellable, error)) |
| return FALSE; |
| if (!info) |
| break; |
| |
| filename = g_file_info_get_attribute_as_string (info, G_FILE_ATTRIBUTE_STANDARD_NAME); |
| if (!filename || !g_str_has_prefix (filename, "upper_")) |
| continue; |
| |
| link_path = g_strdup_printf ("%s/%s", sysfs_path, filename); |
| real_path = realpath (link_path, NULL); |
| if (!real_path) |
| continue; |
| |
| basename = g_path_get_basename (real_path); |
| |
| /* skip interface if it was already known */ |
| if (previous_links && g_ptr_array_find_with_equal_func (previous_links, basename, g_str_equal, NULL)) |
| continue; |
| |
| g_ptr_array_add (links, g_steal_pointer (&basename)); |
| } |
| |
| if (!links || !links->len) { |
| *out_links = NULL; |
| return TRUE; |
| } |
| |
| g_ptr_array_sort (links, (GCompareFunc) g_ascii_strcasecmp); |
| *out_links = g_steal_pointer (&links); |
| return TRUE; |
| } |
| |
| /******************************************************************************/ |
| |
| void |
| qmi_helpers_clear_string (gchar **value) |
| { |
| if (value && *value) |
| g_free (*value); |
| } |
| |
| /******************************************************************************/ |
| |
| #if !GLIB_CHECK_VERSION(2,54,0) |
| |
| gboolean |
| qmi_ptr_array_find_with_equal_func (GPtrArray *haystack, |
| gconstpointer needle, |
| GEqualFunc equal_func, |
| guint *index_) |
| { |
| guint i; |
| |
| g_return_val_if_fail (haystack != NULL, FALSE); |
| |
| if (equal_func == NULL) |
| equal_func = g_direct_equal; |
| |
| for (i = 0; i < haystack->len; i++) { |
| if (equal_func (g_ptr_array_index (haystack, i), needle)) { |
| if (index_ != NULL) |
| *index_ = i; |
| return TRUE; |
| } |
| } |
| |
| return FALSE; |
| } |
| |
| #endif |