mce-io.c 54.1 KB
Newer Older
1 2 3 4
/**
 * @file mce-io.c
 * Generic I/O functionality for the Mode Control Entity
 * <p>
Santtu Lakkala's avatar
Santtu Lakkala committed
5
 * Copyright © 2006-2011 Nokia Corporation and/or its subsidiary(-ies).
6
 * Copyright (C) 2012-2019 Jolla Ltd.
7 8
 * <p>
 * @author David Weinehall <david.weinehall@nokia.com>
9 10
 * @author Santtu Lakkala <ext-santtu.1.lakkala@nokia.com>
 * @author Jukka Turunen <ext-jukka.t.turunen@nokia.com>
11
 * @author Simo Piiroinen <simo.piiroinen@jollamobile.com>
12 13 14 15 16 17 18 19 20 21 22 23 24
 *
 * 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 <http://www.gnu.org/licenses/>.
 */
25

26 27
#include "mce-io.h"

28
#include "mce.h"
29
#include "mce-log.h"
30
#include "mce-lib.h"
31
#include "mce-wakelock.h"
32

33
#ifdef ENABLE_WAKELOCKS
34
# include "libwakelock.h"
35 36
#endif

37 38
#include <sys/timerfd.h>

39 40 41 42 43 44 45 46
#include <unistd.h>
#include <inttypes.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>

#include <glib/gstdio.h>

47 48 49 50
#ifndef  TFD_TIMER_CANCELON_SET
# define TFD_TIMER_CANCELON_SET (1<<1)
#endif

51 52 53 54 55 56 57 58 59 60
/* ========================================================================= *
 * CONSTANTS
 * ========================================================================= */

/** Suffix used for temporary files */
#define TMP_SUFFIX				".tmp"

/* ========================================================================= *
 * TYPES
 * ========================================================================= */
61 62 63

/** I/O monitor type */
typedef enum {
64 65 66
	IOMON_UNSET  = -1,		/**< I/O monitor type unset */
	IOMON_STRING =  0,		/**< String I/O monitor */
	IOMON_CHUNK  =  1,		/**< Chunk I/O monitor */
67 68 69
} iomon_type;

/** I/O monitor structure */
70
struct mce_io_mon_t {
71 72 73 74 75 76 77 78 79 80
	gchar          *path;		/**< Monitored file */
	iomon_type      type;		/**< Monitor type */
	gulong          chunk_size;	/**< Read-chunk size */

	gboolean        seekable;	/**< is the I/O channel seekable */
	gboolean        suspended;	/**< Is the I/O monitor suspended? */

	GIOChannel     *iochan;		/**< I/O channel */
	guint           iowatch_id;	/**< GSource ID for input */

81
	mce_io_mon_notify_cb nofity_cb;	/**< Input handling callback */
82
	mce_io_mon_delete_cb delete_cb;	/**< Iomon delete callback */
83 84 85 86

	error_policy_t  error_policy;	/**< Error policy */
	gboolean        rewind_policy;	/**< Rewind policy */

87 88
	void              *user_data;   /**< Attached user data block */
	mce_io_mon_free_cb user_free_cb;/**< Callback for freeing user_data */
89
};
90

91 92 93 94 95 96
/* ========================================================================= *
 * STATE_DATA
 * ========================================================================= */

/** List of all file monitors */
static GSList *file_monitors = NULL;
97

98 99 100 101
/* ========================================================================= *
 * PROTOTYPES
 * ========================================================================= */

102 103 104 105
// SUSPEND_DETECTION

static void    io_detect_resume  (void);

106 107 108 109 110
static gboolean     mce_io_resume_timer_cb    (GIOChannel *chn, GIOCondition cnd, gpointer aptr);
static bool         mce_io_prime_resume_timer (void);
void                mce_io_init_resume_timer  (void);
void                mce_io_quit_resume_timer  (void);

111 112
// GLIB_IO_HELPERS

113 114
const char *mce_io_condition_repr (GIOCondition cond);
const char *mce_io_status_name    (GIOStatus io_status);
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142

// IO_MONITOR

static mce_io_mon_t *mce_io_mon_create                  (const char *path, mce_io_mon_delete_cb delete_cb);
static void          mce_io_mon_delete                  (mce_io_mon_t *self);
static void          mce_io_mon_probe_seekable          (mce_io_mon_t *self);

static gboolean      mce_io_mon_read_chunks             (GIOChannel *source, GIOCondition condition, gpointer data);
static gboolean      mce_io_mon_read_string             (GIOChannel *source, GIOCondition condition, gpointer data);
static gboolean      mce_io_mon_input_cb                (GIOChannel *source, GIOCondition condition, gpointer data);

static mce_io_mon_t *mce_io_mon_register                (gint fd, const gchar *path, error_policy_t error_policy, gboolean rewind_policy, mce_io_mon_notify_cb callback, mce_io_mon_delete_cb delete_cb);

mce_io_mon_t        *mce_io_mon_register_string         (const gint fd, const gchar *const file, error_policy_t error_policy, gboolean rewind_policy, mce_io_mon_notify_cb callback, mce_io_mon_delete_cb delete_cb);
mce_io_mon_t        *mce_io_mon_register_chunk          (const gint fd, const gchar *const file, error_policy_t error_policy, gboolean rewind_policy, mce_io_mon_notify_cb callback, mce_io_mon_delete_cb delete_cb, gulong chunk_size);

void                 mce_io_mon_unregister              (mce_io_mon_t *iomon);
void                 mce_io_mon_unregister_list         (GSList *list);
void                 mce_io_mon_unregister_at_path      (const char *path);

void                 mce_io_mon_suspend                 (mce_io_mon_t *iomon);
void                 mce_io_mon_resume                  (mce_io_mon_t *iomon);

const gchar         *mce_io_mon_get_path                (const mce_io_mon_t *iomon);
int                  mce_io_mon_get_fd                  (const mce_io_mon_t *iomon);

// MISC_UTILS

