/*
 * Copyright (c) 2000-2006 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. The rights granted to you under the License
 * may not be used to create, or enable the creation or redistribution of,
 * unlawful or unlicensed copies of an Apple operating system, or to
 * circumvent, violate, or enable the circumvention or violation of, any
 * terms of an Apple operating system software license agreement.
 *
 * Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
 */
/* Copyright (c) 1995 NeXT Computer, Inc. All Rights Reserved */
/*-
 * Copyright (c) 1986, 1988, 1991, 1993
 *	The Regents of the University of California.  All rights reserved.
 * (c) UNIX System Laboratories, Inc.
 * All or some portions of this file are derived from material licensed
 * to the University of California by American Telephone and Telegraph
 * Co. or Unix System Laboratories, Inc. and are reproduced herein with
 * the permission of UNIX System Laboratories, Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *	@(#)subr_prf.c	8.4 (Berkeley) 5/4/95
 */
/* HISTORY
 * 22-Sep-1997 Umesh Vaishampayan (umeshv@apple.com)
 *	Cleaned up m68k crud. Fixed vlog() to do logpri() for ppc, too.
 *
 * 17-July-97  Umesh Vaishampayan (umeshv@apple.com)
 *	Eliminated multiple definition of constty which is defined
 *	in bsd/dev/XXX/cons.c
 *
 * 26-MAR-1997 Umesh Vaishampayan (umeshv@NeXT.com
 *      Fixed tharshing format in many functions. Cleanup.
 *
 * 17-Jun-1995 Mac Gillon (mgillon) at NeXT
 *	Purged old history
 *	New version based on 4.4 and NS3.3
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/reboot.h>
#include <sys/msgbuf.h>
#include <sys/proc_internal.h>
#include <sys/ioctl.h>
#include <sys/tty.h>
#include <sys/file_internal.h>
#include <sys/tprintf.h>
#include <sys/syslog.h>
#include <stdarg.h>
#include <sys/malloc.h>
#include <sys/lock.h>
#include <sys/subr_prf.h>

#include <kern/cpu_number.h>    /* for cpu_number() */
#include <libkern/libkern.h>
#include <os/log_private.h>

/* for vaddlog(): the following are implemented in osfmk/kern/printf.c  */
extern bool bsd_log_lock(bool);
extern void bsd_log_unlock(void);

uint32_t vaddlog_msgcount = 0;
uint32_t vaddlog_msgcount_dropped = 0;

/* Keep this around only because it's exported */
void _printf(int, struct tty *, const char *, ...);

struct snprintf_arg {
	char *str;
	size_t remain;
};


/*
 * In case console is off,
 * debugger_panic_str contains argument to last
 * call to panic.
 */
extern const char       *debugger_panic_str;

extern  void cnputc(char);              /* standard console putc */

extern  struct tty cons;                /* standard console tty */
extern struct   tty *constty;           /* pointer to console "window" tty */
extern int  __doprnt(const char *fmt,
    va_list    argp,
    void       (*)(int, void *),
    void       *arg,
    int        radix,
    int        is_log);

/*
 *	Record cpu that panic'd and lock around panic data
 */

extern  void logwakeup(struct msgbuf *);
extern  void halt_cpu(void);

static void
snprintf_func(int ch, void *arg);

struct putchar_args {
	int flags;
	struct tty *tty;
	bool last_char_was_cr;
};
static void putchar(int c, void *arg);


/*
 * Uprintf prints to the controlling terminal for the current process.
 * It may block if the tty queue is overfull.  No message is printed if
 * the queue does not clear in a reasonable time.
 */
void
uprintf(const char *fmt, ...)
{
	struct proc *p = current_proc();
	struct putchar_args pca;
	va_list ap;
	struct session *sessp;

	sessp = proc_session(p);

	if (p->p_flag & P_CONTROLT && sessp != SESSION_NULL && sessp->s_ttyvp) {
		pca.flags = TOTTY;
		pca.tty   = SESSION_TP(sessp);
		if (pca.tty != NULL) {
			tty_lock(pca.tty);
		}
		va_start(ap, fmt);
		__doprnt(fmt, ap, putchar, &pca, 10, FALSE);
		va_end(ap);
		if (pca.tty != NULL) {
			tty_unlock(pca.tty);
		}
	}
	if (sessp != SESSION_NULL) {
		session_rele(sessp);
	}
}

