/* Copyright (C) 2004       Manuel Novoa III    <mjn3@codepoet.org>
 *
 * GNU Library General Public License (LGPL) version 2 or later.
 *
 * Dedicated to Toni.  See uClibc/DEDICATION.mjn3 for details.
 */

#include "_stdio.h"


#ifdef __DO_UNLOCKED

/* Even if the stream is set to user-locking, we still need to lock
 * when all (lbf) writing streams are flushed. */

#define __MY_STDIO_THREADLOCK(__stream)					\
        __UCLIBC_IO_MUTEX_CONDITIONAL_LOCK((__stream)->__lock,		\
	(_stdio_user_locking != 2))

#define __MY_STDIO_THREADUNLOCK(__stream)				\
        __UCLIBC_IO_MUTEX_CONDITIONAL_UNLOCK((__stream)->__lock,		\
	(_stdio_user_locking != 2))

#if defined(__UCLIBC_HAS_THREADS__) && defined(__STDIO_BUFFERS)
void attribute_hidden _stdio_openlist_dec_use(void)
{
	__STDIO_THREADLOCK_OPENLIST_DEL;
	if ((_stdio_openlist_use_count == 1) && (_stdio_openlist_del_count > 0)) {
		FILE *p = NULL;
		FILE *n;
		FILE *stream;

		/* Grab the openlist add lock since we might change the head of the list. */
		__STDIO_THREADLOCK_OPENLIST_ADD;
		for (stream = _stdio_openlist; stream; stream = n) {
			n = stream->__nextopen;
			if ((stream->__modeflags & (__FLAG_READONLY|__FLAG_WRITEONLY|__FLAG_FAILED_FREOPEN))
				== (__FLAG_READONLY|__FLAG_WRITEONLY)
				) {		 /* The file was closed and should be removed from the list. */
				if (!p) {
					_stdio_openlist = n;
				} else {
					p->__nextopen = n;
				}
				__STDIO_STREAM_FREE_FILE(stream);
			} else {
				p = stream;
			}
		}
		__STDIO_THREADUNLOCK_OPENLIST_ADD;
		_stdio_openlist_del_count = 0; /* Should be clean now. */
	}
	--_stdio_openlist_use_count;
	__STDIO_THREADUNLOCK_OPENLIST_DEL;
}
#endif

int fflush_unlocked(register FILE *stream)
{
#ifdef __STDIO_BUFFERS

	int retval = 0;
	unsigned short bufmask = __FLAG_LBF;

#ifndef NDEBUG
	if ((stream != NULL) && (stream != (FILE *) &_stdio_openlist)) {
		__STDIO_STREAM_VALIDATE(stream); /* debugging only */
	}
#endif

	if (stream == (FILE *) &_stdio_openlist) { /* Flush all lbf streams. */
		stream = NULL;
		bufmask = 0;
	}

	if (!stream) {				/* Flush all (lbf) writing streams. */

		__STDIO_OPENLIST_INC_USE;

		__STDIO_THREADLOCK_OPENLIST_ADD;
		stream = _stdio_openlist;
		__STDIO_THREADUNLOCK_OPENLIST_ADD;

		while(stream) {
			/* We only care about currently writing streams and do not want to
			 * block trying to obtain mutexes on non-writing streams. */
			if (__STDIO_STREAM_IS_WRITING(stream)) { /* ONLY IF ATOMIC!!! */
				__MY_STDIO_THREADLOCK(stream);
				/* Need to check again once we have the lock. */
				if (!(((stream->__modeflags | bufmask)
					   ^ (__FLAG_WRITING|__FLAG_LBF)
					   ) & (__FLAG_WRITING|__MASK_BUFMODE))
					) {
					if (!__STDIO_COMMIT_WRITE_BUFFER(stream)) {
						__STDIO_STREAM_DISABLE_PUTC(stream);
						__STDIO_STREAM_CLEAR_WRITING(stream);
					} else {
						retval = EOF;
					}
				}
				__MY_STDIO_THREADUNLOCK(stream);
			}
			stream = stream->__nextopen;
		}

		__STDIO_OPENLIST_DEC_USE;

	} else if (__STDIO_STREAM_IS_WRITING(stream)) {
		if (!__STDIO_COMMIT_WRITE_BUFFER(stream)) {
			__STDIO_STREAM_DISABLE_PUTC(stream);
			__STDIO_STREAM_CLEAR_WRITING(stream);
		} else {
			retval = EOF;
		}
	}
#if 0
	else if (stream->__modeflags & (__MASK_READING|__FLAG_READONLY)) {
		/* ANSI/ISO says behavior in this case is undefined but also says you
		 * shouldn't flush a stream you were reading from.  As usual, glibc
		 * caters to broken programs and simply ignores this. */
		__UNDEFINED_OR_NONPORTABLE;
		__STDIO_STREAM_SET_ERROR(stream);
		__set_errno(EBADF);
		retval = EOF;
	}
#endif

#ifndef NDEBUG
	if ((stream != NULL) && (stream != (FILE *) &_stdio_openlist)) {
		__STDIO_STREAM_VALIDATE(stream); /* debugging only */
	}
#endif

	return retval;

#else  /* __STDIO_BUFFERS --------------------------------------- */

#ifndef NDEBUG
	if ((stream != NULL)
#ifdef __STDIO_HAS_OPENLIST
		&& (stream != (FILE *) &_stdio_openlist)
#endif
		) {
		__STDIO_STREAM_VALIDATE(stream); /* debugging only */
	}
#endif

#if 0
	if (stream && (stream->__modeflags & (__MASK_READING|__FLAG_READONLY))) {
		/* ANSI/ISO says behavior in this case is undefined but also says you
		 * shouldn't flush a stream you were reading from.  As usual, glibc
		 * caters to broken programs and simply ignores this. */
		__UNDEFINED_OR_NONPORTABLE;
		__STDIO_STREAM_SET_ERROR(stream);
		__set_errno(EBADF);
		return EOF;
	}
#endif

	return 0;
#endif /* __STDIO_BUFFERS */
}
libc_hidden_def(fflush_unlocked)

#ifndef __UCLIBC_HAS_THREADS__
strong_alias(fflush_unlocked,fflush)
libc_hidden_def(fflush)
#endif

#elif defined __UCLIBC_HAS_THREADS__

int fflush(register FILE *stream)
{
	int retval;
	__STDIO_AUTO_THREADLOCK_VAR;

	if (stream
#ifdef __STDIO_HAS_OPENLIST
		&& (stream != (FILE *) &_stdio_openlist)
#endif
		) {

		__STDIO_AUTO_THREADLOCK(stream);

		retval = fflush_unlocked(stream);

		__STDIO_AUTO_THREADUNLOCK(stream);
	} else {
		retval = fflush_unlocked(stream);
	}

	return retval;
}
libc_hidden_def(fflush)

#endif