143
guint           mce_io_add_watch                        (int fd, bool close_on_unref, GIOCondition cnd, GIOFunc io_cb, gpointer aptr);
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
gboolean        mce_close_file                          (const gchar *const file, FILE **fp);
gboolean        mce_read_chunk_from_file                (const gchar *const file, void **data, gssize *len, int flags);
gboolean        mce_read_string_from_file               (const gchar *const file, gchar **string);
gboolean        mce_read_number_string_from_file        (const gchar *const file, gulong *number, FILE **fp, gboolean rewind_file, gboolean close_on_exit);
gboolean        mce_write_string_to_file                (const gchar *const file, const gchar *const string);
void            mce_close_output                        (output_state_t *output);
gboolean        mce_write_number_string_to_file         (output_state_t *output, const gulong number);
gboolean        mce_write_number_string_to_file_atomic  (const gchar *const file, const gulong number);
gboolean        mce_are_settings_locked                 (void);
gboolean        mce_unlock_settings                     (void);
static gboolean mce_io_read_all                         (int fd, void *buff, size_t size, size_t *pdone);
static gboolean mce_io_write_all                        (int fd, const void *buff, size_t size, size_t *pdone);
void           *mce_io_load_file                        (const char *path, size_t *psize);
void           *mce_io_load_file_until_eof              (const char *path, size_t *psize);
gboolean        mce_io_save_file                        (const char *path, const void *data, size_t size, mode_t mode);
gboolean        mce_io_save_to_existing_file            (const char *path, const void *data, size_t size);
gboolean        mce_io_save_file_atomic                 (const char *path, const void *data, size_t size, mode_t mode, gboolean keep_backup);
gboolean        mce_io_update_file_atomic               (const char *path, const void *data, size_t size, mode_t mode, gboolean keep_backup);
162 163

/* ========================================================================= *
164
 * SUSPEND_DETECTION
165 166 167 168 169 170 171 172
 * ========================================================================= */

/** Detect suspend/resume cycle from CLOCK_MONOTONIC vs CLOCK_BOOTTIME
 */
static void io_detect_resume(void)
{
	static int64_t prev = 0;

173 174
	int64_t boot = mce_lib_get_boot_tick();
	int64_t mono = mce_lib_get_mono_tick();
175 176 177 178 179 180
	int64_t diff = boot - mono;

	int64_t skip = diff - prev;

	// small jitter can be due to scheduling too
	if( skip < 100 )
181 182
		goto EXIT;

183
	prev = diff;
184

185 186 187 188 189 190
	// no logging from the 1st time skip
	if( prev == skip )
		goto EXIT;

	mce_log(LL_DEVEL, "time skip: assume %"PRId64".%03"PRId64"s suspend",
		skip / 1000, skip % 1000);
191

192
	// notify in case some timers need re-evaluating
193
	datapipe_exec_full(&resume_detected_event_pipe, &prev);
194

195
EXIT:
196
	return;
197 198
}

199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
/** Timerfd we use for detecting resume
 */
static int mce_io_resume_timer_fd = -1;

/** Glib io watch for mce_io_resume_timer_fd
 */
static guint mce_io_resume_timer_id = 0;

/** Virtual wakelock used for protecting resume timer wakeups
 */
static const char mce_io_resume_timer_wakelock[] = "mce_io_resume_timer";

/** Glib io callback for mce_io_resume_timer_fd
 */
static gboolean
mce_io_resume_timer_cb(GIOChannel  *chn, GIOCondition cnd, gpointer aptr)
{
	(void)chn;
	(void)aptr;

	/* Deny suspending while handling timer wakeup */
	mce_wakelock_obtain(mce_io_resume_timer_wakelock, -1);

	gboolean result = G_SOURCE_REMOVE;

	if( mce_io_resume_timer_fd == -1 || mce_io_resume_timer_id == 0 ) {
		mce_log(LL_WARN, "stray resume timer wakeup");
		goto EXIT;
	}

	if( cnd & ~G_IO_IN ) {
		mce_log(LL_CRIT, "unexpected resume timer wakeup: %s",
231
			mce_io_condition_repr(cnd));
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358
		goto EXIT;
	}

	/* Read trigger count. Expected result is ECANCELED */
	uint64_t cnt = 0;
	int res = read(mce_io_resume_timer_fd, &cnt, sizeof cnt);

	if( res == -1 ) {
		if( errno == EAGAIN || errno == EINTR ) {
			/* Silentry ignore temporary errors */
		}
		else if( errno == ECANCELED ) {
			/* The expected error */
			mce_log(LL_DEBUG, "resume timer wakeup");
			io_detect_resume();
			if( !mce_io_prime_resume_timer() )
				goto EXIT;
		}
		else {
			/* Some unexpected error */
			mce_log(LL_CRIT, "can't read resume timer: %m");
			goto EXIT;
		}
	}
	else {
		/* Silentry ignore timer actually triggering */
		if( !mce_io_prime_resume_timer() )
			goto EXIT;
	}

	result = G_SOURCE_CONTINUE;

EXIT:
	if( result != G_SOURCE_CONTINUE && mce_io_resume_timer_id ) {
		mce_log(LL_CRIT, "disabling resume timer");
		mce_io_resume_timer_id = 0;
		mce_io_quit_resume_timer();
	}

	mce_wakelock_release(mce_io_resume_timer_wakelock);

	return result;
}

/** Program resume timer fd
 */
static bool
mce_io_prime_resume_timer(void)
{
	bool ack = false;

	if( mce_io_resume_timer_fd == -1 )
		goto EXIT;

	/* Use dummy interval as we are basically only interested in
	 * getting ECANCELED when CLOCK_REALTIME is adjusted due to
	 * device resuming from suspend.
	 */
	struct itimerspec its = { };
	int flags = TFD_TIMER_ABSTIME | TFD_TIMER_CANCELON_SET;
	if( timerfd_settime(mce_io_resume_timer_fd, flags, &its, 0) == -1 ) {
		mce_log(LL_WARN, "can't program resume timer: %m");
		goto EXIT;
	}

	ack = true;

EXIT:
	return ack;
}

/** Create resume timer fd
 */
void
mce_io_init_resume_timer(void)
{
	bool ack = false;

	/* Create realtime clock timerfd */
	int clockid = CLOCK_REALTIME;
	int flags = O_NONBLOCK | O_CLOEXEC;
	mce_io_resume_timer_fd = timerfd_create(clockid, flags);

	if( mce_io_resume_timer_fd == -1 ) {
		mce_log(LL_WARN, "timerfd_create: %m");
		goto EXIT;
	}

	/* Program dummy wakeup time */
	if( !mce_io_prime_resume_timer() )
		goto EXIT;

	/* Add io watch for the timerfd */
	mce_io_resume_timer_id = mce_io_add_watch(mce_io_resume_timer_fd,
						  false,
						  G_IO_IN,
						  mce_io_resume_timer_cb,
						  0);

	if( !mce_io_resume_timer_id )
		goto EXIT;

	ack = true;

EXIT:
	if( !ack ) {
		mce_io_quit_resume_timer();
		mce_log(LL_WARN, "detect resume via timerfd not in use");
	}
}

/** Delete resume timer fd
 */
void
mce_io_quit_resume_timer(void)
{
	if( mce_io_resume_timer_id ) {
		g_source_remove(mce_io_resume_timer_id),
			mce_io_resume_timer_id = 0;
	}

	if( mce_io_resume_timer_fd != -1 ) {
		close(mce_io_resume_timer_fd),
			mce_io_resume_timer_fd = -1;
	}
}