tpr_t
tprintf_open(struct proc *p)
{
	struct session * sessp;

	sessp = proc_session(p);

	if (p->p_flag & P_CONTROLT && sessp->s_ttyvp) {
		return (tpr_t)sessp;
	}
	if (sessp != SESSION_NULL) {
		session_rele(sessp);
	}

	return (tpr_t) NULL;
}

void
tprintf_close(tpr_t sessp)
{
	if (sessp) {
		session_rele((struct session *) sessp);
	}
}

/*
 * tprintf prints on the controlling terminal associated
 * with the given session.
 *
 * NOTE:	No one else should call this function!!!
 */
void
tprintf(tpr_t tpr, const char *fmt, ...)
{
	struct session *sess = (struct session *)tpr;
	struct tty *tp;
	va_list ap;
	struct putchar_args pca;

	if (sess && (tp = SESSION_TP(sess)) != TTY_NULL) {
		/* ttycheckoutq(), tputchar() require a locked tp */
		tty_lock(tp);
		if (ttycheckoutq(tp, 0)) {
			pca.flags = TOTTY;
			/* going to the tty; leave locked */
			pca.tty = tp;
			va_start(ap, fmt);
			__doprnt(fmt, ap, putchar, &pca, 10, FALSE);
			va_end(ap);
		}
		tty_unlock(tp);
	}

	va_start(ap, fmt);
	os_log_with_args(OS_LOG_DEFAULT, OS_LOG_TYPE_DEFAULT, fmt, ap, __builtin_return_address(0));
	va_end(ap);
}

/*
 * Ttyprintf displays a message on a tty; it should be used only by
 * the tty driver, or anything that knows the underlying tty will not
 * be revoke(2)'d away.  Other callers should use tprintf.
 *
 * Locks:	It is assumed that the tty_lock() is held over the call
 *		to this function.  Ensuring this is the responsibility
 *		of the caller.
 */
void
ttyprintf(struct tty *tp, const char *fmt, ...)
{
	va_list ap;

	if (tp != NULL) {
		struct putchar_args pca;
		pca.flags = TOTTY;
		pca.tty   = tp;

		va_start(ap, fmt);
		__doprnt(fmt, ap, putchar, &pca, 10, TRUE);
		va_end(ap);
	}
}

void
logtime(time_t secs)
{
	printf("Time 0x%lx Message ", secs);
}

static void
putchar_asl(int c, void *arg)
{
	struct putchar_args *pca = arg;

	if ((pca->flags & TOLOGLOCKED) && c != '\0' && c != '\r' && c != 0177) {
		log_putc_locked(aslbufp, (char)c);
	}
	putchar(c, arg);
}

/*
 * Vestigial support for kern_asl_msg() via /dev/klog
 */
int
vaddlog(const char *fmt, va_list ap)
{
	if (!bsd_log_lock(oslog_is_safe())) {
		os_atomic_inc(&vaddlog_msgcount_dropped, relaxed);
		return 1;
	}

	struct putchar_args pca = {
		.flags = TOLOGLOCKED,
		.tty = NULL,
	};

	__doprnt(fmt, ap, putchar_asl, &pca, 10, TRUE);
	bsd_log_unlock();
	logwakeup(NULL);

	os_atomic_inc(&vaddlog_msgcount, relaxed);
	return 0;
}

void
_printf(int flags, struct tty *ttyp, const char *format, ...)
{
	va_list ap;
	struct putchar_args pca;

	pca.flags = flags;
	pca.tty   = ttyp;

	if (ttyp != NULL) {
		tty_lock(ttyp);

		va_start(ap, format);
		__doprnt(format, ap, putchar, &pca, 10, TRUE);
		va_end(ap);

		tty_unlock(ttyp);
	}
}

int
prf(const char *fmt, va_list ap, int flags, struct tty *ttyp)
{
	struct putchar_args pca;

	pca.flags = flags;
	pca.tty   = ttyp;

	__doprnt(fmt, ap, putchar, &pca, 10, TRUE);

	return 0;
}

/*
 * Warn that a system table is full.
 */
void
tablefull(const char *tab)
{
	log(LOG_ERR, "%s: table is full\n", tab);
}

