/**
* @file mce-lib.c
* This file provides various helper functions
* for the Mode Control Entity
*
* Copyright © 2004-2011 Nokia Corporation and/or its subsidiary(-ies).
* Copyright (C) 2014-2019 Jolla Ltd.
*
* @author David Weinehall
* @author Tapio Rantala
* @author Santtu Lakkala
* @author Simo Piiroinen
*
* mce is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* mce 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 mce. If not, see .
*/
#include "mce-lib.h"
#include "mce.h"
#include "mce-log.h"
#include "mce-wakelock.h"
#include
#include
/**
* Set a bit
*
* @param bit The bit to set
* @param bitfield A pointer to an array with the bitfield
*/
void set_bit(guint bit, gulong **bitfield)
{
if ((bitfield == NULL) || (*bitfield == NULL))
goto EXIT;
(*bitfield)[bit / bitsize_of(**bitfield)] |= 1UL << (bit % bitsize_of(**bitfield));
EXIT:
return;
}
/**
* Clear a bit
*
* @param bit The bit to clear
* @param bitfield A pointer to an array with the bitfield
*/
void clear_bit(guint bit, gulong **bitfield)
{
if ((bitfield == NULL) || (*bitfield == NULL))
goto EXIT;
(*bitfield)[bit / bitsize_of(**bitfield)] &= ~(1UL << (bit % bitsize_of(**bitfield)));
EXIT:
return;
}
/**
* Test whether a bit is set
*
* @param bit The bit to test for
* @param bitfield An array with the bitfield
* @return TRUE if the bit is set,
* FALSE if the bit is unset
*/
gboolean test_bit(guint bit, const gulong *bitfield)
{
return ((1UL << (bit % bitsize_of(*bitfield))) &
(((gulong *)bitfield)[bit / bitsize_of(*bitfield)])) != 0;
}
/**
* Convert a string to a bitfield
*
* @param string The string with comma-separated numbers
* to turn into a bitfield
* @param[in,out] bitfield A bitfield to return the string in
* @param bitfieldsize The size of the bitfield
* @return TRUE on success,
* FALSE if the string could not be parsed numerically
* or if a number was out of range for the bitfield
*/
gboolean string_to_bitfield(const gchar *string,
gulong **bitfield, gsize bitfieldsize)
{
gchar *tmp = (gchar *)string;
gboolean status = FALSE;
int offset = 0;
guint num;
if ((string == NULL) || (bitfield == NULL) || (*bitfield == NULL))
goto EXIT;
while ((sscanf(tmp, "%u%n", &num, &offset) != 0) && (offset != 0)) {
/* Make sure we can represent this number */
if (num > (bitfieldsize * bitsize_of(**bitfield)))
goto EXIT;
set_bit(num, bitfield);
tmp += (offset + 1);
offset = 0;
}
status = TRUE;
EXIT:
return status;
}
/**
* Convert a bitfield to a string
*
* @param bitfield The bitfield to convert to a comma-separated string
* with the numbers of the set bits
* @param bitfieldsize The size of the bitfield
* @return A string with the newly allocated string on success,
* NULL on failure
*/
char *bitfield_to_string(const gulong *bitfield, gsize bitfieldsize)
{
gchar *tmp = NULL;
guint i, j;
/* Always pass 0; this way a NULL string represents failure,
* and a string with no bits set will represent an empty mask;
* we also simplify the g_strdup_printf() case quite a bit
*/
if ((tmp = strdup("0")) == NULL) {
mce_log(LL_CRIT,
"Failed to allocate memory "
"for tmp");
goto EXIT;
}
for (i = 0; i < bitfieldsize; i++) {
for (j = 0; bitfield[i] && j < bitsize_of(*bitfield); j++) {
if (bitfield[i] & (1UL << j)) {
gchar *tmp2;
tmp2 = g_strdup_printf("%s,%u",
tmp, (i * bitsize_of(*bitfield)) + j);
g_free(tmp);
if (tmp2 == NULL) {
mce_log(LL_CRIT,
"Failed to allocate memory "
"for tmp2");
goto EXIT;
}
tmp = tmp2;
}
}
}
EXIT:
return tmp;
}
/**
* Convert a value to a binary string (9-bit, since it's for Lysti)
* FIXME: convert to handle arbitrary length instead and make reentrant
* @note This function is non-reentrant; it returns a fixed sized,
* statically allocated, string that should not be freed
*
* @param bin The value to convert to a binary string
* @return A static string with a representation the value
*/
const gchar *bin_to_string(guint bin)
{
static gchar bin_string[] = "000000000";
gint i;
for (i = 0; i < 9; i++) {
bin_string[8 - i] = (bin & (1 << i)) ? '1' : '0';
}
return bin_string;
}
/**
* Translate an integer to its string representation;
* if no valid mapping exists, return the provided default string
* (if one has been provided)
*
* @param translation A mce_translation_t mapping
* @param number The number to map to a string
* @param default_string The default string to return if no match is found
* @return A string translation of the integer
*/
const gchar *mce_translate_int_to_string_with_default(const mce_translation_t translation[], gint number, const gchar *default_string)
{
const gchar *string;
gint i = 0;
/* This might seem awkward, but it's made to allow sparse
* number spaces
*/
do {
string = translation[i].string;
} while (translation[i].number != MCE_INVALID_TRANSLATION &&
translation[i++].number != number);
/* XXX: will this really behave correctly if there's only
* one (MCE_INVALID_TRANSLATION) element in the structure?
*/
if ((translation[i].number == MCE_INVALID_TRANSLATION) &&
(translation[i - 1].number != number) &&
(default_string != NULL))
string = default_string;
return string;
}
/**
* Translate an integer to its string representation
*
* @param translation A mce_translation_t mapping
* @param number The number to map to a string
* @return A string translation of the integer
*/
const gchar *mce_translate_int_to_string(const mce_translation_t translation[],
gint number)
{
return mce_translate_int_to_string_with_default(translation, number, NULL);
}
/**
* Translate a string to its integer representation
* if no valid mapping exists, return the provided default integer
* (if one has been provided)
*
* @param translation A mce_translation_t mapping
* @param string The string to map to an number
* @param default_integer The number to return if no match is found
* @return An integer translation value of the string
*/
gint mce_translate_string_to_int_with_default(const mce_translation_t translation[], const gchar *const string, gint default_integer)
{
gint number = MCE_INVALID_TRANSLATION;
gint i = 0;
while (translation[i].number != MCE_INVALID_TRANSLATION) {
/* If the string matches, set number and stop searching */
if (!strcmp(translation[i].string, string)) {
number = translation[i].number;
break;
}
i++;
}
if (translation[i].number == MCE_INVALID_TRANSLATION)
number = default_integer;
return number;
}
/**
* Translate a string to its integer representation
*
* @param translation A mce_translation_t mapping
* @param string The string to map to an number
* @return An integer translation value of the string
*/
gint mce_translate_string_to_int(const mce_translation_t translation[],
const gchar *const string)
{
return mce_translate_string_to_int_with_default(translation, string, MCE_INVALID_TRANSLATION);
}
/**
* Locate a delimited substring
*
* @param haystack The string to search in
* @param needle The string to search for
* @param delimiter The delimiter
* @return A pointer to the position of the substring on match,
* NULL if no match found
*/
gchar *strstr_delim(const gchar *const haystack, const char *needle,
const char *const delimiter)
{
char *match = NULL;
const char *tmp2;
size_t dlen;
if ((haystack == NULL) || (needle == NULL))
return NULL;
/* If there's no delimiter, we'll behave as strstr */
dlen = (delimiter == NULL) ? 0 : strlen(delimiter);
tmp2 = haystack;
while (tmp2 != NULL) {
const char *tmp;
ptrdiff_t len;
/* Find the first occurence of the delimiter */
if (dlen != 0)
tmp = strstr(tmp2, delimiter);
else
tmp = NULL;
/* If there's a delimiter, match up to it,
* if not, match the entire remaining string
*/
if (tmp != NULL)
len = tmp - tmp2;
else
len = strlen(tmp2);
/* If we find a match, we're done */
if ((match = g_strstr_len(tmp2, len, needle)) != NULL)
goto EXIT;
/* If there's no more delimiters, we're done */
if (len == 0)
goto EXIT;
/* Skip past the current token + the delimiter */
tmp2 += (len + dlen);
};
EXIT:
return match;
}
/**
* Compare a string with memory, with length checks
*
* @param mem The memory to compare with
* @param str The string to compare the memory to
* @param len The length of the memory area
* @return TRUE if the string matches the memory area,
* FALSE if the memory area does not match, or if the lengths differ
*/
gboolean strmemcmp(guint8 *mem, const gchar *str, gulong len)
{
gboolean result = FALSE;
if (strlen(str) != len)
goto EXIT;
if (memcmp(mem, str, len) != 0)
goto EXIT;
result = TRUE;
EXIT:
return result;
}
/** Get clock id specific time stamp in milliseconds
*
* @param id Clock id such as CLOCK_REALTIME or CLOCK_MONOTONIC
*
* @return 64-bit timestamp
*/
static int64_t mce_lib_get_tick(clockid_t id)
{
int64_t res = 0;
struct timespec ts;
if( clock_gettime(id, &ts) == 0 ) {
res = ts.tv_sec;
res *= 1000;
res += ts.tv_nsec / 1000000;
}
return res;
}
/** Get CLOCK_BOOTTIME time stamp in milliseconds
*
* @return 64-bit timestamp
*/
int64_t mce_lib_get_boot_tick(void)
{
return mce_lib_get_tick(CLOCK_BOOTTIME);
}
/** Get CLOCK_MONOTONIC time stamp in milliseconds
*
* @return 64-bit timestamp
*/
int64_t mce_lib_get_mono_tick(void)
{
return mce_lib_get_tick(CLOCK_MONOTONIC);
}
/** Get CLOCK_REALTIME time stamp in milliseconds
*
* @return 64-bit timestamp
*/
int64_t mce_lib_get_real_tick(void)
{
return mce_lib_get_tick(CLOCK_REALTIME);
}
/** Bookkeeping data for wakelocked glib timers */
typedef struct timeout_gate_t
{
gchar *tg_lock;
GSourceFunc tg_func;
gpointer tg_data;
GDestroyNotify tg_free;
} timeout_gate_t;
/** Delete wakelocked glib timer gate
*/
static void
timeout_gate_delete_cb(gpointer aptr)
{
timeout_gate_t *self = aptr;
if( self->tg_free )
self->tg_free(self->tg_data);
mce_wakelock_release(self->tg_lock);
g_free(self->tg_lock);
g_slice_free1(sizeof *self, self);
}
/** Create wakelocked glib timer gate
*/
static timeout_gate_t *
timeout_gate_create(GSourceFunc func, gpointer aptr, GDestroyNotify notify)
{
static unsigned uniq = 0;
timeout_gate_t *self = g_slice_alloc0(sizeof *self);
self->tg_lock = g_strdup_printf("mce_timeout_%u", ++uniq);
self->tg_func = func;
self->tg_data = aptr;
self->tg_free = notify;
mce_wakelock_obtain(self->tg_lock, -1);
return self;
}
/** Handle wakelocked glib timeout
*/
static gboolean
timeout_gate_cb(gpointer aptr)
{
timeout_gate_t *self = aptr;
return self->tg_func(self->tg_data);
}
/** Wakelocking alternative for g_timeout_add_full()
*
* Obtains multiplexed wakelock that is released when the timeout
* source is released either implicitly by returning FALSE from callback
* function, or explicitly by calling g_source_remove().
*
* @param priority the priority of the timeout source
* @param interval the time between calls to the function, in milliseconds
* @param function function to call
* @param data data to pass to function
* @param notify function to call when the timeout is removed, or NULL
*
* @return glib source identifier
*/
guint
mce_wakelocked_timeout_add_full(gint priority, guint interval,
GSourceFunc function,
gpointer data, GDestroyNotify notify)
{
return g_timeout_add_full(priority, interval, timeout_gate_cb,
timeout_gate_create(function, data, notify),
timeout_gate_delete_cb);
}
/** Wakelocking alternative for g_timeout_add()
*
* See g_timeout_add_full() for details.
*
* @param interval the time between calls to the function, in milliseconds
* @param function function to call
* @param data data to pass to function
*
* @return glib source identifier
*/
guint
mce_wakelocked_timeout_add(guint interval, GSourceFunc function, gpointer data)
{
return mce_wakelocked_timeout_add_full(G_PRIORITY_DEFAULT, interval,
function, data, NULL);
}
/** Wakelocking alternative for g_idle_add()
*
* See g_timeout_add_full() for details.
*
* @param function function to call
* @param data data to pass to function
*
* @return glib source identifier
*/
guint
mce_wakelocked_idle_add(GSourceFunc function, gpointer data)
{
/* NB This not exactly like g_idle_add() */
return mce_wakelocked_timeout_add_full(G_PRIORITY_DEFAULT, 0,
function, data, NULL);
}