359
/* ========================================================================= *
360
 * GLIB_IO_HELPERS
361 362
 * ========================================================================= */

363
/**
364
 * Get glib io condition as human readable string
365
 *
366 367 368
 * @param cond Bitmap of glib io conditions
 *
 * @return Names of bits set, separated with " | "
369
 */
370
const char *mce_io_condition_repr(GIOCondition cond)
371
{
372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397
	static const struct
	{
		GIOCondition bit;
		const char  *name;
	} lut[] =
	{
		{ .bit = G_IO_IN,   .name = "IN"   },
		{ .bit = G_IO_OUT,  .name = "OUT"  },
		{ .bit = G_IO_PRI,  .name = "PRI"  },
		{ .bit = G_IO_ERR,  .name = "ERR"  },
		{ .bit = G_IO_HUP,  .name = "HUP"  },
		{ .bit = G_IO_NVAL, .name = "NVAL" },
		// sentinel
		{ .bit = 0,         .name = 0      }
	};

	static char buf[64];
	char *end = buf + sizeof buf - 1;
	char *pos = buf;

	auto void add(const char *s);

	auto void add(const char *s)
	{
		while( pos < end && *s ) *pos++ = *s++;
	}
398

399 400 401 402 403 404 405 406 407 408 409
	for( size_t i = 0; lut[i].bit; ++i ) {
		if( cond & lut[i].bit ) {
			cond ^= lut[i].bit;
			if( pos > buf ) add("|");
			add(lut[i].name);
		}
	}
	*pos = 0;
	if( cond ) {
		if( pos > buf ) add("|");
		snprintf(pos, end - pos, "0x%x", cond);
410 411
	}

412 413 414 415 416 417 418 419 420 421
	return buf;
}

/**
 * Get glib io status as human readable string
 *
 * @param io_status as returned from g_io_channel_read_chars()
 *
 * @return Name of the status enum, without the common prefix
 */
422
const char *mce_io_status_name(GIOStatus io_status)
423 424 425 426 427 428 429 430
{
	const char *status_name = "UNKNOWN";
	switch (io_status) {
	case G_IO_STATUS_NORMAL: status_name = "NORMAL"; break;
	case G_IO_STATUS_ERROR:  status_name = "ERROR";  break;
	case G_IO_STATUS_EOF:    status_name = "EOF";    break;
	case G_IO_STATUS_AGAIN:  status_name = "AGAIN";  break;
	default: break; // ... just to keep static analysis happy
431
	}
432 433
	return status_name;
}
434

435
/* ========================================================================= *
436
 * IO_MONITOR
437 438
 * ========================================================================= */

439
/** Create I/O monitor object
440
 *
441 442 443 444 445 446 447 448 449 450
 * Allocates I/O monitor object and does all initialization that
 * does not need monitoring type information.
 *
 * Specifically the io watch is not activated from within this
 * function, it needs to be done separately.
 *
 * @param path       File path
 * @param delete_cb  I/O monitor object delete notification callback
 *
 * @return I/O monitor object
451
 */
452
static mce_io_mon_t *mce_io_mon_create(const char *path, mce_io_mon_delete_cb delete_cb)
453
{
454
	mce_io_mon_t *self = 0;
455

456 457 458
	if( !path ) {
		mce_log(LL_ERR, "path == NULL!");
		goto EXIT;
459
	}
460 461 462 463

	if( !delete_cb ) {
		mce_log(LL_ERR, "delete_cb == NULL!");
		goto EXIT;
464 465
	}

466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488
	if( !(self = g_slice_new(mce_io_mon_t)) )
		goto EXIT;

	memset(self, 0, sizeof *self);

	/* Fill in sane default values */

	self->path          = g_strdup(path);
	self->type          = IOMON_UNSET;
	self->chunk_size    = 0;

	self->seekable      = FALSE;
	self->suspended     = TRUE;

	self->iochan        = 0;
	self->iowatch_id    = 0;

	self->nofity_cb     = 0;
	self->delete_cb     = delete_cb;

	self->error_policy  = MCE_IO_ERROR_POLICY_WARN;
	self->rewind_policy = FALSE;

489 490 491
	self->user_data     = 0;
	self->user_free_cb  = 0;

492
	mce_log(LL_DEBUG, "adding monitor for: %s", self->path);
493 494 495

EXIT:
	return self;
496
}
497

498 499 500 501 502 503 504 505 506 507
/** Delete I/O monitor object
 *
 * Calls delete notification callback to allow upper level
 * logic to perform cleanup.
 *
 * Then removes io watch, closes io channel and releases
 * all dynamic resources associated with the I/O monitor object.
 *
 * @param self I/O monitor object
 */
508
static void mce_io_mon_delete(mce_io_mon_t *self)
509 510
{
	if( !self )
511 512
		goto EXIT;

513 514 515 516 517
	mce_log(LL_NOTICE, "removing monitor for: %s", self->path);

	/* Call the about to delete callback */
	if( self->delete_cb ) {
		self->delete_cb(self);
518 519
	}

520 521 522 523
	/* Free attached user data */
	if( self->user_data && self->user_free_cb )
		self->user_free_cb(self->user_data);

524 525 526 527 528 529 530 531
	/* Unlink from monitor list */
	if( !g_slist_find(file_monitors, self) ) {
		mce_log(LL_WARN, "Trying to unregister non-registered"
			" file monitor");
	}
	else {
		file_monitors = g_slist_remove(file_monitors, self);
	}
532

533
	/* Remove I/O watch */
534
	mce_io_mon_suspend(self);
535

536 537 538 539 540
	/* Close the I/O channel */
	if( self->iochan ) {
		GError    *error    = NULL;
		GIOStatus  iostatus = g_io_channel_shutdown(self->iochan,
							    TRUE, &error);
541

542 543
		if( iostatus != G_IO_STATUS_NORMAL ) {
			loglevel_t loglevel = LL_ERR;
544

545 546 547 548 549 550 551
			/* If we get ENODEV, only log a debug message,
			 * since this happens for hotpluggable
			 * /dev/input files
			 */
			if( (error->code == G_IO_CHANNEL_ERROR_FAILED) &&
			    (errno == ENODEV) )
				loglevel = LL_DEBUG;
552

553 554 555 556
			mce_log(loglevel, "Cannot close `%s'; %s",
				self->path, error->message);
		}
		g_clear_error(&error);
557

558 559
		g_io_channel_unref(self->iochan);
		self->iochan = 0;
560 561
	}

562 563
	/* Forget file path */
	g_free(self->path), self->path = 0;
564

565 566 567
	/* Reset to something that is likely to generate segfaults
	 * if it ends up used after freeing ... */
	memset(self, 0xff, sizeof *self);
568

569
	g_slice_free(mce_io_mon_t, self);
570
EXIT:
571
	return;
572 573
}