/*
 * Print a character on console or users terminal.
 * If destination is console then the last MSGBUFS characters
 * are saved in msgbuf for inspection later.
 *
 * Locks:	If TOTTY is set, we assume that the tty lock is held
 *		over the call to this function.
 */
/*ARGSUSED*/
void
putchar(int c, void *arg)
{
	struct putchar_args *pca = arg;
	char **sp = (char**) pca->tty;

	if (debugger_panic_str) {
		constty = 0;
	}
	if ((pca->flags & TOCONS) && pca->tty == NULL && constty) {
		pca->tty = constty;
		pca->flags |= TOTTY;
	}
	if ((pca->flags & TOTTY) && pca->tty && tputchar(c, pca->tty) < 0 &&
	    (pca->flags & TOCONS) && pca->tty == constty) {
		constty = 0;
	}
	if ((pca->flags & TOLOG) && c != '\0' && c != '\r' && c != 0177) {
		log_putc((char)c);
	}
	if ((pca->flags & TOLOGLOCKED) && c != '\0' && c != '\r' && c != 0177) {
		log_putc_locked(msgbufp, (char)c);
	}
	if ((pca->flags & TOCONS) && constty == 0 && c != '\0') {
		cnputc((char)c);
	}
	if (pca->flags & TOSTR) {
		**sp = (char)c;
		(*sp)++;
	}

	pca->last_char_was_cr = ('\n' == c);
}

bool
printf_log_locked(bool addcr, const char *fmt, ...)
{
	bool retval;
	va_list args;

	va_start(args, fmt);
	retval = vprintf_log_locked(fmt, args, addcr);
	va_end(args);

	return retval;
}

bool
vprintf_log_locked(const char *fmt, va_list ap, bool addcr)
{
	struct putchar_args pca;

	pca.flags = TOLOGLOCKED;
	pca.tty   = NULL;
	pca.last_char_was_cr = false;
	__doprnt(fmt, ap, putchar, &pca, 10, TRUE);
	if (addcr) {
		putchar('\n', &pca);
	}
	return pca.last_char_was_cr;
}

#if CONFIG_VSPRINTF
/*
 * Scaled down version of vsprintf(3).
 *
 * Deprecation Warning:
 *	vsprintf() is being deprecated. Please use vsnprintf() instead.
 */
int
vsprintf(char *buf, const char *cfmt, va_list ap)
{
	int retval;
	struct snprintf_arg info;

	info.str = buf;
	info.remain = 999999;

	retval = __doprnt(cfmt, ap, snprintf_func, &info, 10, FALSE);
	if (info.remain >= 1) {
		*info.str++ = '\0';
	}
	return 0;
}
#endif  /* CONFIG_VSPRINTF */

/*
 * Scaled down version of snprintf(3).
 */
int
snprintf(char *str, size_t size, const char *format, ...)
{
	int retval;
	va_list ap;

	va_start(ap, format);
	retval = vsnprintf(str, size, format, ap);
	va_end(ap);
	return retval;
}

/*
 * Scaled down version of vsnprintf(3).
 */
int
vsnprintf(char *str, size_t size, const char *format, va_list ap)
{
	struct snprintf_arg info;
	int retval;

	info.str = str;
	info.remain = size;
	retval = __doprnt(format, ap, snprintf_func, &info, 10, FALSE);
	if (info.remain >= 1) {
		*info.str++ = '\0';
	}
	return retval;
}

int
vscnprintf(char *buf, size_t size, const char *fmt, va_list args)
{
	ssize_t ssize = size;
	int i;

	i = vsnprintf(buf, size, fmt, args);

	return (i >= ssize) ? (int)(ssize - 1) : i;
}

int
scnprintf(char *buf, size_t size, const char *fmt, ...)
{
	va_list args;
	int i;

	va_start(args, fmt);
	i = vscnprintf(buf, size, fmt, args);
	va_end(args);

	return i;
}

static void
snprintf_func(int ch, void *arg)
{
	struct snprintf_arg *const info = arg;

	if (info->remain >= 2) {
		*info->str++ = (char)ch;
		info->remain--;
	}
}

int
kvprintf(char const *fmt, void (*func)(int, void*), void *arg, int radix, va_list ap)
{
	__doprnt(fmt, ap, func, arg, radix, TRUE);
	return 0;
}