574 575
/**
 * Check if the monitored io channel is truly seekable
576
 *
577 578
 * Glib seems to be making guesses based on file type and
 * gets it massively wrong for the files MCE needs to read.
579
 */
580 581

static void mce_io_mon_probe_seekable(mce_io_mon_t *self)
582
{
583
	gboolean glib = FALSE, kernel = FALSE;
584

585 586 587
	/* glib assumes ... */
	if (g_io_channel_get_flags(self->iochan) & G_IO_FLAG_IS_SEEKABLE) {
		glib = TRUE;
588
	}
589 590 591 592 593 594 595 596
	/* ... kernel knows */
	if (lseek64(g_io_channel_unix_get_fd(self->iochan), 0, SEEK_CUR) != -1) {
		kernel = TRUE;
	}
	/* report the difference */
	if (kernel != glib) {
		mce_log(LL_DEBUG, "%s: is %sseekable, while glib thinks it is %sseekable",
			self->path, kernel ? "" : "NOT ", glib ? "" : "NOT ");
597 598
	}

599
	self->seekable = kernel;
600 601
}

602 603
/** Process input for chunked io monitor
 *
604
 * For use from mce_io_mon_input_cb() only.
605 606 607 608
 *
 * @param source    The source of the activity
 * @param condition The I/O condition
 * @param data      The iomon structure
609 610 611
 *
 * @return TRUE on success, FALSE on failure
 */
612 613 614
static gboolean mce_io_mon_read_chunks(GIOChannel *source,
				       GIOCondition condition,
				       gpointer data)
615
{
616
	gboolean      status      = FALSE;
617

618
	mce_io_mon_t  *iomon      = data;
619 620 621 622 623 624 625
	gchar        *buffer      = NULL;
	gsize         bytes_want  = 4096;
	gsize         bytes_have  = 0;
	gsize         chunks_have = 0;
	gsize         chunks_done = 0;
	GError       *error       = NULL;
	GIOStatus     io_status   = G_IO_STATUS_NORMAL;
626

627 628 629 630 631 632
#ifdef ENABLE_WAKELOCKS
	/* Since the locks on kernel side are released once all
	 * events are read, we must obtain the userspace lock
	 * before reading the available data */
	wakelock_lock("mce_input_handler", -1);
#endif
633

634 635
	/* We get input from evdev nodes at resume, handle that 1st */
	io_detect_resume();
636

637 638 639
	// paranoia mode:  upper levels should take care of these
	if( !(condition & G_IO_IN) )
		goto EXIT;
640

641 642
	if( !iomon )
		goto EXIT;
643

644 645 646 647 648 649 650 651
	/* Seek to the beginning of the file before reading if needed */
	if( iomon->rewind_policy ) {
		g_io_channel_seek_position(source, 0, G_SEEK_SET, &error);

		if( error ) {
			mce_log(LL_ERR,	"%s: seek error: %s",
				iomon->path, error->message);
			g_clear_error(&error);
652 653 654
		}
	}

655 656 657 658 659 660
	/* Adjust read size to multiples of small sized chunks,
	 * or size of one larger chunk */
	if( iomon->chunk_size < bytes_want )
		bytes_want -= bytes_want % iomon->chunk_size;
	else
		bytes_want = iomon->chunk_size;
661

662 663
	/* Allocate read buffer */
	buffer = g_malloc(bytes_want);
Santtu Lakkala's avatar
Santtu Lakkala committed
664

665 666
	io_status = g_io_channel_read_chars(source, buffer,
					    bytes_want, &bytes_have, &error);
Santtu Lakkala's avatar
Santtu Lakkala committed
667

668 669 670 671
	/* If the read was interrupted, ignore */
	if( io_status == G_IO_STATUS_AGAIN ) {
		status = TRUE;
		goto EXIT;
Santtu Lakkala's avatar
Santtu Lakkala committed
672
	}
673

674 675 676 677 678 679
	if( error ) {
		mce_log(LL_ERR, "Error when reading from %s: %s",
			iomon->path, error->message);
		g_clear_error(&error);
		goto EXIT;
	}
680

681 682 683
	if( bytes_have % iomon->chunk_size ) {
		mce_log(LL_WARN, "Incomplete chunks read from: %s",
			iomon->path);
684 685
	}

686 687 688 689
	/* Process the data, and optionally ignore some of it */
	chunks_have = bytes_have / iomon->chunk_size;
	if( !chunks_have ) {
		mce_log(LL_ERR, "Empty read from %s", iomon->path);
690
	}
691 692 693 694
	else {
		gchar *chunk = buffer;
		for( ; chunks_done < chunks_have ; chunk += iomon->chunk_size ) {
			++chunks_done;
695

696
			if( !iomon->nofity_cb(iomon, chunk, iomon->chunk_size) ) {
697 698
				continue;
			}
699

700 701 702
			/* Ignore rest of the data already read */
			if( !iomon->seekable )
				break;
703

704 705 706 707 708 709 710 711 712 713 714
			/* Try to seek to end of the file */
			g_io_channel_seek_position(iomon->iochan, 0,
						   G_SEEK_END, &error);

			if( error ) {
				mce_log(LL_ERR, "Error when reading from %s: %s",
					iomon->path, error->message);
				g_clear_error(&error);
			}
			break;
		}
715 716
	}

717
	mce_log(LL_INFO, "%s: status=%s, data=%d/%d=%d+%d, skipped=%d",
718
		iomon->path, mce_io_status_name(io_status),
719 720 721 722
		bytes_have, (int)iomon->chunk_size, chunks_have,
		bytes_have % (int)iomon->chunk_size, chunks_have - chunks_done);

	status = TRUE;
723 724

EXIT:
725 726 727 728 729 730 731 732
	g_clear_error(&error);
	g_free(buffer);

#ifdef ENABLE_WAKELOCKS
	/* Release the lock after we're done with processing it */
	wakelock_unlock("mce_input_handler");
#endif

733 734 735
	return status;
}

736 737
/** Process input for string io monitor
 *
738
 * For use from mce_io_mon_input_cb() only.
739 740 741 742
 *
 * @param source    The source of the activity
 * @param condition The I/O condition
 * @param data      The iomon structure
743 744 745
 *
 * @return TRUE on success, FALSE on failure
 */
746 747 748
static gboolean mce_io_mon_read_string(GIOChannel *source,
				       GIOCondition condition,
				       gpointer data)
749
{
750
	gboolean      status     = FALSE;
751

752
	mce_io_mon_t *iomon      = data;
753 754 755 756 757 758
	gchar        *str        = NULL;
	gsize         bytes_read = 0;
	GError       *error      = NULL;

	// paranoia mode:  upper levels should take care of these
	if( !(condition & G_IO_IN) )
759 760
		goto EXIT;

761
	if( !iomon )
762 763
		goto EXIT;

764 765 766
	/* Seek to the beginning of the file before reading if needed */
	if( iomon->rewind_policy ) {
		g_io_channel_seek_position(source, 0, G_SEEK_SET, &error);
Santtu Lakkala's avatar
Santtu Lakkala committed
767

768 769 770 771 772
		if( error ) {
			mce_log(LL_ERR,	"%s: seek error: %s",
				iomon->path, error->message);
			g_clear_error(&error);
		}
773 774
	}

775
	g_io_channel_read_line(source, &str, &bytes_read, NULL, &error);
776

777 778 779 780
	if( error ) {
		mce_log(LL_ERR, "Error when reading from %s: %s",
			iomon->path, error->message);
		goto EXIT;
781 782
	}

783 784 785
	if( !bytes_read || !str || !*str )
		mce_log(LL_ERR, "Empty read from %s",iomon->path);
	else
786
		iomon->nofity_cb(iomon, str, bytes_read);
787

788
	status = TRUE;
789

Santtu Lakkala's avatar
Santtu Lakkala committed
790
EXIT:
791 792 793
	g_free(str);
	g_clear_error(&error);

794 795 796
	return status;
}

797
/** Callback for I/O watch
798
 *
799 800
 * Handles error conditions first; then does input monitor
 * type specific input processing.
801
 *
802 803 804
 * The I/O monitor will be disabled and deleted on errors.
 * Additionally the whole process can be terminated if
 * error policy requires it.
805
 *
806 807 808
 * @param source    Unused
 * @param condition The GIOCondition for the error
 * @param data      The iomon structure
809
 *
810 811
 * @return TRUE to keep iomon active, FALSE to disable it;
 *         may also exit depending on error policy for iomon
812
 */
813 814 815
static gboolean mce_io_mon_input_cb(GIOChannel *source,
				    GIOCondition condition,
				    gpointer data)
816
{
817
	(void)source; // unused
818

819
	mce_io_mon_t *iomon      = data;
820 821 822
	gboolean      keep_going = TRUE;
	gboolean      terminate  = FALSE;
	loglevel_t    loglevel   = LL_DEBUG;
823

824 825 826 827 828
	// sanity checks
	if( !iomon ) {
		mce_log(LL_ERR, "iomon == NULL!");
		keep_going = FALSE;
		goto EXIT;
829 830
	}

831 832 833
	// error conditions
	if( condition & (G_IO_ERR | G_IO_HUP | G_IO_NVAL) ) {
		mce_log(LL_ERR, "iomon '%s' got %s", iomon->path,
834
			mce_io_condition_repr(condition));
835
		keep_going = FALSE;
836
		goto EXIT;
837 838
	}

839 840 841 842
	// input processing
	if( condition & G_IO_IN ) {
		switch (iomon->type) {
		case IOMON_STRING:
843 844
			if( !mce_io_mon_read_string(source, condition, data) ) {
				mce_log(LL_WARN, "mce_io_mon_read_string failed");
845 846 847 848
			}
			break;

		case IOMON_CHUNK:
849 850
			if( !mce_io_mon_read_chunks(source, condition, data) ) {
				mce_log(LL_WARN, "mce_io_mon_read_chunks failed");
851 852 853 854 855 856 857 858
			}
			break;

		default:
		case IOMON_UNSET:
			mce_log(LL_WARN, "unknown iomon type");
			keep_going = FALSE;
			break;
859 860 861
		}
	}

862 863 864 865 866 867 868 869 870 871 872 873
EXIT:
	// cancel io monitor
	if( !keep_going && iomon ) {
		/* Mark error watch as removed */
		iomon->iowatch_id = 0;

		/* Adjust actions based on error policy */
		switch (iomon->error_policy) {
		case MCE_IO_ERROR_POLICY_EXIT:
			terminate = TRUE;
			loglevel = LL_CRIT;
			break;
874

875 876 877
		case MCE_IO_ERROR_POLICY_WARN:
			loglevel = LL_WARN;
			break;
878

879 880 881 882 883
		default:
		case MCE_IO_ERROR_POLICY_IGNORE:
			loglevel = LL_DEBUG;
			break;
		}
884

885 886
		/* Write log */
		mce_log(loglevel, "disabling io monitor for: %s", iomon->path);
887

888
		/* Remove IO monitor */
889
		mce_io_mon_unregister(iomon);
890 891
	}

892 893 894 895 896 897 898
	// terminate process
	if( terminate ) {
		mce_log(LL_CRIT, "terminating due to error policy");
		mce_quit_mainloop();
	}

	return keep_going;
899 900
}

901 902 903 904
/* ========================================================================= *
 * I/O MONITOR API
 * ========================================================================= */

905
/**
906
 * Register an I/O monitor; reads and returns data
907
 *
908 909 910 911 912 913 914 915
 * @param fd File Descriptor; this takes priority over file; -1 if not used
 * @param file Path to the file
 * @param error_policy MCE_IO_ERROR_POLICY_EXIT to exit on error,
 *                     MCE_IO_ERROR_POLICY_WARN to warn about errors
 *                                              but ignore them,
 *                     MCE_IO_ERROR_POLICY_IGNORE to silently ignore errors
 * @param callback Function to call with result
 * @return An I/O monitor pointer on success, NULL on failure
916
 */
917 918 919 920 921 922
static mce_io_mon_t *mce_io_mon_register(gint fd,
					 const gchar *path,
					 error_policy_t error_policy,
					 gboolean rewind_policy,
					 mce_io_mon_notify_cb callback,
					 mce_io_mon_delete_cb delete_cb)
923
{
924
	bool          success = false;
925
	mce_io_mon_t *iomon   = 0;
926
	GError       *error   = NULL;
927

928 929 930
	/* Sanity checks */
	if( !path ) {
		mce_log(LL_ERR, "path == NULL!");
931 932 933
		goto EXIT;
	}

934 935
	if( !callback ) {
		mce_log(LL_ERR, "callback == NULL!");
936 937 938
		goto EXIT;
	}

939 940
	/* Silently ignore non-existing files */
	if( fd == -1 && access(path, F_OK) == -1 )
941 942
		goto EXIT;

943
	/* Allocate monitor object */
944
	if( !(iomon = mce_io_mon_create(path, delete_cb)) )
945
		goto EXIT;
946

947 948
	/* Add to monitor list */
	file_monitors = g_slist_prepend(file_monitors, iomon);
949

950 951 952
	/* Set custom props */
	iomon->nofity_cb    = callback;
	iomon->error_policy = error_policy;
953

954 955 956 957 958
	/* Set up io channel */
	if( fd != -1 )
		iomon->iochan = g_io_channel_unix_new(fd);
	else
		iomon->iochan = g_io_channel_new_file(path, "r", &error);
959

960 961 962
	if( !iomon->iochan ) {
		mce_log(LL_ERR, "Failed to open `%s'; %s", path,
			error ? error->message : "unknown error");
963 964 965
		goto EXIT;
	}

966 967
	/* Transfer fd ownership to io channel */
	g_io_channel_set_close_on_unref(iomon->iochan, TRUE), fd = -1;
968

969
	/* Glib seekability is broken, probe via syscall */
970
	mce_io_mon_probe_seekable(iomon);
971

972 973 974 975 976 977
	/* Set rewind policy */
	if( iomon->seekable ) {
		iomon->rewind_policy = rewind_policy;
	} else if( rewind_policy ) {
		mce_log(LL_ERR, "Attempting to set rewind policy to TRUE "
			"on non-seekable I/O channel `%s'", path);
978 979
	}

980
	success = true;
981
EXIT:
982 983
	if( fd != -1 )
		close(fd);
984

985
	if( !success )
986
		mce_io_mon_delete(iomon), iomon = 0;
987 988 989 990

	g_clear_error(&error);

	return iomon;
991 992
}

993
/** Unregister an I/O monitor
994
 *
995
 * @param io_monitor A pointer to the I/O monitor to unregister
996
 */
997
void mce_io_mon_unregister(mce_io_mon_t *iomon)
998
{
999
	mce_io_mon_delete(iomon);
1000
}
1001 1002

/** Remove all touch device I/O monitors in a list
1003
 *
1004
 * @param list A list of I/O monitors
1005
 */
1006
void mce_io_mon_unregister_list(GSList *list)
1007
{
1008 1009 1010
	GSList *now, *zen;
	for( now = list; now; now = zen ) {
		zen = now->next;
1011
		mce_io_mon_unregister(now->data);
1012 1013
	}
}
1014

1015 1016 1017 1018
/** Unregister I/O monitors for the given path
 *
 * @param path Path to file for which all monitors should be unregistered
 */
1019
void mce_io_mon_unregister_at_path(const char *path)
1020
{
1021
	GSList *now, *zen;
1022

1023 1024
	if( !path )
		goto EXIT;
1025

1026 1027
	for( now = file_monitors; now; now = zen ) {
		zen = now->next;
1028

1029
		mce_io_mon_t *self = now->data;
1030

1031 1032
		if( !self->path || strcmp(self->path, path) )
			continue;
1033

1034
		mce_io_mon_unregister(self);
1035
	}
1036 1037
EXIT:
	return;
1038 1039
}

1040 1041 1042 1043 1044
/**
 * Suspend an I/O monitor
 *
 * @param io_monitor A pointer to the I/O monitor to suspend
 */
1045
void mce_io_mon_suspend(mce_io_mon_t *iomon)
1046
{
1047 1048
	if( !iomon ) {
		mce_log(LL_ERR, "iomon == NULL!");
1049
		goto EXIT;
1050
	}
1051

1052 1053 1054 1055 1056
	/* Remove I/O watches */
	if( iomon->iowatch_id ) {
		g_source_remove(iomon->iowatch_id),
			iomon->iowatch_id = 0;
	}
1057

1058
	iomon->suspended = TRUE;
1059 1060 1061 1062 1063

EXIT:
	return;
}

1064
/**
1065
 * Resume an I/O monitor
1066
 *
1067
 * @param io_monitor A pointer to the I/O monitor to resume
1068
 */
1069
void mce_io_mon_resume(mce_io_mon_t *iomon)
1070
{
1071 1072
	if( !iomon ) {
		mce_log(LL_ERR, "iomon == NULL!");
1073 1074 1075
		goto EXIT;
	}

1076 1077
	if( !iomon->suspended )
		goto EXIT;
1078

1079 1080 1081 1082 1083 1084 1085
	/* Seek to the end of the file if the file is seekable,
	 * and rewind policy is not requested
	 */
	if( iomon->seekable && !iomon->rewind_policy ) {
		GError *error = NULL;
		g_io_channel_seek_position(iomon->iochan, 0,
					   G_SEEK_END, &error);
1086 1087
		if( error ) {
			mce_log(LL_ERR,	"%s: seek error: %s",
1088
				iomon->path, error->message);
1089
		}
Santtu Lakkala's avatar
Santtu Lakkala committed
1090
		g_clear_error(&error);
1091 1092
	}

1093 1094 1095
	/* Set up input monitor */
	if( iomon->iowatch_id )
		g_source_remove(iomon->iowatch_id);
1096

1097 1098 1099
	iomon->iowatch_id =
		g_io_add_watch(iomon->iochan,
			       G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
1100
			       mce_io_mon_input_cb, iomon);
1101

1102 1103
	/* Mark as not-suspended */
	iomon->suspended = FALSE;
1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122

EXIT:
	return;
}

/**
 * Register an I/O monitor; reads and returns a string
 *
 * @param fd File Descriptor; this takes priority over file; -1 if not used
 * @param file Path to the file
 * @param error_policy MCE_IO_ERROR_POLICY_EXIT to exit on error,
 *                     MCE_IO_ERROR_POLICY_WARN to warn about errors
 *                                              but ignore them,
 *                     MCE_IO_ERROR_POLICY_IGNORE to silently ignore errors
 * @param rewind_policy TRUE to seek to the beginning,
 *                      FALSE to stay at current position
 * @param callback Function to call with result
 * @return An I/O monitor cookie on success, NULL on failure
 */
1123 1124 1125 1126 1127 1128
mce_io_mon_t *mce_io_mon_register_string(const gint fd,
					 const gchar *const file,
					 error_policy_t error_policy,
					 gboolean rewind_policy,
					 mce_io_mon_notify_cb callback,
					 mce_io_mon_delete_cb delete_cb)
1129
{
1130
	mce_io_mon_t *iomon = NULL;
1131

1132 1133 1134
	iomon = mce_io_mon_register(fd, file,
				    error_policy, rewind_policy,
				    callback, delete_cb);
1135 1136 1137 1138 1139 1140

	if (iomon == NULL)
		goto EXIT;

	/* Set the I/O monitor type and call resume to add an I/O watch */
	iomon->type = IOMON_STRING;
1141
	mce_io_mon_resume(iomon);
1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161

EXIT:
	return iomon;
}

/**
 * Register an I/O monitor; reads and returns a chunk of specified size
 *
 * @param fd File Descriptor; this takes priority over file; -1 if not used
 * @param file Path to the file
 * @param error_policy MCE_IO_ERROR_POLICY_EXIT to exit on error,
 *                     MCE_IO_ERROR_POLICY_WARN to warn about errors
 *                                              but ignore them,
 *                     MCE_IO_ERROR_POLICY_IGNORE to silently ignore errors
 * @param rewind_policy TRUE to seek to the beginning,
 *                      FALSE to stay at current position
 * @param callback Function to call with result
 * @param chunk_size The number of bytes to read in each chunk
 * @return An I/O monitor cookie on success, NULL on failure
 */
1162 1163 1164 1165 1166 1167 1168
mce_io_mon_t *mce_io_mon_register_chunk(const gint fd,
					const gchar *const file,
					error_policy_t error_policy,
					gboolean rewind_policy,
					mce_io_mon_notify_cb callback,
					mce_io_mon_delete_cb delete_cb,
					gulong chunk_size)
1169
{
1170
	mce_io_mon_t *iomon = NULL;
1171 1172
	GError *error = NULL;

1173 1174 1175
	iomon = mce_io_mon_register(fd, file,
				    error_policy, rewind_policy,
				    callback, delete_cb);
1176

1177
	if( !iomon )
1178 1179 1180
		goto EXIT;

	/* We only read this file in binary form */
1181 1182
	g_io_channel_set_encoding(iomon->iochan, NULL, &error);
	g_clear_error(&error);
1183

1184 1185 1186 1187 1188 1189 1190
	/* No buffering since we're using this for reading data from
	 * device drivers and need to keep the i/o state in sync
	 * between kernel and user space for the automatic suspend
	 * prevention via wakelocks to work
	 */
	g_io_channel_set_buffered(iomon->iochan, FALSE);

1191
	/* Don't block */
1192
	g_io_channel_set_flags(iomon->iochan, G_IO_FLAG_NONBLOCK, &error);
1193 1194 1195
	g_clear_error(&error);

	/* Set the I/O monitor type and call resume to add an I/O watch */
1196 1197
	iomon->type       = IOMON_CHUNK;
	iomon->chunk_size = chunk_size;
1198
	mce_io_mon_resume(iomon);
1199 1200 1201 1202 1203 1204

EXIT:
	return iomon;
}

/**
1205
 * Return the name of the monitored file
1206
 *
1207 1208
 * @param io_monitor An opaque pointer to the I/O monitor structure
 * @return The name of the monitored file
1209
 */
1210
const gchar *mce_io_mon_get_path(const mce_io_mon_t *iomon)
1211
{
1212 1213 1214 1215
	const gchar *path = 0;

	if( iomon )
		path = iomon->path;
1216

1217
	return path;
1218
}
1219

1220 1221 1222 1223 1224 1225 1226 1227
/**
 * Return the file descriptor of the monitored file;
 * if the file being monitored was opened from a path
 * rather than a file descriptor, -1 is returned
 *
 * @param io_monitor An opaque pointer to the I/O monitor structure
 * @return The file descriptor of the monitored file
 */
1228
int mce_io_mon_get_fd(const mce_io_mon_t *iomon)
1229 1230
{
	int fd = -1;
1231

1232 1233
	if( iomon && iomon->iochan )
		fd = g_io_channel_unix_get_fd(iomon->iochan);
1234

1235 1236
	return fd;
}
1237

1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279
/** Attach user data block to io monitor
 *
 * If non-null free_cb callback is given, the user_data block will
 * be released using it when io-monitor itself is deleted.
 *
 * Note: The delete notification callback is called before the
 *       user data is released, i.e. user data is still available
 *       at that point.
 *
 * @param io_monitor An opaque pointer to the I/O monitor structure
 * @param user_data  Data block to attach to io monitor, or NULL
 * @param free_cb    Free function to release data block or NULL
 */
void mce_io_mon_set_user_data(mce_io_mon_t *iomon,
			      void *user_data,
			      mce_io_mon_free_cb free_cb)
{
	if( !iomon )
		goto EXIT;

	/* Clear already existing user data */
	if( iomon->user_data && iomon->user_free_cb )
		iomon->user_free_cb(iomon->user_data);

	/* Set user data */
	iomon->user_data = user_data;
	iomon->user_free_cb = free_cb;
EXIT:
	return;
}

/** Get user data block attached to io monitor
 *
 * @param io_monitor An opaque pointer to the I/O monitor structure
 *
 * @return user data block, or NULL if not set
 */
void *mce_io_mon_get_user_data(const mce_io_mon_t *iomon)
{
	return iomon ? iomon->user_data : 0;
}

1280
/* ========================================================================= *
1281
 * MISC_UTILS
1282
 * ========================================================================= */
1283

1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317
/** Helper for creating I/O watch for file descriptor
 *
 * @param fd              File descriptor for which to add glib io watch
 * @param close_on_unref  True to transfer fd owhership to io watch
 * @param cnd             Condition bitmask
 * @param io_cb           Callback function
 * @param aptr            User data to pass to the callback
 *
 * @return wath id on success, or 0 on failure
 */
guint
mce_io_add_watch(int fd, bool close_on_unref,
		 GIOCondition cnd, GIOFunc io_cb, gpointer aptr)
{
	guint         wid = 0;
	GIOChannel   *chn = 0;

	if( !(chn = g_io_channel_unix_new(fd)) )
		goto cleanup;

	g_io_channel_set_close_on_unref(chn, close_on_unref);

	cnd |= G_IO_ERR | G_IO_HUP | G_IO_NVAL;

	if( !(wid = g_io_add_watch(chn, cnd, io_cb, aptr)) )
		goto cleanup;

cleanup:
	if( chn != 0 ) g_io_channel_unref(chn);

	return wid;

}

1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328
/**
 * Helper function for closing files that checks for NULL,
 * prints proper error messages and NULLs the file pointer after close
 *
 * @param file The name of the file to close; only used by error messages
 * @param fp A pointer to the file pointer to close
 * @return TRUE on success, FALSE on failure
 */
gboolean mce_close_file(const gchar *const file, FILE **fp)
{
	gboolean status = FALSE;
1329

1330 1331 1332 1333 1334
	if (fp == NULL) {
		mce_log(LL_CRIT,
			"fp == NULL!");
		goto EXIT;
	}
1335

1336 1337 1338 1339
	if (*fp == NULL) {
		status = TRUE;
		goto EXIT;
	}
1340

1341 1342 1343 1344 1345 1346
	if (fclose(*fp) == EOF) {
		mce_log(LL_ERR,
			"Failed to close `%s'; %s",
			file ? file : "<unset>",
			g_strerror(errno));
		status = FALSE;
1347

1348
		/* Ignore error */
Santtu Lakkala's avatar
Santtu Lakkala committed
1349
		errno = 0;
1350
		goto EXIT;
1351 1352
	}

1353 1354 1355
	*fp = NULL;

	status = TRUE;
1356 1357

EXIT:
1358
	return status;
1359 1360
}

1361
/**
1362
 * Read a chunk from a file
1363
 *
1364 1365 1366 1367 1368 1369 1370 1371
 * @param file Path to the file, or NULL to use an already open fd instead
 * @param[out] data A newly allocated buffer with the first chunk from the file
 * @param[in,out] len [in] The length of the buffer to read
 *                    [out] The number of bytes read
 * @param flags Additional flags to pass to open();
 *              by default O_RDONLY is always passed -- this is mainly
 *              to allow passing O_NONBLOCK
 * @return TRUE on success, FALSE on failure
1372
 */
1373 1374
gboolean mce_read_chunk_from_file(const gchar *const file, void **data,
				  gssize *len, int flags)
1375
{
1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426
	gboolean status = FALSE;
	gint again_count = 0;
	gssize result = -1;
	gint fd;

	if (file == NULL) {
		mce_log(LL_CRIT, "file == NULL!");
		goto EXIT;
	}

	if (len == NULL) {
		mce_log(LL_CRIT, "len == NULL!");
		goto EXIT;
	}

	if (*len <= 0) {
		mce_log(LL_CRIT, "*len <= 0!");
		goto EXIT;
	}

	/* If we cannot open the file, abort */
	if ((fd = open(file, O_RDONLY | flags)) == -1) {
		mce_log(LL_ERR,
			"Cannot open `%s' for reading; %s",
			file, g_strerror(errno));

		/* Ignore error */
		errno = 0;
		goto EXIT;
	}

	if ((*data = g_try_malloc(*len)) == NULL) {
		mce_log(LL_CRIT,
			"Failed to allocate memory (%zd bytes)!",
			*len);
		goto EXIT2;
	}

	while (again_count++ < 10) {
		/* Clear errors from earlier iterations */
		errno = 0;

		result = read(fd, *data, *len);

		if ((result == -1) &&
		    ((errno == EAGAIN) || (errno == EWOULDBLOCK))) {
			continue;
		} else {
			break;
		}
	}
1427

1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445
	if (result == -1) {
		mce_log(LL_ERR,
			"Failed to read from `%s'; %s",
			file, g_strerror(errno));

		/* Ignore error */
		errno = 0;
		goto EXIT2;
	}

	status = TRUE;

EXIT2:
	if (close(fd) == -1) {
		mce_log(LL_ERR,
			"Failed to close `%s'; %s",
			file, g_strerror(errno));
		errno = 0;
1446
	}
1447 1448 1449 1450 1451 1452 1453 1454

	/* Ignore error */
	errno = 0;

	*len = result;

EXIT:
	return status;
1455 1456
}

1457
/**
1458
 * Read a string from a file
1459
 *
1460 1461 1462
 * @param file Path to the file
 * @param[out] string A newly allocated string with the first line of the file
 * @return TRUE on success, FALSE on failure
1463
 */
1464
gboolean mce_read_string_from_file(const gchar *const file, gchar **string)
1465
{
1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488
	GError *error = NULL;
	gboolean status = FALSE;

	if (file == NULL) {
		mce_log(LL_CRIT, "file == NULL!");
		goto EXIT;
	}

	if (g_file_get_contents(file, string, NULL, &error) == FALSE) {
		mce_log(LL_ERR,
			"Cannot open `%s' for reading; %s",
			file, error->message);
		goto EXIT;
	}

	status = TRUE;

EXIT:
	/* Reset errno,
	 * to avoid false positives down the line
	 */
	errno = 0;
	g_clear_error(&error);
1489

1490
	return status;
1491 1492 1493
}

/**
1494
 * Read a number representation of a string from a file
1495
 *
1496 1497 1498 1499 1500 1501 1502 1503 1504 1505
 * @param file Path to the file, or NULL to user an already open FILE * instead
 * @param[out] number A number representation of the first line of the file
 * @param fp A pointer to a FILE *; set the FILE * to NULL to use the file
 *           path instead
 * @param rewind_file TRUE to seek to the beginning of the file,
 *                    FALSE to read from the current position;
 *                    only affects already open files
 * @param close_on_exit TRUE to close the file on exit,
 *                      FALSE to leave the file open
 * @return TRUE on success, FALSE on failure
1506
 */
1507 1508 1509 1510
gboolean mce_read_number_string_from_file(const gchar *const file,
					  gulong *number, FILE **fp,
					  gboolean rewind_file,
					  gboolean close_on_exit)
1511
{
1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549
	gboolean status = FALSE;
	gint again_count = 0;
	FILE *new_fp = NULL;
	gint retval;

	if ((file == NULL) && ((fp == NULL) || (*fp == NULL))) {
		mce_log(LL_CRIT,
			"(file == NULL) && ((fp == NULL) || (*fp == NULL))!");
		goto EXIT;
	}

	if ((fp == NULL) && (close_on_exit == FALSE)) {
		mce_log(LL_CRIT,
			"(fp == NULL) && (close_on_exit == FALSE)!");
		goto EXIT;
	}

	/* If we cannot open the file, abort */
	if ((fp == NULL) || (*fp == NULL)) {
		if ((new_fp = fopen(file, "r")) == NULL) {
			mce_log(LL_ERR,
				"Cannot open `%s' for reading; %s",
				file, g_strerror(errno));

			/* Ignore error */
			errno = 0;
			goto EXIT;
		}
	} else {
		new_fp = *fp;
	}

	/* Rewind file if we already have one */
	if ((fp != NULL) && (*fp != NULL) && (rewind_file == TRUE)) {
		if (fseek(*fp, 0L, SEEK_SET) == -1) {
			mce_log(LL_ERR,
				"Failed to rewind `%s'; %s",
				file, g_strerror(errno));
1550

1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598
			/* Ignore error */
			errno = 0;
			goto EXIT2;
		}
	}

	if ((fp != NULL) && (*fp == NULL))
		*fp = new_fp;

	while (again_count++ < 10) {
		/* Clear errors from earlier iterations */
		clearerr(new_fp);
		errno = 0;

		retval = fscanf(new_fp, "%lu", number);

		if ((retval == EOF) &&
		    (ferror(new_fp) != 0) && (errno == EAGAIN)) {
			continue;
		} else {
			break;
		}
	}

	/* Was the read successful? */
	if ((retval == EOF) && (ferror(new_fp) != 0)) {
		mce_log(LL_ERR,
			"Failed to read from `%s'; %s",
			file, g_strerror(errno));
		clearerr(new_fp);

		/* Ignore error */
		errno = 0;
		goto EXIT2;
	}

	if (retval != 1) {
		mce_log(LL_ERR,
			"Could not match any values when reading from `%s'",
			file);
		goto EXIT2;
	}

	status = TRUE;

EXIT2:
	/* XXX: improve close policy? */
	if ((status == FALSE) || (close_on_exit == TRUE)) {
1599
		mce_close_file(file, &new_fp);
1600 1601 1602 1603 1604 1605