png_ascii_from_fp(png_const_structrp png_ptr, png_charp ascii, png_size_t size,
    double fp, unsigned int precision)
{
   /* We use standard functions from math.h, but not printf because
    * that would require stdio.  The caller must supply a buffer of
    * sufficient size or we will png_error.  The tests on size and
    * the space in ascii[] consumed are indicated below.
    */
   if (precision < 1)
      precision = DBL_DIG;

   /* Enforce the limit of the implementation precision too. */
   if (precision > DBL_DIG+1)
      precision = DBL_DIG+1;

   /* Basic sanity checks */
   if (size >= precision+5) /* See the requirements below. */
   {
      if (fp < 0)
      {
         fp = -fp;
         *ascii++ = 45; /* '-'  PLUS 1 TOTAL 1 */
         --size;
      }

      if (fp >= DBL_MIN && fp <= DBL_MAX)
      {
         int exp_b10;   /* A base 10 exponent */
         double base;   /* 10^exp_b10 */

         /* First extract a base 10 exponent of the number,
          * the calculation below rounds down when converting
          * from base 2 to base 10 (multiply by log10(2) -
          * 0.3010, but 77/256 is 0.3008, so exp_b10 needs to
          * be increased.  Note that the arithmetic shift
          * performs a floor() unlike C arithmetic - using a
          * C multiply would break the following for negative
          * exponents.
          */
         (void)frexp(fp, &exp_b10); /* exponent to base 2 */

         exp_b10 = (exp_b10 * 77) >> 8; /* <= exponent to base 10 */

         /* Avoid underflow here. */
         base = png_pow10(exp_b10); /* May underflow */

         while (base < DBL_MIN || base < fp)
         {
            /* And this may overflow. */
            double test = png_pow10(exp_b10+1);

            if (test <= DBL_MAX)
               ++exp_b10, base = test;

            else
               break;
         }

         /* Normalize fp and correct exp_b10, after this fp is in the
          * range [.1,1) and exp_b10 is both the exponent and the digit
          * *before* which the decimal point should be inserted
          * (starting with 0 for the first digit).  Note that this
          * works even if 10^exp_b10 is out of range because of the
          * test on DBL_MAX above.
          */
         fp /= base;
         while (fp >= 1) fp /= 10, ++exp_b10;

         /* Because of the code above fp may, at this point, be
          * less than .1, this is ok because the code below can
          * handle the leading zeros this generates, so no attempt
          * is made to correct that here.
          */

         {
            unsigned int czero, clead, cdigits;
            char exponent[10];

            /* Allow up to two leading zeros - this will not lengthen
             * the number compared to using E-n.
             */
            if (exp_b10 < 0 && exp_b10 > -3) /* PLUS 3 TOTAL 4 */
            {
               czero = (unsigned int)(-exp_b10); /* PLUS 2 digits: TOTAL 3 */
               exp_b10 = 0;      /* Dot added below before first output. */
            }
            else
               czero = 0;    /* No zeros to add */

            /* Generate the digit list, stripping trailing zeros and
             * inserting a '.' before a digit if the exponent is 0.
             */
            clead = czero; /* Count of leading zeros */
            cdigits = 0;   /* Count of digits in list. */

            do
            {
               double d;

               fp *= 10;
               /* Use modf here, not floor and subtract, so that
                * the separation is done in one step.  At the end
                * of the loop don't break the number into parts so
                * that the final digit is rounded.
                */
               if (cdigits+czero+1 < precision+clead)
                  fp = modf(fp, &d);

               else
               {
                  d = floor(fp + .5);

                  if (d > 9)
                  {
                     /* Rounding up to 10, handle that here. */
                     if (czero > 0)
                     {
                        --czero, d = 1;
                        if (cdigits == 0) --clead;
                     }
                     else
                     {
                        while (cdigits > 0 && d > 9)
                        {
                           int ch = *--ascii;

                           if (exp_b10 != (-1))
                              ++exp_b10;

                           else if (ch == 46)
                           {
                              ch = *--ascii, ++size;
                              /* Advance exp_b10 to '1', so that the
                               * decimal point happens after the
                               * previous digit.
                               */
                              exp_b10 = 1;
                           }

                           --cdigits;
                           d = ch - 47;  /* I.e. 1+(ch-48) */
                        }

                        /* Did we reach the beginning? If so adjust the
                         * exponent but take into account the leading
                         * decimal point.
                         */
                        if (d > 9)  /* cdigits == 0 */
                        {
                           if (exp_b10 == (-1))
                           {
                              /* Leading decimal point (plus zeros?), if
                               * we lose the decimal point here it must
                               * be reentered below.
                               */
                              int ch = *--ascii;

                              if (ch == 46)
                                 ++size, exp_b10 = 1;

                              /* Else lost a leading zero, so 'exp_b10' is
                               * still ok at (-1)
                               */
                           }
                           else
                              ++exp_b10;

                           /* In all cases we output a '1' */
                           d = 1;
                        }
                     }
                  }
                  fp = 0; /* Guarantees termination below. */
               }

               if (d == 0)
               {
                  ++czero;
                  if (cdigits == 0) ++clead;
               }
               else
               {
                  /* Included embedded zeros in the digit count. */
                  cdigits += czero - clead;
                  clead = 0;

                  while (czero > 0)
                  {
                     /* exp_b10 == (-1) means we just output the decimal
                      * place - after the DP don't adjust 'exp_b10' any
                      * more!
                      */
                     if (exp_b10 != (-1))
                     {
                        if (exp_b10 == 0) *ascii++ = 46, --size;
                        /* PLUS 1: TOTAL 4 */
                        --exp_b10;
                     }
                     *ascii++ = 48, --czero;
                  }

                  if (exp_b10 != (-1))
                  {
                     if (exp_b10 == 0)
                        *ascii++ = 46, --size; /* counted above */

                     --exp_b10;
                  }
                  *ascii++ = (char)(48 + (int)d), ++cdigits;
               }
            }
            while (cdigits+czero < precision+clead && fp > DBL_MIN);

            /* The total output count (max) is now 4+precision */

            /* Check for an exponent, if we don't need one we are
             * done and just need to terminate the string.  At
             * this point exp_b10==(-1) is effectively if flag - it got
             * to '-1' because of the decrement after outputting
             * the decimal point above (the exponent required is
             * *not* -1!)
             */
            if (exp_b10 >= (-1) && exp_b10 <= 2)
            {
               /* The following only happens if we didn't output the
                * leading zeros above for negative exponent, so this
                * doesn't add to the digit requirement.  Note that the
                * two zeros here can only be output if the two leading
                * zeros were *not* output, so this doesn't increase
                * the output count.
                */
               while (--exp_b10 >= 0) *ascii++ = 48;

               *ascii = 0;

               /* Total buffer requirement (including the '\0') is
                * 5+precision - see check at the start.
                */
               return;
            }

            /* Here if an exponent is required, adjust size for
             * the digits we output but did not count.  The total
             * digit output here so far is at most 1+precision - no
             * decimal point and no leading or trailing zeros have
             * been output.
             */
            size -= cdigits;

            *ascii++ = 69, --size;    /* 'E': PLUS 1 TOTAL 2+precision */

            /* The following use of an unsigned temporary avoids ambiguities in
             * the signed arithmetic on exp_b10 and permits GCC at least to do
             * better optimization.
             */
            {
               unsigned int uexp_b10;

               if (exp_b10 < 0)
               {
                  *ascii++ = 45, --size; /* '-': PLUS 1 TOTAL 3+precision */
                  uexp_b10 = (unsigned int)(-exp_b10);
               }

               else
                  uexp_b10 = (unsigned int)exp_b10;

               cdigits = 0;

               while (uexp_b10 > 0)
               {
                  exponent[cdigits++] = (char)(48 + uexp_b10 % 10);
                  uexp_b10 /= 10;
               }
            }

            /* Need another size check here for the exponent digits, so
             * this need not be considered above.
             */
            if (size > cdigits)
            {
               while (cdigits > 0) *ascii++ = exponent[--cdigits];

               *ascii = 0;

               return;
            }
         }
      }
      else if (!(fp >= DBL_MIN))
      {
         *ascii++ = 48; /* '0' */
         *ascii = 0;
         return;
      }
      else
      {
         *ascii++ = 105; /* 'i' */
         *ascii++ = 110; /* 'n' */
         *ascii++ = 102; /* 'f' */
         *ascii = 0;
         return;
      }
   }

   /* Here on buffer too small. */
   png_error(png_ptr, "ASCII conversion buffer too small");
}


char *format_add_linestart(const char *text, const char *linestart)
{
	GString *str;
	char *ret;

	if (linestart == NULL)
		return g_strdup(text);

	if (strchr(text, '\n') == NULL)
		return g_strconcat(linestart, text, NULL);

	str = g_string_new(linestart);
	while (*text != '\0') {
		g_string_append_c(str, *text);
		if (*text == '\n')
			g_string_append(str, linestart);
		text++;
	}

	ret = str->str;
	g_string_free(str, FALSE);
	return ret;
}


char *format_get_text_theme_args(THEME_REC *theme, const char *module,
				 TEXT_DEST_REC *dest, int formatnum,
				 va_list va)
{
	char *arglist[MAX_FORMAT_PARAMS];
	char buffer[DEFAULT_FORMAT_ARGLIST_SIZE];
	FORMAT_REC *formats;

	formats = g_hash_table_lookup(default_formats, module);
	format_read_arglist(va, &formats[formatnum],
			    arglist, sizeof(arglist)/sizeof(char *),
			    buffer, sizeof(buffer));

	return format_get_text_theme_charargs(theme, module, dest,
					      formatnum, arglist);
}


NICK_REC *irc_nicklist_insert(IRC_CHANNEL_REC *channel, const char *nick,
			      int op, int halfop, int voice, int send_massjoin,
			      const char *prefixes)
{
	NICK_REC *rec;

	g_return_val_if_fail(IS_IRC_CHANNEL(channel), NULL);
	g_return_val_if_fail(nick != NULL, NULL);

	rec = g_new0(NICK_REC, 1);
	rec->nick = g_strdup(nick);

	if (op) rec->op = TRUE;
	if (halfop) rec->halfop = TRUE;
	if (voice) rec->voice = TRUE;
	rec->send_massjoin = send_massjoin;

	if (prefixes != NULL) {
		strocpy(rec->prefixes, prefixes, sizeof(rec->prefixes));
	}

	nicklist_insert(CHANNEL(channel), rec);
	return rec;
}


void format_create_dest_tag(TEXT_DEST_REC *dest, void *server,
			    const char *server_tag, const char *target,
			    int level, WINDOW_REC *window)
{
	memset(dest, 0, sizeof(TEXT_DEST_REC));

	dest->server = server;
	dest->server_tag = server != NULL ? SERVER(server)->tag : server_tag;
	dest->target = target;
	dest->level = level;
	dest->window = window != NULL ? window :
		window_find_closest(server, target, level);
}


int term_has_colors(void)
{
        return current_term->TI_colors > 0;
}


void term_move(TERM_WINDOW *window, int x, int y)
{
	if (x >= 0 && y >= 0) {
	vcmove = TRUE;
	vcx = x+window->x;
        vcy = y+window->y;

	if (vcx >= term_width)
		vcx = term_width-1;
	if (vcy >= term_height)
                vcy = term_height-1;
	}
}


int format_get_length(const char *str)
{
	GString *tmp;
	int len;
	gboolean utf8;
	int adv = 0;

	g_return_val_if_fail(str != NULL, 0);

	utf8 = is_utf8() && g_utf8_validate(str, -1, NULL);

	tmp = g_string_new(NULL);
	len = 0;
	while (*str != '\0') {
		if (*str == '%' && str[1] != '\0') {
			str++;
			if (*str != '%') {
			     adv = format_expand_styles(tmp, &str, NULL);
			     str += adv;
			     if (adv)
				continue;
			}

			/* %% or unknown %code, written as-is */
			if (*str != '%')
				len++;
		}

		len += advance(&str, utf8);
	}

	g_string_free(tmp, TRUE);
	return len;
}


static const char *get_ansi_color(THEME_REC *theme, const char *str,
				  int *fg_ret, int *bg_ret, int *flags_ret)
{
	static char ansitab[8] = { 0, 4, 2, 6, 1, 5, 3, 7 };
	const char *start;
	int fg, bg, flags, num, i;
	unsigned int num2;

	if (*str != '[')
		return str;
	start = str++;

	fg = fg_ret == NULL || *fg_ret < 0 ? theme->default_color : *fg_ret;
	bg = bg_ret == NULL || *bg_ret < 0 ? -1 : *bg_ret;
	flags = flags_ret == NULL ? 0 : *flags_ret;

	num = 0;
	for (;; str++) {
		if (*str == '\0') return start;

		if (i_isdigit(*str)) {
			num = num*10 + (*str-'0');
			continue;
		}

		if (*str != ';' && *str != 'm')
			return start;

		switch (num) {
		case 0:
			/* reset colors and attributes back to default */
			fg = theme->default_color;
			bg = -1;
			flags &= ~(GUI_PRINT_FLAG_INDENT |
				   GUI_PRINT_FLAG_BOLD | GUI_PRINT_FLAG_ITALIC | GUI_PRINT_FLAG_UNDERLINE |
				   GUI_PRINT_FLAG_BLINK | GUI_PRINT_FLAG_REVERSE |
				   GUI_PRINT_FLAG_COLOR_24_FG | GUI_PRINT_FLAG_COLOR_24_BG);
			break;
		case 1:
			/* hilight */
			flags |= GUI_PRINT_FLAG_BOLD;
			break;
		case 22:
			/* normal */
			flags &= ~GUI_PRINT_FLAG_BOLD;
			break;
		case 3:
			/* italic */
			flags |= GUI_PRINT_FLAG_ITALIC;
			break;
		case 23:
			/* not italic */
			flags &= ~GUI_PRINT_FLAG_ITALIC;
			break;
		case 4:
			/* underline */
			flags |= GUI_PRINT_FLAG_UNDERLINE;
			break;
		case 24:
			/* not underline */
			flags &= ~GUI_PRINT_FLAG_UNDERLINE;
			break;
		case 5:
			/* blink */
			flags |= GUI_PRINT_FLAG_BLINK;
			break;
		case 25:
			/* steady */
			flags &= ~GUI_PRINT_FLAG_BLINK;
			break;
		case 7:
			/* reverse */
			flags |= GUI_PRINT_FLAG_REVERSE;
			break;
		case 27:
			/* positive */
			flags &= ~GUI_PRINT_FLAG_REVERSE;
			break;
		case 39:
			/* reset fg */
			flags &= ~GUI_PRINT_FLAG_COLOR_24_FG;
			fg = theme->default_color;
			break;
		case 49:
			/* reset bg */
			bg = -1;
			flags &= ~(GUI_PRINT_FLAG_COLOR_24_BG | GUI_PRINT_FLAG_INDENT);
			break;
		case 38:
		case 48:
			/* ANSI indexed color or RGB color */
			if (*str != ';') break;
			str++;
			for (num2 = 0; i_isdigit(*str); str++)
				num2 = num2*10 + (*str-'0');
			if (*str == '\0') return start;

			switch (num2) {
			case 2:
				/* RGB */
				num2 = 0;

				for (i = 0; i < 3; ++i) {
					num2 <<= 8;

					if (*str != ';' && *str != ':') {
						i = -1;
						break;
					}
					str++;
					for (; i_isdigit(*str); str++)
						num2 = (num2&~0xff) |
							(((num2&0xff) * 10 + (*str-'0'))&0xff);

					if (*str == '\0') return start;
				}

				if (i == -1) break;
#ifdef TERM_TRUECOLOR
				if (num == 38) {
					flags |= GUI_PRINT_FLAG_COLOR_24_FG;
					fg = num2;
				} else if (num == 48) {
					flags |= GUI_PRINT_FLAG_COLOR_24_BG;
					bg = num2;
				}
#else /* !TERM_TRUECOLOR */
				if (num == 38) {
					flags &= ~GUI_PRINT_FLAG_COLOR_24_FG;
					fg = color_24bit_256_int(num2);
				} else if (num == 48) {
					flags &= ~GUI_PRINT_FLAG_COLOR_24_BG;
					bg = color_24bit_256_int(num2);
				}
#endif

				break;
			case 5:
				/* indexed */
				if (*str != ';') break;
				str++;
				for (num2 = 0; i_isdigit(*str); str++)
					num2 = num2*10 + (*str-'0');
				if (*str == '\0') return start;

				if (num == 38) {
					flags &= ~GUI_PRINT_FLAG_COLOR_24_FG;
					fg = num2;
				} else if (num == 48) {
					flags &= ~GUI_PRINT_FLAG_COLOR_24_BG;
					bg = num2;
				}

				break;
			}
			break;
		default:
			if (num >= 30 && num <= 37) {
				flags &= ~GUI_PRINT_FLAG_COLOR_24_FG;
				fg = ansitab[num-30];
			} else if (num >= 40 && num <= 47) {
				flags &= ~GUI_PRINT_FLAG_COLOR_24_BG;
				bg = ansitab[num-40];
			} else if (num >= 90 && num <= 97) {
				flags &= ~GUI_PRINT_FLAG_COLOR_24_FG;
				fg = 8 + ansitab[num-90];
			} else if (num >= 100 && num <= 107) {
				flags &= ~GUI_PRINT_FLAG_COLOR_24_BG;
				bg = 8 + ansitab[num-100];
			}
			break;
		}
		num = 0;

		if (*str == 'm') {
			if (fg_ret != NULL) *fg_ret = fg;
			if (bg_ret != NULL) *bg_ret = bg;
			if (flags_ret != NULL) *flags_ret = flags;

			str++;
			break;
		}
	}

	return str;
}


static void event_own_unaway(SERVER_REC *server, const char *data)
{
	char *params, *nick;

	g_return_if_fail(data != NULL);

	/* set user's gone flag.. */
	params = event_get_params(data, 2, &nick, NULL);
	nicklist_update_flags(server, nick, FALSE, -1);
	g_free(params);
}


extract_resources_callback (WinLibrary *fi, WinResource *wr,
                            WinResource *type_wr, WinResource *name_wr,
                            WinResource *lang_wr)
{
	int size;
	bool free_it;
	void *memory;
	char *outname;
	FILE *out;

	memory = extract_resource(fi, wr, &size, &free_it, type_wr->id, (lang_wr == NULL ? NULL : lang_wr->id), arg_raw);
	free_it = false;
	if (memory == NULL) {
		/* extract resource has printed error */
		return;
	}

	/* determine where to extract to */
	outname = get_destination_name(fi, type_wr->id, name_wr->id, (lang_wr == NULL ? NULL : lang_wr->id));
	if (outname == NULL) {
		out = stdout;
	} else {
		out = fopen(outname, "wb");
		if (out == NULL) {
			warn_errno("%s", outname);
			goto cleanup;
		}
	}

	/* write the actual data */
	fwrite(memory, size, 1, out);

	cleanup:
	if (free_it)
		free(memory);
	if (out != NULL && out != stdout)
		fclose(out);
}


pending_exception_check1(thr_crit_bup, ptr)
    int thr_crit_bup;
    struct tcltkip *ptr;
{
    volatile VALUE exc = rbtk_pending_exception;

    if (!NIL_P(exc) && rb_obj_is_kind_of(exc, rb_eException)) {
        DUMP1("find a pending exception");

        if (rbtk_eventloop_depth > 0
	    || rbtk_internal_eventloop_handler > 0
	    ) {
            return 1; /* pending */
        } else {
            rbtk_pending_exception = Qnil;

            if (ptr != (struct tcltkip *)NULL) {
                /* Tcl_Release(ptr->ip); */
                rbtk_release_ip(ptr);
            }

            rb_thread_critical = thr_crit_bup;

            if (rb_obj_is_kind_of(exc, eTkCallbackRetry)) {
                DUMP1("pending_exception_check1: call rb_jump_tag(retry)");
                rb_jump_tag(TAG_RETRY);
            } else if (rb_obj_is_kind_of(exc, eTkCallbackRedo)) {
                DUMP1("pending_exception_check1: call rb_jump_tag(redo)");
                rb_jump_tag(TAG_REDO);
            } else if (rb_obj_is_kind_of(exc, eTkCallbackThrow)) {
                DUMP1("pending_exception_check1: call rb_jump_tag(throw)");
                rb_jump_tag(TAG_THROW);
            }
            rb_exc_raise(exc);
        }
    } else {
        return 0;
    }

    UNREACHABLE;
}


lib_do_one_event(argc, argv, self)
    int   argc;
    VALUE *argv;
    VALUE self;
{
    return lib_do_one_event_core(argc, argv, self, 0);
}


Tcl_PushCallFrame(interp, framePtr, nsPtr, isProcCallFrame)
    Tcl_Interp *interp;
    Tcl_CallFrame *framePtr;
    Tcl_Namespace *nsPtr;
    int isProcCallFrame;
{
    DummyInterp *iPtr = (DummyInterp*)interp;
    CallFrame *frame = (CallFrame *)framePtr;

    /* **** DUMMY **** */
    Tcl_InitHashTable(&frame.varTable, TCL_STRING_KEYS);
    if (iPtr->varFramePtr != NULL) {
        frame.level = iPtr->varFramePtr->level + 1;
    } else {
        frame.level = 1;
    }
    frame.callerPtr = iPtr->framePtr;
    frame.callerVarPtr = iPtr->varFramePtr;
    iPtr->framePtr = &frame;
    iPtr->varFramePtr = &frame;

    return TCL_OK;
}


rubytk_kitpath_init(Tcl_Interp *interp)
{
    Tcl_CreateObjCommand(interp, "::tcl::kitpath", rubytk_kitpathObjCmd, 0, 0);
    if (Tcl_LinkVar(interp, "::tcl::kitpath", (char *) &rubytk_kitpath,
		TCL_LINK_STRING | TCL_LINK_READ_ONLY) != TCL_OK) {
	Tcl_ResetResult(interp);
    }

    Tcl_CreateObjCommand(interp, "::tcl::rubytk_kitpath", rubytk_kitpathObjCmd, 0, 0);
    if (Tcl_LinkVar(interp, "::tcl::rubytk_kitpath", (char *) &rubytk_kitpath,
		TCL_LINK_STRING | TCL_LINK_READ_ONLY) != TCL_OK) {
	Tcl_ResetResult(interp);
    }

    if (rubytk_kitpath == NULL) {
	/*
	 * XXX: We may want to avoid doing this to allow tcl::kitpath calls
	 * XXX: to obtain changes in nameofexe, if they occur.
	 */
	set_rubytk_kitpath(Tcl_GetNameOfExecutable());
    }

    return Tcl_PkgProvide(interp, "rubytk_kitpath", "1.0");
}


ip_evloop_abort_on_exc_set(self, val)
    VALUE self, val;
{
    struct tcltkip *ptr = get_ip(self);

    /* ip is deleted? */
    if (deleted_ip(ptr)) {
        return lib_evloop_abort_on_exc(self);
    }

    if (Tcl_GetMaster(ptr->ip) != (Tcl_Interp*)NULL) {
        /* slave IP */
        return lib_evloop_abort_on_exc(self);
    }
    return lib_evloop_abort_on_exc_set(self, val);
}


    if (rb_obj_is_kind_of(ret, rb_eException)) {
        DUMP1("raise exception");
        /* rb_exc_raise(ret); */
	rb_exc_raise(rb_exc_new3(rb_obj_class(ret),
				 rb_funcallv(ret, ID_to_s, 0, 0)));
    }


ip_ruby_cmd_receiver_get(str)
     char *str;
{
  volatile VALUE receiver;
  int state;

  if (str[0] == ':' || ('A' <= str[0] && str[0] <= 'Z')) {
    /* class | module | constant */
    receiver = rb_protect(ip_ruby_cmd_receiver_const_get, (VALUE)str, &state);
    if (state) return Qnil;
  } else if (str[0] == '$') {
    /* global variable */
    receiver = rb_gv_get(str);
  } else {
    /* global variable omitted '$' */
    char *buf;
    size_t len;

    len = strlen(str);
    buf = ALLOC_N(char, len + 2);
    /* buf = ckalloc(sizeof(char) * (len + 2)); */
    buf[0] = '$';
    memcpy(buf + 1, str, len);
    buf[len + 1] = 0;
    receiver = rb_gv_get(buf);
    xfree(buf);
    /* ckfree(buf); */
  }

  return receiver;
}


ip_get_result_string_obj(interp)
    Tcl_Interp *interp;
{
#if TCL_MAJOR_VERSION >= 8
    Tcl_Obj *retObj;
    volatile VALUE strval;

    retObj = Tcl_GetObjResult(interp);
    Tcl_IncrRefCount(retObj);
    strval = get_str_from_obj(retObj);
    RbTk_OBJ_UNTRUST(strval);
    Tcl_ResetResult(interp);
    Tcl_DecrRefCount(retObj);
    return strval;
#else
    return rb_tainted_str_new2(interp->result);
#endif
}


call_tclkit_init_script(Tcl_Interp  *interp)
{
#if 0
  /* Currently, do nothing in this function.
     It's a memo (quoted from kitInit.c of Tclkit)
     to support an initScript for Tcl interpreters in the future. */
  if (Tcl_EvalEx(interp, initScript, -1, TCL_EVAL_GLOBAL) == TCL_OK) {
    const char *encoding = NULL;
    Tcl_Obj* path = Tcl_GetStartupScript(&encoding);
    Tcl_SetStartupScript(Tcl_GetObjResult(interp), encoding);
    if (path == NULL) {
      Tcl_Eval(interp, "incr argc -1; set argv [lrange $argv 1 end]");
    }
  }
#endif

  return 1;
}


tcltkip_init_tk(interp)
    VALUE interp;
{
    struct tcltkip *ptr = get_ip(interp);

#if TCL_MAJOR_VERSION >= 8
    int  st;

    if (Tcl_IsSafe(ptr->ip)) {
        DUMP1("Tk_SafeInit");
        st = ruby_tk_stubs_safeinit(ptr->ip);
        switch(st) {
        case TCLTK_STUBS_OK:
            break;
        case NO_Tk_Init:
            return rb_exc_new2(rb_eLoadError,
                               "tcltklib: can't find Tk_SafeInit()");
        case FAIL_Tk_Init:
            return create_ip_exc(interp, rb_eRuntimeError,
                                 "tcltklib: fail to Tk_SafeInit(). %s",
                                 Tcl_GetStringResult(ptr->ip));
        case FAIL_Tk_InitStubs:
            return create_ip_exc(interp, rb_eRuntimeError,
                                 "tcltklib: fail to Tk_InitStubs(). %s",
                                 Tcl_GetStringResult(ptr->ip));
        default:
            return create_ip_exc(interp, rb_eRuntimeError,
                                 "tcltklib: unknown error(%d) on ruby_tk_stubs_safeinit", st);
        }
    } else {
        DUMP1("Tk_Init");
        st = ruby_tk_stubs_init(ptr->ip);
        switch(st) {
        case TCLTK_STUBS_OK:
            break;
        case NO_Tk_Init:
            return rb_exc_new2(rb_eLoadError,
                               "tcltklib: can't find Tk_Init()");
        case FAIL_Tk_Init:
            return create_ip_exc(interp, rb_eRuntimeError,
                                 "tcltklib: fail to Tk_Init(). %s",
                                 Tcl_GetStringResult(ptr->ip));
        case FAIL_Tk_InitStubs:
            return create_ip_exc(interp, rb_eRuntimeError,
                                 "tcltklib: fail to Tk_InitStubs(). %s",
                                 Tcl_GetStringResult(ptr->ip));
        default:
            return create_ip_exc(interp, rb_eRuntimeError,
                                 "tcltklib: unknown error(%d) on ruby_tk_stubs_init", st);
        }
    }

#else /* TCL_MAJOR_VERSION < 8 */
    DUMP1("Tk_Init");
    if (ruby_tk_stubs_init(ptr->ip) != TCLTK_STUBS_OK) {
        return rb_exc_new2(rb_eRuntimeError, ptr->ip->result);
    }
#endif

#ifdef RUBY_USE_NATIVE_THREAD
    ptr->tk_thread_id = Tcl_GetCurrentThread();
#endif

    return Qnil;
}


lib_split_tklist(self, list_str)
    VALUE self;
    VALUE list_str;
{
    return lib_split_tklist_core(Qnil, list_str);
}


get_thread_alone_check_flag(void)
{
#ifdef RUBY_USE_NATIVE_THREAD
  return 0;
#else
  set_tcltk_version();

  if (tcltk_version.major < 8) {
    /* Tcl/Tk 7.x */
    return 1;
  } else if (tcltk_version.major == 8) {
    if (tcltk_version.minor < 5) {
      /* Tcl/Tk 8.0 - 8.4 */
      return 1;
    } else if (tcltk_version.minor == 5) {
      if (tcltk_version.type < TCL_FINAL_RELEASE) {
	/* Tcl/Tk 8.5a? - 8.5b? */
	return 1;
      } else {
	/* Tcl/Tk 8.5.x */
	return 0;
      }
    } else {
      /* Tcl/Tk 8.6 - 8.9 ?? */
      return 0;
    }
  } else {
    /* Tcl/Tk 9+ ?? */
    return 0;
  }
#endif
}


ip_get_variable2(self, varname, index, flag)
    VALUE self;
    VALUE varname;
    VALUE index;
    VALUE flag;
{
    VALUE argv[3];
    VALUE retval;

    StringValue(varname);
    if (!NIL_P(index)) StringValue(index);

    argv[0] = varname;
    argv[1] = index;
    argv[2] = flag;

    retval = tk_funcall(ip_get_variable2_core, 3, argv, self);

    if (NIL_P(retval)) {
        return rb_tainted_str_new2("");
    } else {
        return retval;
    }
}


ip_create_console_core(interp, argc, argv)
    VALUE interp;
    int   argc;   /* dummy */
    VALUE *argv;  /* dummy */
{
    struct tcltkip *ptr = get_ip(interp);

    if (!tk_stubs_init_p()) {
        tcltkip_init_tk(interp);
    }

    if (Tcl_GetVar(ptr->ip,"tcl_interactive",TCL_GLOBAL_ONLY) == (char*)NULL) {
        Tcl_SetVar(ptr->ip, "tcl_interactive", "0", TCL_GLOBAL_ONLY);
    }

#if TCL_MAJOR_VERSION > 8 \
    || (TCL_MAJOR_VERSION == 8 \
        && (TCL_MINOR_VERSION > 1 \
            || (TCL_MINOR_VERSION == 1 \
                 && TCL_RELEASE_LEVEL == TCL_FINAL_RELEASE \
                 && TCL_RELEASE_SERIAL >= 1) ) )
    Tk_InitConsoleChannels(ptr->ip);

    if (Tk_CreateConsoleWindow(ptr->ip) != TCL_OK) {
        rb_raise(rb_eRuntimeError, "fail to create console-window");
    }
#else
#if defined(MAC_TCL) || defined(__WIN32__)
#if TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION == 1 \
    && ( (TCL_RELEASE_LEVEL == TCL_FINAL_RELEASE && TCL_RELEASE_SERIAL == 0) \
        || (TCL_RELEASE_LEVEL == TCL_BETA_RELEASE && TCL_RELEASE_SERIAL >= 2) )
    TkConsoleCreate_();
#else
    TkConsoleCreate();
#endif

    if (TkConsoleInit(ptr->ip) != TCL_OK) {
        rb_raise(rb_eRuntimeError, "fail to create console-window");
    }
#else
    rb_notimplement();
#endif
#endif

    return interp;
}


lib_getversion(self)
    VALUE self;
{
    set_tcltk_version();

    return rb_ary_new3(4, INT2NUM(tcltk_version.major),
		          INT2NUM(tcltk_version.minor),
		          INT2NUM(tcltk_version.type),
		          INT2NUM(tcltk_version.patchlevel));
}


rb_threadUpdateProc(clientData)
    ClientData clientData;      /* Pointer to integer to set to 1. */
{
    struct th_update_param *param = (struct th_update_param *) clientData;

    DUMP1("threadUpdateProc is called");
    param->done = 1;
    rb_thread_wakeup(param->thread);

    return;
}


eventloop_sleep(dummy)
    VALUE dummy;
#endif
{
    struct timeval t;

    if (no_event_wait <= 0) {
      return Qnil;
    }

    t.tv_sec = 0;
    t.tv_usec = (int)(no_event_wait*1000.0);

#ifdef HAVE_NATIVETHREAD
#ifndef RUBY_USE_NATIVE_THREAD
    if (!ruby_native_thread_p()) {
        rb_bug("cross-thread violation on eventloop_sleep()");
    }
#endif
#endif

    DUMP2("eventloop_sleep: rb_thread_wait_for() at thread : %"PRIxVALUE, rb_thread_current());
    rb_thread_wait_for(t);
    DUMP2("eventloop_sleep: finish at thread : %"PRIxVALUE, rb_thread_current());

#ifdef HAVE_NATIVETHREAD
#ifndef RUBY_USE_NATIVE_THREAD
    if (!ruby_native_thread_p()) {
        rb_bug("cross-thread violation on eventloop_sleep()");
    }
#endif
#endif

    return Qnil;
}


lib_evloop_thread_p(self)
    VALUE self;
{
    if (NIL_P(eventloop_thread)) {
        return Qnil;    /* no eventloop */
    } else if (rb_thread_current() == eventloop_thread) {
        return Qtrue;   /* is eventloop */
    } else {
        return Qfalse;  /* not eventloop */
    }
}


lib_restart(self)
    VALUE self;
{
    struct tcltkip *ptr = get_ip(self);

    tcl_stubs_check();

    /* ip is deleted? */
    if (deleted_ip(ptr)) {
        rb_raise(rb_eRuntimeError, "interpreter is deleted");
    }

    return tk_funcall(lib_restart_core, 0, (VALUE*)NULL, self);
}


ip_InterpExitCommand(clientData, interp, argc, argv)
    ClientData clientData;
    Tcl_Interp *interp;
    int argc;
    char *argv[];
#endif
#endif
{
    DUMP1("start ip_InterpExitCommand");
    if (interp != (Tcl_Interp*)NULL
        && !Tcl_InterpDeleted(interp)
#if TCL_NAMESPACE_DEBUG
        && !ip_null_namespace(interp)
#endif
        ) {
        Tcl_ResetResult(interp);
        /* Tcl_Preserve(interp); */
        /* Tcl_Eval(interp, "interp eval {} {destroy .}; interp delete {}"); */
	if (!Tcl_InterpDeleted(interp)) {
	  ip_finalize(interp);

	  Tcl_DeleteInterp(interp);
	  Tcl_Release(interp);
	}
    }
    return TCL_OK;
}


lib_UTF_backslash(self, str)
    VALUE self;
    VALUE str;
{
    return lib_UTF_backslash_core(self, str, 0);
}


get_ip(self)
    VALUE self;
{
    struct tcltkip *ptr;

    TypedData_Get_Struct(self, struct tcltkip, &tcltkip_type, ptr);
    if (ptr == 0) {
        /* rb_raise(rb_eTypeError, "uninitialized TclTkIp"); */
        return((struct tcltkip *)NULL);
    }
    if (ptr->ip == (Tcl_Interp*)NULL) {
        /* rb_raise(rb_eRuntimeError, "deleted IP"); */
        return((struct tcltkip *)NULL);
    }
    return ptr;
}


ip_rb_threadTkWaitCommand(clientData, interp, objc, objv)
    ClientData clientData;
    Tcl_Interp *interp;
    int objc;
    char *objv[];
#endif
{
    struct th_vwait_param *param;
    Tk_Window tkwin = (Tk_Window) clientData;
    Tk_Window window;
    int index;
    static CONST char *optionStrings[] = { "variable", "visibility", "window",
                                           (char *) NULL };
    enum options { TKWAIT_VARIABLE, TKWAIT_VISIBILITY, TKWAIT_WINDOW };
    char *nameString;
    int ret, dummy;
    int thr_crit_bup;
    volatile VALUE current_thread = rb_thread_current();
    struct timeval t;

    DUMP1("Ruby's 'thread_tkwait' is called");
    if (interp == (Tcl_Interp*)NULL) {
        rbtk_pending_exception = rb_exc_new2(rb_eRuntimeError,
                                             "IP is deleted");
        return TCL_ERROR;
    }

    if (rb_thread_alone() || eventloop_thread == current_thread) {
#if TCL_MAJOR_VERSION >= 8
        DUMP1("call ip_rbTkWaitObjCmd");
        DUMP2("eventloop_thread %"PRIxVALUE, eventloop_thread);
        DUMP2("current_thread %"PRIxVALUE, current_thread);
        return ip_rbTkWaitObjCmd(clientData, interp, objc, objv);
#else /* TCL_MAJOR_VERSION < 8 */
        DUMP1("call rb_VwaitCommand");
        return ip_rbTkWaitCommand(clientData, interp, objc, objv);
#endif
    }

    Tcl_Preserve(interp);
    Tcl_Preserve(tkwin);

    Tcl_ResetResult(interp);

    if (objc != 3) {
#ifdef Tcl_WrongNumArgs
        Tcl_WrongNumArgs(interp, 1, objv, "variable|visibility|window name");
#else
        thr_crit_bup = rb_thread_critical;
        rb_thread_critical = Qtrue;

#if TCL_MAJOR_VERSION >= 8
        Tcl_AppendResult(interp, "wrong number of arguments: should be \"",
                         Tcl_GetStringFromObj(objv[0], &dummy),
                         " variable|visibility|window name\"",
                         (char *) NULL);
#else /* TCL_MAJOR_VERSION < 8 */
        Tcl_AppendResult(interp, "wrong number of arguments: should be \"",
                         objv[0], " variable|visibility|window name\"",
                         (char *) NULL);
#endif

        rb_thread_critical = thr_crit_bup;
#endif

        Tcl_Release(tkwin);
        Tcl_Release(interp);
        return TCL_ERROR;
    }

#if TCL_MAJOR_VERSION >= 8
    thr_crit_bup = rb_thread_critical;
    rb_thread_critical = Qtrue;
    /*
    if (Tcl_GetIndexFromObj(interp, objv[1],
                            (CONST84 char **)optionStrings,
                            "option", 0, &index) != TCL_OK) {
        return TCL_ERROR;
    }
    */
    ret = Tcl_GetIndexFromObj(interp, objv[1],
                              (CONST84 char **)optionStrings,
                              "option", 0, &index);

    rb_thread_critical = thr_crit_bup;

    if (ret != TCL_OK) {
        Tcl_Release(tkwin);
        Tcl_Release(interp);
        return TCL_ERROR;
    }
#else /* TCL_MAJOR_VERSION < 8 */
    {
        int c = objv[1][0];
        size_t length = strlen(objv[1]);

        if ((c == 'v') && (strncmp(objv[1], "variable", length) == 0)
            && (length >= 2)) {
            index = TKWAIT_VARIABLE;
        } else if ((c == 'v') && (strncmp(objv[1], "visibility", length) == 0)
                   && (length >= 2)) {
            index = TKWAIT_VISIBILITY;
        } else if ((c == 'w') && (strncmp(objv[1], "window", length) == 0)) {
            index = TKWAIT_WINDOW;
        } else {
            Tcl_AppendResult(interp, "bad option \"", objv[1],
                             "\": must be variable, visibility, or window",
                             (char *) NULL);
            Tcl_Release(tkwin);
            Tcl_Release(interp);
            return TCL_ERROR;
        }
    }
#endif

    thr_crit_bup = rb_thread_critical;
    rb_thread_critical = Qtrue;

#if TCL_MAJOR_VERSION >= 8
    Tcl_IncrRefCount(objv[2]);
    /* nameString = Tcl_GetString(objv[2]); */
    nameString = Tcl_GetStringFromObj(objv[2], &dummy);
#else /* TCL_MAJOR_VERSION < 8 */
    nameString = objv[2];
#endif

    /* param = (struct th_vwait_param *)Tcl_Alloc(sizeof(struct th_vwait_param)); */
    param = RbTk_ALLOC_N(struct th_vwait_param, 1);
#if 1 /* use Tcl_Preserve/Release */
    Tcl_Preserve((ClientData)param);
#endif
    param->thread = current_thread;
    param->done = 0;

    rb_thread_critical = thr_crit_bup;

    switch ((enum options) index) {
    case TKWAIT_VARIABLE:
        thr_crit_bup = rb_thread_critical;
        rb_thread_critical = Qtrue;
        /*
        if (Tcl_TraceVar(interp, nameString,
                         TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
                         rb_threadVwaitProc, (ClientData) param) != TCL_OK) {
            return TCL_ERROR;
        }
        */
        ret = Tcl_TraceVar(interp, nameString,
                         TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
                         rb_threadVwaitProc, (ClientData) param);

        rb_thread_critical = thr_crit_bup;

        if (ret != TCL_OK) {
#if 0 /* use Tcl_EventuallyFree */
            Tcl_EventuallyFree((ClientData)param, TCL_DYNAMIC); /* XXXXXXXX */
#else
#if 1 /* use Tcl_Preserve/Release */
            Tcl_Release(param);
#else
            /* Tcl_Free((char *)param); */
            ckfree((char *)param);
#endif
#endif

#if TCL_MAJOR_VERSION >= 8
            Tcl_DecrRefCount(objv[2]);
#endif

            Tcl_Release(tkwin);
            Tcl_Release(interp);
            return TCL_ERROR;
        }

	t.tv_sec  = 0;
	t.tv_usec = (long)((EVENT_HANDLER_TIMEOUT)*1000.0);

        while(!param->done) {
	  /* rb_thread_stop(); */
	  /* rb_thread_sleep_forever(); */
	  rb_thread_wait_for(t);
	  if (NIL_P(eventloop_thread)) {
	    break;
	  }
        }

        thr_crit_bup = rb_thread_critical;
        rb_thread_critical = Qtrue;

        if (param->done > 0) {
            Tcl_UntraceVar(interp, nameString,
                           TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
                           rb_threadVwaitProc, (ClientData) param);
        }

#if TCL_MAJOR_VERSION >= 8
        Tcl_DecrRefCount(objv[2]);
#endif

        rb_thread_critical = thr_crit_bup;

        break;

    case TKWAIT_VISIBILITY:
        thr_crit_bup = rb_thread_critical;
        rb_thread_critical = Qtrue;

#if 0 /* variable 'tkwin' must keep the token of MainWindow */
        if (!tk_stubs_init_p() || Tk_MainWindow(interp) == (Tk_Window)NULL) {
            window = NULL;
        } else {
            window = Tk_NameToWindow(interp, nameString, tkwin);
        }
#else
        if (!tk_stubs_init_p() || tkwin == (Tk_Window)NULL) {
            window = NULL;
	} else {
            /* Tk_NameToWindow() returns right token on non-eventloop thread */
            Tcl_CmdInfo info;
            if (Tcl_GetCommandInfo(interp, ".", &info)) { /* check root */
                window = Tk_NameToWindow(interp, nameString, tkwin);
            } else {
                window = NULL;
            }
	}
#endif

        if (window == NULL) {
            Tcl_AppendResult(interp, ": thread_tkwait: ",
                             "no main-window (not Tk application?)",
                             (char*)NULL);

            rb_thread_critical = thr_crit_bup;

#if 0 /* use Tcl_EventuallyFree */
	    Tcl_EventuallyFree((ClientData)param, TCL_DYNAMIC); /* XXXXXXXX */
#else
#if 1 /* use Tcl_Preserve/Release */
            Tcl_Release(param);
#else
            /* Tcl_Free((char *)param); */
            ckfree((char *)param);
#endif
#endif

#if TCL_MAJOR_VERSION >= 8
            Tcl_DecrRefCount(objv[2]);
#endif
            Tcl_Release(tkwin);
            Tcl_Release(interp);
            return TCL_ERROR;
        }
        Tcl_Preserve(window);

        Tk_CreateEventHandler(window,
                              VisibilityChangeMask|StructureNotifyMask,
                              rb_threadWaitVisibilityProc, (ClientData) param);

        rb_thread_critical = thr_crit_bup;

	t.tv_sec  = 0;
	t.tv_usec = (long)((EVENT_HANDLER_TIMEOUT)*1000.0);

        while(param->done != TKWAIT_MODE_VISIBILITY) {
	  if (param->done == TKWAIT_MODE_DESTROY) break;
	  /* rb_thread_stop(); */
	  /* rb_thread_sleep_forever(); */
	  rb_thread_wait_for(t);
	  if (NIL_P(eventloop_thread)) {
	    break;
	  }
        }

        thr_crit_bup = rb_thread_critical;
        rb_thread_critical = Qtrue;

        /* when a window is destroyed, no need to call Tk_DeleteEventHandler */
        if (param->done != TKWAIT_MODE_DESTROY) {
            Tk_DeleteEventHandler(window,
                                  VisibilityChangeMask|StructureNotifyMask,
                                  rb_threadWaitVisibilityProc,
                                  (ClientData) param);
        }

        if (param->done != 1) {
            Tcl_ResetResult(interp);
            Tcl_AppendResult(interp, "window \"", nameString,
                             "\" was deleted before its visibility changed",
                             (char *) NULL);

            rb_thread_critical = thr_crit_bup;

            Tcl_Release(window);

#if 0 /* use Tcl_EventuallyFree */
	    Tcl_EventuallyFree((ClientData)param, TCL_DYNAMIC); /* XXXXXXXX */
#else
#if 1 /* use Tcl_Preserve/Release */
            Tcl_Release(param);
#else
            /* Tcl_Free((char *)param); */
            ckfree((char *)param);
#endif
#endif

#if TCL_MAJOR_VERSION >= 8
            Tcl_DecrRefCount(objv[2]);
#endif

            Tcl_Release(tkwin);
            Tcl_Release(interp);
            return TCL_ERROR;
        }

        Tcl_Release(window);

#if TCL_MAJOR_VERSION >= 8
        Tcl_DecrRefCount(objv[2]);
#endif

        rb_thread_critical = thr_crit_bup;

        break;

    case TKWAIT_WINDOW:
        thr_crit_bup = rb_thread_critical;
        rb_thread_critical = Qtrue;

#if 0 /* variable 'tkwin' must keep the token of MainWindow */
        if (!tk_stubs_init_p() || Tk_MainWindow(interp) == (Tk_Window)NULL) {
            window = NULL;
        } else {
            window = Tk_NameToWindow(interp, nameString, tkwin);
        }
#else
        if (!tk_stubs_init_p() || tkwin == (Tk_Window)NULL) {
            window = NULL;
	} else {
            /* Tk_NameToWindow() returns right token on non-eventloop thread */
            Tcl_CmdInfo info;
            if (Tcl_GetCommandInfo(interp, ".", &info)) { /* check root */
                window = Tk_NameToWindow(interp, nameString, tkwin);
            } else {
                window = NULL;
            }
	}
#endif

#if TCL_MAJOR_VERSION >= 8
        Tcl_DecrRefCount(objv[2]);
#endif

        if (window == NULL) {
            Tcl_AppendResult(interp, ": thread_tkwait: ",
                             "no main-window (not Tk application?)",
                             (char*)NULL);

            rb_thread_critical = thr_crit_bup;

#if 0 /* use Tcl_EventuallyFree */
	    Tcl_EventuallyFree((ClientData)param, TCL_DYNAMIC); /* XXXXXXXX */
#else
#if 1 /* use Tcl_Preserve/Release */
            Tcl_Release(param);
#else
            /* Tcl_Free((char *)param); */
            ckfree((char *)param);
#endif
#endif

            Tcl_Release(tkwin);
            Tcl_Release(interp);
            return TCL_ERROR;
        }

        Tcl_Preserve(window);

        Tk_CreateEventHandler(window, StructureNotifyMask,
                              rb_threadWaitWindowProc, (ClientData) param);

        rb_thread_critical = thr_crit_bup;

	t.tv_sec  = 0;
	t.tv_usec = (long)((EVENT_HANDLER_TIMEOUT)*1000.0);

        while(param->done != TKWAIT_MODE_DESTROY) {
	  /* rb_thread_stop(); */
	  /* rb_thread_sleep_forever(); */
	  rb_thread_wait_for(t);
	  if (NIL_P(eventloop_thread)) {
	    break;
	  }
        }

        Tcl_Release(window);

        /* when a window is destroyed, no need to call Tk_DeleteEventHandler
        thr_crit_bup = rb_thread_critical;
        rb_thread_critical = Qtrue;

        Tk_DeleteEventHandler(window, StructureNotifyMask,
                              rb_threadWaitWindowProc, (ClientData) param);

        rb_thread_critical = thr_crit_bup;
        */

        break;
    } /* end of 'switch' statement */

#if 0 /* use Tcl_EventuallyFree */
    Tcl_EventuallyFree((ClientData)param, TCL_DYNAMIC); /* XXXXXXXX */
#else
#if 1 /* use Tcl_Preserve/Release */
    Tcl_Release((ClientData)param);
#else
    /* Tcl_Free((char *)param); */
    ckfree((char *)param);
#endif
#endif

    /*
     * Clear out the interpreter's result, since it may have been set
     * by event handlers.
     */

    Tcl_ResetResult(interp);

    Tcl_Release(tkwin);
    Tcl_Release(interp);
    return TCL_OK;
}


    if (status) {
        char *buf;
        VALUE old_gc;
        volatile VALUE type, str;

        old_gc = rb_gc_disable();

        switch(status) {
        case TAG_RETURN:
            type = eTkCallbackReturn;
            goto error;
        case TAG_BREAK:
            type = eTkCallbackBreak;
            goto error;
        case TAG_NEXT:
            type = eTkCallbackContinue;
            goto error;
        error:
            str = rb_str_new2("LocalJumpError: ");
            rb_str_append(str, rb_obj_as_string(rb_errinfo()));
            exc = rb_exc_new3(type, str);
            break;

        case TAG_RETRY:
            if (NIL_P(rb_errinfo())) {
                DUMP1("rb_protect: retry");
                exc = rb_exc_new2(eTkCallbackRetry, "retry jump error");
            } else {
                exc = rb_errinfo();
            }
            break;

        case TAG_REDO:
            if (NIL_P(rb_errinfo())) {
                DUMP1("rb_protect: redo");
                exc = rb_exc_new2(eTkCallbackRedo,  "redo jump error");
            } else {
                exc = rb_errinfo();
            }
            break;

        case TAG_RAISE:
            if (NIL_P(rb_errinfo())) {
                exc = rb_exc_new2(rb_eException, "unknown exception");
            } else {
                exc = rb_errinfo();
            }
            break;

        case TAG_FATAL:
            if (NIL_P(rb_errinfo())) {
                exc = rb_exc_new2(rb_eFatal, "FATAL");
            } else {
                exc = rb_errinfo();
            }
            break;

        case TAG_THROW:
            if (NIL_P(rb_errinfo())) {
                DUMP1("rb_protect: throw");
                exc = rb_exc_new2(eTkCallbackThrow,  "throw jump error");
            } else {
                exc = rb_errinfo();
            }
            break;

        default:
            buf = ALLOC_N(char, 256);
            /* buf = ckalloc(sizeof(char) * 256); */
            sprintf(buf, "unknown loncaljmp status %d", status);
            exc = rb_exc_new2(rb_eException, buf);
            xfree(buf);
            /* ckfree(buf); */
            break;
        }

        if (old_gc == Qfalse) rb_gc_enable();

        ret = Qnil;
    }


    if (argv) {
        /* VALUE *temp = ALLOC_N(VALUE, argc); */
        VALUE *temp = RbTk_ALLOC_N(VALUE, argc);
#if 0 /* use Tcl_Preserve/Release */
	Tcl_Preserve((ClientData)temp); /* XXXXXXXX */
#endif
        MEMCPY(temp, argv, VALUE, argc);
        argv = temp;
    }


ip_invoke_real(argc, argv, interp)
    int argc;
    VALUE *argv;
    VALUE interp;
{
    VALUE v;
    struct tcltkip *ptr;        /* tcltkip data struct */

#if TCL_MAJOR_VERSION >= 8
    Tcl_Obj **av = (Tcl_Obj **)NULL;
#else /* TCL_MAJOR_VERSION < 8 */
    char **av = (char **)NULL;
#endif

    DUMP2("invoke_real called by thread:%"PRIxVALUE, rb_thread_current());

    /* get the data struct */
    ptr = get_ip(interp);

    /* ip is deleted? */
    if (deleted_ip(ptr)) {
        return rb_tainted_str_new2("");
    }

    /* allocate memory for arguments */
    av = alloc_invoke_arguments(argc, argv);

    /* Invoke the C procedure */
    Tcl_ResetResult(ptr->ip);
    v = ip_invoke_core(interp, argc, av);

    /* free allocated memory */
    free_invoke_arguments(argc, av);

    return v;
}


ip_rbNamespaceObjCmd(clientData, interp, objc, objv)
    ClientData clientData;
    Tcl_Interp *interp;
    int objc;
    Tcl_Obj *CONST objv[];
{
    Tcl_CmdInfo info;
    int ret;

    DUMP1("call ip_rbNamespaceObjCmd");
    DUMP2("objc = %d", objc);
    DUMP2("objv[0] = '%s'", Tcl_GetString(objv[0]));
    DUMP2("objv[1] = '%s'", Tcl_GetString(objv[1]));
    if (!Tcl_GetCommandInfo(interp, ORIG_NAMESPACE_CMD, &(info))) {
      DUMP1("fail to get "ORIG_NAMESPACE_CMD);
        Tcl_ResetResult(interp);
        Tcl_AppendResult(interp,
                         "invalid command name \"namespace\"", (char*)NULL);
        return TCL_ERROR;
    }

    rbtk_eventloop_depth++;
    DUMP2("namespace wrapper enter depth == %d", rbtk_eventloop_depth);

    if (info.isNativeObjectProc) {
#if TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION < 6
        DUMP1("call a native-object-proc");
        ret = (*(info.objProc))(info.objClientData, interp, objc, objv);
#else
        /* Tcl8.6 or later */
        int i;
        Tcl_Obj **cp_objv;
        char org_ns_cmd_name[] = ORIG_NAMESPACE_CMD;

        DUMP1("call a native-object-proc for tcl8.6 or later");
        cp_objv = RbTk_ALLOC_N(Tcl_Obj *, (objc + 1));

        cp_objv[0] = Tcl_NewStringObj(org_ns_cmd_name, strlen(org_ns_cmd_name));
        for(i = 1; i < objc; i++) {
            cp_objv[i] = objv[i];
        }
        cp_objv[objc] = (Tcl_Obj *)NULL;

        /* ret = Tcl_EvalObjv(interp, objc, cp_objv, TCL_EVAL_DIRECT); */
        ret = Tcl_EvalObjv(interp, objc, cp_objv, 0);

        ckfree((char*)cp_objv);
#endif
    } else {
        /* string interface */
        int i;
        char **argv;

        DUMP1("call with the string-interface");
        /* argv = (char **)Tcl_Alloc(sizeof(char *) * (objc + 1)); */
        argv = RbTk_ALLOC_N(char *, (objc + 1));
#if 0 /* use Tcl_Preserve/Release */
	Tcl_Preserve((ClientData)argv); /* XXXXXXXX */
#endif

        for(i = 0; i < objc; i++) {
            /* argv[i] = Tcl_GetString(objv[i]); */
            argv[i] = Tcl_GetStringFromObj(objv[i], (int*)NULL);
        }
        argv[objc] = (char *)NULL;

        ret = (*(info.proc))(info.clientData, interp,
                              objc, (CONST84 char **)argv);

#if 0 /* use Tcl_EventuallyFree */
	Tcl_EventuallyFree((ClientData)argv, TCL_DYNAMIC); /* XXXXXXXX */
#else
#if 0 /* use Tcl_Preserve/Release */
	Tcl_Release((ClientData)argv); /* XXXXXXXX */
#else
        /* Tcl_Free((char*)argv); */
        ckfree((char*)argv);
#endif
#endif
    }

    DUMP2("namespace wrapper exit depth == %d", rbtk_eventloop_depth);
    rbtk_eventloop_depth--;

    DUMP1("end of ip_rbNamespaceObjCmd");
    return ret;
}


ip_make_menu_embeddable(interp, menu_path)
    VALUE interp;
    VALUE menu_path;
{
    VALUE argv[1];

    argv[0] = menu_path;
    return tk_funcall(ip_make_menu_embeddable_core, 1, argv, interp);
}


ivq_safelevel_handler(arg, ivq)
    VALUE arg;
    VALUE ivq;
{
    struct invoke_queue *q;

    Data_Get_Struct(ivq, struct invoke_queue, q);
    DUMP2("(safe-level handler) $SAFE = %d", q->safe_level);
    rb_set_safe_level(q->safe_level);
    return ip_invoke_core(q->interp, q->argc, q->argv);
}


ip_invoke_with_position(argc, argv, obj, position)
    int argc;
    VALUE *argv;
    VALUE obj;
    Tcl_QueuePosition position;
{
    struct invoke_queue *ivq;
#ifdef RUBY_USE_NATIVE_THREAD
    struct tcltkip *ptr;
#endif
    int  *alloc_done;
    int  thr_crit_bup;
    volatile VALUE current = rb_thread_current();
    volatile VALUE ip_obj = obj;
    volatile VALUE result;
    volatile VALUE ret;
    struct timeval t;

#if TCL_MAJOR_VERSION >= 8
    Tcl_Obj **av = (Tcl_Obj **)NULL;
#else /* TCL_MAJOR_VERSION < 8 */
    char **av = (char **)NULL;
#endif

    if (argc < 1) {
        rb_raise(rb_eArgError, "command name missing");
    }

#ifdef RUBY_USE_NATIVE_THREAD
    ptr = get_ip(ip_obj);
    DUMP2("invoke status: ptr->tk_thread_id %p", ptr->tk_thread_id);
    DUMP2("invoke status: Tcl_GetCurrentThread %p", Tcl_GetCurrentThread());
#else
    DUMP2("status: Tcl_GetCurrentThread %p", Tcl_GetCurrentThread());
#endif
    DUMP2("status: eventloopt_thread %"PRIxVALUE, eventloop_thread);

    if (
#ifdef RUBY_USE_NATIVE_THREAD
	(ptr->tk_thread_id == 0 || ptr->tk_thread_id == Tcl_GetCurrentThread())
	&&
#endif
	(NIL_P(eventloop_thread) || current == eventloop_thread)
	) {
        if (NIL_P(eventloop_thread)) {
            DUMP2("invoke from thread:%"PRIxVALUE" but no eventloop", current);
        } else {
            DUMP2("invoke from current eventloop %"PRIxVALUE, current);
        }
        result = ip_invoke_real(argc, argv, ip_obj);
        if (rb_obj_is_kind_of(result, rb_eException)) {
            rb_exc_raise(result);
        }
        return result;
    }

    DUMP2("invoke from thread %"PRIxVALUE" (NOT current eventloop)", current);

    thr_crit_bup = rb_thread_critical;
    rb_thread_critical = Qtrue;

    /* allocate memory (for arguments) */
    av = alloc_invoke_arguments(argc, argv);

    /* allocate memory (keep result) */
    /* alloc_done = (int*)ALLOC(int); */
    alloc_done = RbTk_ALLOC_N(int, 1);
#if 0 /* use Tcl_Preserve/Release */
    Tcl_Preserve((ClientData)alloc_done); /* XXXXXXXX */
#endif
    *alloc_done = 0;

    /* allocate memory (freed by Tcl_ServiceEvent) */
    /* ivq = (struct invoke_queue *)Tcl_Alloc(sizeof(struct invoke_queue)); */
    ivq = RbTk_ALLOC_N(struct invoke_queue, 1);
#if 0 /* use Tcl_Preserve/Release */
    Tcl_Preserve((ClientData)ivq); /* XXXXXXXX */
#endif

    /* allocate result obj */
    result = rb_ary_new3(1, Qnil);

    /* construct event data */
    ivq->done = alloc_done;
    ivq->argc = argc;
    ivq->argv = av;
    ivq->interp = ip_obj;
    ivq->result = result;
    ivq->thread = current;
    ivq->safe_level = rb_safe_level();
    ivq->ev.proc = invoke_queue_handler;

    /* add the handler to Tcl event queue */
    DUMP1("add handler");
#ifdef RUBY_USE_NATIVE_THREAD
    if (ptr->tk_thread_id) {
      /* Tcl_ThreadQueueEvent(ptr->tk_thread_id, &(ivq->ev), position); */
      Tcl_ThreadQueueEvent(ptr->tk_thread_id, (Tcl_Event*)ivq, position);
      Tcl_ThreadAlert(ptr->tk_thread_id);
    } else if (tk_eventloop_thread_id) {
      /* Tcl_ThreadQueueEvent(tk_eventloop_thread_id,
			   &(ivq->ev), position); */
      Tcl_ThreadQueueEvent(tk_eventloop_thread_id,
			   (Tcl_Event*)ivq, position);
      Tcl_ThreadAlert(tk_eventloop_thread_id);
    } else {
      /* Tcl_QueueEvent(&(ivq->ev), position); */
      Tcl_QueueEvent((Tcl_Event*)ivq, position);
    }
#else
    /* Tcl_QueueEvent(&(ivq->ev), position); */
    Tcl_QueueEvent((Tcl_Event*)ivq, position);
#endif

    rb_thread_critical = thr_crit_bup;

    /* wait for the handler to be processed */
    t.tv_sec  = 0;
    t.tv_usec = (long)((EVENT_HANDLER_TIMEOUT)*1000.0);

    DUMP2("ivq wait for handler (current thread:%"PRIxVALUE")", current);
    while(*alloc_done >= 0) {
      /* rb_thread_stop(); */
      /* rb_thread_sleep_forever(); */
      rb_thread_wait_for(t);
      DUMP2("*** ivq wakeup (current thread:%"PRIxVALUE")", current);
      DUMP2("***          (eventloop thread:%"PRIxVALUE")", eventloop_thread);
      if (NIL_P(eventloop_thread)) {
	DUMP1("*** ivq lost eventloop thread");
	break;
      }
    }
    DUMP2("back from handler (current thread:%"PRIxVALUE")", current);

    /* get result & free allocated memory */
    ret = RARRAY_PTR(result)[0];
#if 0 /* use Tcl_EventuallyFree */
    Tcl_EventuallyFree((ClientData)alloc_done, TCL_DYNAMIC); /* XXXXXXXX */
#else
#if 0 /* use Tcl_Preserve/Release */
    Tcl_Release((ClientData)alloc_done); /* XXXXXXXX */
#else
    /* free(alloc_done); */
    ckfree((char*)alloc_done);
#endif
#endif

#if 0 /* ivq is freed by Tcl_ServiceEvent */
#if 0 /* use Tcl_EventuallyFree */
    Tcl_EventuallyFree((ClientData)ivq, TCL_DYNAMIC); /* XXXXXXXX */
#else
#if 0 /* use Tcl_Preserve/Release */
    Tcl_Release(ivq);
#else
    ckfree((char*)ivq);
#endif
#endif
#endif

    /* free allocated memory */
    free_invoke_arguments(argc, av);

    /* exception? */
    if (rb_obj_is_kind_of(ret, rb_eException)) {
        DUMP1("raise exception");
        /* rb_exc_raise(ret); */
	rb_exc_raise(rb_exc_new3(rb_obj_class(ret),
				 rb_funcallv(ret, ID_to_s, 0, 0)));
    }

    DUMP1("exit ip_invoke");
    return ret;
}


    } else if (tk_eventloop_thread_id) {
      /* Tcl_ThreadQueueEvent(tk_eventloop_thread_id,
			   &(callq->ev), TCL_QUEUE_HEAD); */
      Tcl_ThreadQueueEvent(tk_eventloop_thread_id,
			   (Tcl_Event*)callq, TCL_QUEUE_HEAD);
      Tcl_ThreadAlert(tk_eventloop_thread_id);
    } else {


ip_null_proc(clientData, interp, argc, argv)
    ClientData clientData;
    Tcl_Interp *interp;
    int argc;
    char *argv[];
#endif
#endif
{
    Tcl_ResetResult(interp);
    return TCL_OK;
}


ip_ruby_cmd(clientData, interp, argc, argv)
    ClientData clientData;
    Tcl_Interp *interp;
    int argc;
    char *argv[];
#endif
{
    volatile VALUE receiver;
    volatile ID method;
    volatile VALUE args;
    char *str;
    int i;
    int  len;
    struct cmd_body_arg *arg;
    int thr_crit_bup;
    VALUE old_gc;
    int code;

    if (interp == (Tcl_Interp*)NULL) {
        rbtk_pending_exception = rb_exc_new2(rb_eRuntimeError,
                                             "IP is deleted");
        return TCL_ERROR;
    }

    if (argc < 3) {
#if 0
        rb_raise(rb_eArgError, "too few arguments");
#else
        Tcl_ResetResult(interp);
        Tcl_AppendResult(interp, "too few arguments", (char *)NULL);
        rbtk_pending_exception = rb_exc_new2(rb_eArgError,
                                             Tcl_GetStringResult(interp));
        return TCL_ERROR;
#endif
    }

    /* get arguments from Tcl objects */
    thr_crit_bup = rb_thread_critical;
    rb_thread_critical = Qtrue;
    old_gc = rb_gc_disable();

    /* get receiver */
#if TCL_MAJOR_VERSION >= 8
    str = Tcl_GetStringFromObj(argv[1], &len);
#else /* TCL_MAJOR_VERSION < 8 */
    str = argv[1];
#endif
    DUMP2("receiver:%s",str);
    /* receiver = rb_protect(ip_ruby_cmd_receiver_get, (VALUE)str, &code); */
    receiver = ip_ruby_cmd_receiver_get(str);
    if (NIL_P(receiver)) {
#if 0
        rb_raise(rb_eArgError,
                 "unknown class/module/global-variable '%s'", str);
#else
        Tcl_ResetResult(interp);
        Tcl_AppendResult(interp, "unknown class/module/global-variable '",
                         str, "'", (char *)NULL);
        rbtk_pending_exception = rb_exc_new2(rb_eArgError,
                                             Tcl_GetStringResult(interp));
        if (old_gc == Qfalse) rb_gc_enable();
        return TCL_ERROR;
#endif
    }

    /* get metrhod */
#if TCL_MAJOR_VERSION >= 8
    str = Tcl_GetStringFromObj(argv[2], &len);
#else /* TCL_MAJOR_VERSION < 8 */
    str = argv[2];
#endif
    method = rb_intern(str);

    /* get args */
    args = rb_ary_new2(argc - 2);
    for(i = 3; i < argc; i++) {
        VALUE s;
#if TCL_MAJOR_VERSION >= 8
        str = Tcl_GetStringFromObj(argv[i], &len);
        s = rb_tainted_str_new(str, len);
#else /* TCL_MAJOR_VERSION < 8 */
        str = argv[i];
        s = rb_tainted_str_new2(str);
#endif
        DUMP2("arg:%s",str);
        rb_ary_push(args, s);
    }

    if (old_gc == Qfalse) rb_gc_enable();
    rb_thread_critical = thr_crit_bup;

    /* allocate */
    arg = ALLOC(struct cmd_body_arg);
    /* arg = RbTk_ALLOC_N(struct cmd_body_arg, 1); */

    arg->receiver = receiver;
    arg->method = method;
    arg->args = args;

    /* evaluate the argument string by ruby */
    code = tcl_protect(interp, ip_ruby_cmd_core, (VALUE)arg);

    xfree(arg);
    /* ckfree((char*)arg); */

    return code;
}


invoke_queue_handler(evPtr, flags)
    Tcl_Event *evPtr;
    int flags;
{
    struct invoke_queue *q = (struct invoke_queue *)evPtr;
    volatile VALUE ret;
    volatile VALUE q_dat;
    volatile VALUE thread = q->thread;
    struct tcltkip *ptr;

    DUMP2("do_invoke_queue_handler : evPtr = %p", evPtr);
    DUMP2("invoke queue_thread : %"PRIxVALUE, rb_thread_current());
    DUMP2("added by thread : %"PRIxVALUE, thread);

    if (*(q->done)) {
        DUMP1("processed by another event-loop");
        return 0;
    } else {
        DUMP1("process it on current event-loop");
    }

    if (RTEST(rb_thread_alive_p(thread))
	&& ! RTEST(rb_funcall(thread, ID_stop_p, 0))) {
      DUMP1("caller is not yet ready to receive the result -> pending");
      return 0;
    }

    /* process it */
    *(q->done) = 1;

    /* deleted ipterp ? */
    ptr = get_ip(q->interp);
    if (deleted_ip(ptr)) {
        /* deleted IP --> ignore */
        return 1;
    }

    /* incr internal handler mark */
    rbtk_internal_eventloop_handler++;

    /* check safe-level */
    if (rb_safe_level() != q->safe_level) {
        /* q_dat = Data_Wrap_Struct(rb_cData,0,0,q); */
        q_dat = Data_Wrap_Struct(0,invoke_queue_mark,-1,q);
        ret = rb_funcall(rb_proc_new(ivq_safelevel_handler, q_dat),
                         ID_call, 0);
        rb_gc_force_recycle(q_dat);
	q_dat = (VALUE)NULL;
    } else {
        DUMP2("call invoke_real (for caller thread:%"PRIxVALUE")", thread);
        DUMP2("call invoke_real (current thread:%"PRIxVALUE")", rb_thread_current());
        ret = ip_invoke_core(q->interp, q->argc, q->argv);
    }

    /* set result */
    RARRAY_ASET(q->result, 0, ret);
    ret = (VALUE)NULL;

    /* decr internal handler mark */
    rbtk_internal_eventloop_handler--;

    /* complete */
    *(q->done) = -1;

    /* unlink ruby objects */
    q->interp = (VALUE)NULL;
    q->result = (VALUE)NULL;
    q->thread = (VALUE)NULL;

    /* back to caller */
    if (RTEST(rb_thread_alive_p(thread))) {
      DUMP2("back to caller (caller thread:%"PRIxVALUE")", thread);
      DUMP2("               (current thread:%"PRIxVALUE")", rb_thread_current());
#if CONTROL_BY_STATUS_OF_RB_THREAD_WAITING_FOR_VALUE
      have_rb_thread_waiting_for_value = 1;
      rb_thread_wakeup(thread);
#else
      rb_thread_run(thread);
#endif
      DUMP1("finish back to caller");
#if DO_THREAD_SCHEDULE_AT_CALLBACK_DONE
      rb_thread_schedule();
#endif
    } else {
      DUMP2("caller is dead (caller thread:%"PRIxVALUE")", thread);
      DUMP2("               (current thread:%"PRIxVALUE")", rb_thread_current());
    }

    /* end of handler : remove it */
    return 1;
}


lib_merge_tklist(argc, argv, obj)
    int argc;
    VALUE *argv;
    VALUE obj;
{
    int  num, len;
    int  *flagPtr;
    char *dst, *result;
    volatile VALUE str;
    int taint_flag = 0;
    int thr_crit_bup;
    VALUE old_gc;

    if (argc == 0) return rb_str_new2("");

    tcl_stubs_check();

    thr_crit_bup = rb_thread_critical;
    rb_thread_critical = Qtrue;
    old_gc = rb_gc_disable();

    /* based on Tcl/Tk's Tcl_Merge() */
    /* flagPtr = ALLOC_N(int, argc); */
    flagPtr = RbTk_ALLOC_N(int, argc);
#if 0 /* use Tcl_Preserve/Release */
    Tcl_Preserve((ClientData)flagPtr); /* XXXXXXXXXX */
#endif

    /* pass 1 */
    len = 1;
    for(num = 0; num < argc; num++) {
        if (OBJ_TAINTED(argv[num])) taint_flag = 1;
        dst = StringValueCStr(argv[num]);
#if TCL_MAJOR_VERSION >= 8
        len += Tcl_ScanCountedElement(dst, RSTRING_LENINT(argv[num]),
                                      &flagPtr[num]) + 1;
#else /* TCL_MAJOR_VERSION < 8 */
        len += Tcl_ScanElement(dst, &flagPtr[num]) + 1;
#endif
    }

    /* pass 2 */
    /* result = (char *)Tcl_Alloc(len); */
    result = (char *)ckalloc(len);
#if 0 /* use Tcl_Preserve/Release */
    Tcl_Preserve((ClientData)result);
#endif
    dst = result;
    for(num = 0; num < argc; num++) {
#if TCL_MAJOR_VERSION >= 8
        len = Tcl_ConvertCountedElement(RSTRING_PTR(argv[num]),
                                        RSTRING_LENINT(argv[num]),
                                        dst, flagPtr[num]);
#else /* TCL_MAJOR_VERSION < 8 */
        len = Tcl_ConvertElement(RSTRING_PTR(argv[num]), dst, flagPtr[num]);
#endif
        dst += len;
        *dst = ' ';
        dst++;
    }
    if (dst == result) {
        *dst = 0;
    } else {
        dst[-1] = 0;
    }

#if 0 /* use Tcl_EventuallyFree */
    Tcl_EventuallyFree((ClientData)flagPtr, TCL_DYNAMIC); /* XXXXXXXX */
#else
#if 0 /* use Tcl_Preserve/Release */
    Tcl_Release((ClientData)flagPtr);
#else
    /* free(flagPtr); */
    ckfree((char*)flagPtr);
#endif
#endif

    /* create object */
    str = rb_str_new(result, dst - result - 1);
    if (taint_flag) RbTk_OBJ_UNTRUST(str);
#if 0 /* use Tcl_EventuallyFree */
    Tcl_EventuallyFree((ClientData)result, TCL_DYNAMIC); /* XXXXXXXX */
#else
#if 0 /* use Tcl_Preserve/Release */
    Tcl_Release((ClientData)result); /* XXXXXXXXXXX */
#else
    /* Tcl_Free(result); */
    ckfree(result);
#endif
#endif

    if (old_gc == Qfalse) rb_gc_enable();
    rb_thread_critical = thr_crit_bup;

    return str;
}


static void jsR_callcfunction(js_State *J, int n, int min, js_CFunction F)
{
	int i;
	js_Value v;

	for (i = n; i < min; ++i)
		js_pushundefined(J);

	F(J);
	v = *stackidx(J, -1);
	TOP = --BOT; /* clear stack */
	js_pushvalue(J, v);
}


void js_pushstring(js_State *J, const char *v)
{
	int n = strlen(v);
	CHECKSTACK(1);
	if (n <= soffsetof(js_Value, type)) {
		char *s = STACK[TOP].u.shrstr;
		while (n--) *s++ = *v++;
		*s = 0;
		STACK[TOP].type = JS_TSHRSTR;
	} else {
		STACK[TOP].type = JS_TMEMSTR;
		STACK[TOP].u.memstr = jsV_newmemstring(J, v, n);
	}
	++TOP;
}


int js_isboolean(js_State *J, int idx) { return stackidx(J, idx)->type == JS_TBOOLEAN; }


static void jsR_pushtrace(js_State *J, const char *name, const char *file, int line)
{
	if (J->tracetop + 1 == JS_ENVLIMIT)
		js_error(J, "call stack overflow");
	++J->tracetop;
	J->trace[J->tracetop].name = name;
	J->trace[J->tracetop].file = file;
	J->trace[J->tracetop].line = line;
}


static void js_stackoverflow(js_State *J)
{
	STACK[TOP].type = JS_TLITSTR;
	STACK[TOP].u.litstr = "stack overflow";
	++TOP;
	js_throw(J);
}


void js_replace(js_State* J, int idx)
{
	idx = idx < 0 ? TOP + idx : BOT + idx;
	if (idx < BOT || idx >= TOP)
		js_error(J, "stack error!");
	STACK[idx] = STACK[--TOP];
}


void *js_savetry(js_State *J)
{
	if (J->trytop == JS_TRYLIMIT)
		js_error(J, "try: exception stack overflow");
	J->trybuf[J->trytop].E = J->E;
	J->trybuf[J->trytop].envtop = J->envtop;
	J->trybuf[J->trytop].tracetop = J->tracetop;
	J->trybuf[J->trytop].top = J->top;
	J->trybuf[J->trytop].bot = J->bot;
	J->trybuf[J->trytop].pc = NULL;
	return J->trybuf[J->trytop++].buf;
}


int js_iscallable(js_State *J, int idx)
{
	js_Value *v = stackidx(J, idx);
	if (v->type == JS_TOBJECT)
		return v->u.object->type == JS_CFUNCTION ||
			v->u.object->type == JS_CSCRIPT ||
			v->u.object->type == JS_CCFUNCTION;
	return 0;
}


static js_Value *stackidx(js_State *J, int idx)
{
	static js_Value undefined = { {0}, {0}, JS_TUNDEFINED };
	idx = idx < 0 ? TOP + idx : BOT + idx;
	if (idx < 0 || idx >= TOP)
		return &undefined;
	return STACK + idx;
}


int js_isobject(js_State *J, int idx) { return stackidx(J, idx)->type == JS_TOBJECT; }


const char *js_ref(js_State *J)
{
	js_Value *v = stackidx(J, -1);
	const char *s;
	char buf[32];
	switch (v->type) {
	case JS_TUNDEFINED: s = "_Undefined"; break;
	case JS_TNULL: s = "_Null"; break;
	case JS_TBOOLEAN:
		s = v->u.boolean ? "_True" : "_False";
		break;
	case JS_TOBJECT:
		sprintf(buf, "%p", (void*)v->u.object);
		s = js_intern(J, buf);
		break;
	default:
		sprintf(buf, "%d", J->nextref++);
		s = js_intern(J, buf);
		break;
	}
	js_setregistry(J, s);
	return s;
}


pevent_load (char *filename)
{
	/* AGL, I've changed this file and pevent_save, could you please take a look at
	 *      the changes and possibly modify them to suit you
	 *      //David H
	 */
	char *buf, *ibuf;
	int fd, i = 0, pnt = 0;
	struct stat st;
	char *text = NULL, *snd = NULL;
	int penum = 0;
	char *ofs;

	if (filename == NULL)
		fd = hexchat_open_file ("pevents.conf", O_RDONLY, 0, 0);
	else
		fd = hexchat_open_file (filename, O_RDONLY, 0, XOF_FULLPATH);

	if (fd == -1)
		return 1;
	if (fstat (fd, &st) != 0)
	{
		close (fd);
		return 1;
	}
	ibuf = g_malloc (st.st_size);
	read (fd, ibuf, st.st_size);
	close (fd);

	while (buf_get_line (ibuf, &buf, &pnt, st.st_size))
	{
		if (buf[0] == '#')
			continue;
		if (strlen (buf) == 0)
			continue;

		ofs = strchr (buf, '=');
		if (!ofs)
			continue;
		*ofs = 0;
		ofs++;

		if (strcmp (buf, "event_name") == 0)
		{
			if (penum >= 0)
				pevent_trigger_load (&penum, &text, &snd);
			penum = pevent_find (ofs, &i);
			continue;
		} else if (strcmp (buf, "event_text") == 0)
		{
			g_free (text);
			text = g_strdup (ofs);
			continue;
		}

		continue;
	}

	pevent_trigger_load (&penum, &text, &snd);
	g_free (ibuf);
	return 0;
}


pevent_find (char *name, int *i_i)
{
	int i = *i_i, j;

	j = i + 1;
	while (1)
	{
		if (j == NUM_XP)
			j = 0;
		if (strcmp (te[j].name, name) == 0)
		{
			*i_i = j;
			return j;
		}
		if (j == i)
			return -1;
		j++;
	}
}


scrollback_shrink (session *sess)
{
	char *buf, *p;
	gsize len;
	gint offset, lines = 0;
	const gint max_lines = MIN(prefs.hex_text_max_lines, SCROLLBACK_MAX);

	if (!g_file_load_contents (sess->scrollfile, NULL, &buf, &len, NULL, NULL))
		return;

	/* count all lines */
	p = buf;
	while (p != buf + len)
	{
		if (*p == '\n')
			lines++;
		p++;
	}

	offset = lines - max_lines;

	/* now just go back to where we want to start the file */
	p = buf;
	lines = 0;
	while (p != buf + len)
	{
		if (*p == '\n')
		{
			lines++;
			if (lines == offset)
			{
				p++;
				break;
			}
		}
		p++;
	}

	if (g_file_replace_contents (sess->scrollfile, p, strlen(p), NULL, FALSE,
							G_FILE_CREATE_PRIVATE, NULL, NULL, NULL))
		sess->scrollwritten = lines;

	g_free (buf);
}


log_create_pathname (char *servname, char *channame, char *netname)
{
	char fname[384];
	char fnametime[384];
	time_t now;

	if (!netname)
	{
		netname = g_strdup ("NETWORK");
	}
	else
	{
		netname = log_create_filename (netname);
	}

	/* first, everything is in UTF-8 */
	if (!rfc_casecmp (channame, servname))
	{
		channame = g_strdup ("server");
	}
	else
	{
		channame = log_create_filename (channame);
	}

	servname = log_create_filename (servname);

	log_insert_vars (fname, sizeof (fname), prefs.hex_irc_logmask, channame, netname, servname);
	g_free (channame);
	g_free (netname);
	g_free (servname);

	/* insert time/date */
	now = time (NULL);
	strftime_utf8 (fnametime, sizeof (fnametime), fname, now);

	/* If one uses log mask variables, such as "%c/...", %c will be empty upon
	 * connecting since there's no channel name yet, so we have to make sure
	 * we won't try to write to the FS root. */
	if (g_path_is_absolute (prefs.hex_irc_logmask))
	{
		g_snprintf (fname, sizeof (fname), "%s", fnametime);
	}
	else	/* relative path */
	{
		g_snprintf (fname, sizeof (fname), "%s" G_DIR_SEPARATOR_S "logs" G_DIR_SEPARATOR_S "%s", get_xdir (), fnametime);
	}

	/* create all the subdirectories */
	mkdir_p (fname);

	return g_strdup (fname);
}


pevent_load_defaults (void)
{
	int i;

	for (i = 0; i < NUM_XP; i++)
	{
		g_free (pntevts_text[i]);

		/* make-te.c sets this 128 flag (DON'T call gettext() flag) */
		if (te[i].num_args & 128)
			pntevts_text[i] = g_strdup (te[i].def);
		else
			pntevts_text[i] = g_strdup (_(te[i].def));
	}
}


alert_match_text (char *text, char *masks)
{
	unsigned char *p = text;
	unsigned char endchar;
	int res;

	if (masks[0] == 0)
		return FALSE;

	while (1)
	{
		if (*p >= '0' && *p <= '9')
		{
			p++;
			continue;
		}

		/* if it's RFC1459 <special>, it can be inside a word */
		switch (*p)
		{
		case '-': case '[': case ']': case '\\':
		case '`': case '^': case '{': case '}':
		case '_': case '|':
			p++;
			continue;
		}

		/* if it's a 0, space or comma, the word has ended. */
		if (*p == 0 || *p == ' ' || *p == ',' ||
			/* if it's anything BUT a letter, the word has ended. */
			 (!g_unichar_isalpha (g_utf8_get_char (p))))
		{
			endchar = *p;
			*p = 0;
			res = alert_match_word (text, masks);
			*p = endchar;

			if (res)
				return TRUE;	/* yes, matched! */

			text = p + g_utf8_skip [p[0]];
			if (*p == 0)
				return FALSE;
		}

		p += g_utf8_skip [p[0]];
	}
}


inbound_nameslist_end (server *serv, char *chan,
							  const message_tags_data *tags_data)
{
	session *sess;
	GSList *list;

	if (!strcmp (chan, "*"))
	{
		list = sess_list;
		while (list)
		{
			sess = list->data;
			if (sess->server == serv)
			{
				sess->end_of_names = TRUE;
				sess->ignore_names = FALSE;
			}
			list = list->next;
		}
		return TRUE;
	}
	sess = find_channel (serv, chan);
	if (sess)
	{
		sess->end_of_names = TRUE;
		sess->ignore_names = FALSE;
		return TRUE;
	}
	return FALSE;
}


inbound_notice (server *serv, char *to, char *nick, char *msg, char *ip, int id,
					 const message_tags_data *tags_data)
{
	char *po,*ptr=to;
	session *sess = 0;
	int server_notice = FALSE;

	if (is_channel (serv, ptr))
		sess = find_channel (serv, ptr);

	/* /notice [mode-prefix]#channel should end up in that channel */
	if (!sess && strchr(serv->nick_prefixes, ptr[0]) != NULL)
	{
		ptr++;
		sess = find_channel (serv, ptr);
	}

	if (strcmp (nick, ip) == 0)
		server_notice = TRUE;

	if (!sess)
	{
		ptr = 0;
		if (prefs.hex_irc_notice_pos == 0)
		{
											/* paranoia check */
			if (msg[0] == '[' && (!serv->have_idmsg || id))
			{
				/* guess where chanserv meant to post this -sigh- */
				if (!g_ascii_strcasecmp (nick, "ChanServ") && !find_dialog (serv, nick))
				{
					char *dest = g_strdup (msg + 1);
					char *end = strchr (dest, ']');
					if (end)
					{
						*end = 0;
						sess = find_channel (serv, dest);
					}
					g_free (dest);
				}
			}
			if (!sess)
				sess = find_session_from_nick (nick, serv);
		} else if (prefs.hex_irc_notice_pos == 1)
		{
			int stype = server_notice ? SESS_SNOTICES : SESS_NOTICES;
			sess = find_session_from_type (stype, serv);
			if (!sess)
			{
				if (stype == SESS_NOTICES)
					sess = new_ircwindow (serv, "(notices)", SESS_NOTICES, 0);
				else
					sess = new_ircwindow (serv, "(snotices)", SESS_SNOTICES, 0);
				fe_set_channel (sess);
				fe_set_title (sess);
				fe_set_nonchannel (sess, FALSE);
				userlist_clear (sess);
				log_open_or_close (sess);
			}
			/* Avoid redundancy with some Undernet notices */
			if (!strncmp (msg, "*** Notice -- ", 14))
				msg += 14;
		} else
		{
			sess = serv->front_session;
		}

		if (!sess)
		{
			if (server_notice)
				sess = serv->server_session;
			else
				sess = serv->front_session;
		}
	}

	if (msg[0] == 1)
	{
		msg++;
		if (!strncmp (msg, "PING", 4))
		{
			inbound_ping_reply (sess, msg + 5, nick, tags_data);
			return;
		}
	}
	po = strchr (msg, '\001');
	if (po)
		po[0] = 0;

	if (server_notice)
		EMIT_SIGNAL_TIMESTAMP (XP_TE_SERVNOTICE, sess, msg, nick, NULL, NULL, 0,
									  tags_data->timestamp);
	else if (ptr)
		EMIT_SIGNAL_TIMESTAMP (XP_TE_CHANNOTICE, sess, nick, to, msg, NULL, 0,
									  tags_data->timestamp);
	else
		EMIT_SIGNAL_TIMESTAMP (XP_TE_NOTICE, sess, nick, msg, NULL, NULL, 0,
									  tags_data->timestamp);
}


static struct rt6_info *ip6_pol_route(struct net *net, struct fib6_table *table, int oif,
				      struct flowi6 *fl6, int flags)
{
	struct fib6_node *fn, *saved_fn;
	struct rt6_info *rt, *nrt;
	int strict = 0;
	int attempts = 3;
	int err;

	strict |= flags & RT6_LOOKUP_F_IFACE;
	if (net->ipv6.devconf_all->forwarding == 0)
		strict |= RT6_LOOKUP_F_REACHABLE;

redo_fib6_lookup_lock:
	read_lock_bh(&table->tb6_lock);

	fn = fib6_lookup(&table->tb6_root, &fl6->daddr, &fl6->saddr);
	saved_fn = fn;

redo_rt6_select:
	rt = rt6_select(fn, oif, strict);
	if (rt->rt6i_nsiblings)
		rt = rt6_multipath_select(rt, fl6, oif, strict);
	if (rt == net->ipv6.ip6_null_entry) {
		fn = fib6_backtrack(fn, &fl6->saddr);
		if (fn)
			goto redo_rt6_select;
		else if (strict & RT6_LOOKUP_F_REACHABLE) {
			/* also consider unreachable route */
			strict &= ~RT6_LOOKUP_F_REACHABLE;
			fn = saved_fn;
			goto redo_rt6_select;
		} else {
			dst_hold(&rt->dst);
			read_unlock_bh(&table->tb6_lock);
			goto out2;
		}
	}

	dst_hold(&rt->dst);
	read_unlock_bh(&table->tb6_lock);

	if (rt->rt6i_flags & RTF_CACHE)
		goto out2;

	if (!(rt->rt6i_flags & (RTF_NONEXTHOP | RTF_GATEWAY)))
		nrt = rt6_alloc_cow(rt, &fl6->daddr, &fl6->saddr);
	else if (!(rt->dst.flags & DST_HOST))
		nrt = rt6_alloc_clone(rt, &fl6->daddr);
	else
		goto out2;

	ip6_rt_put(rt);
	rt = nrt ? : net->ipv6.ip6_null_entry;

	dst_hold(&rt->dst);
	if (nrt) {
		err = ip6_ins_rt(nrt);
		if (!err)
			goto out2;
	}

	if (--attempts <= 0)
		goto out2;

	/*
	 * Race condition! In the gap, when table->tb6_lock was
	 * released someone could insert this route.  Relookup.
	 */
	ip6_rt_put(rt);
	goto redo_fib6_lookup_lock;

out2:
	rt->dst.lastuse = jiffies;
	rt->dst.__use++;

	return rt;
}


int ip6_route_add(struct fib6_config *cfg)
{
	int err;
	struct net *net = cfg->fc_nlinfo.nl_net;
	struct rt6_info *rt = NULL;
	struct net_device *dev = NULL;
	struct inet6_dev *idev = NULL;
	struct fib6_table *table;
	int addr_type;

	if (cfg->fc_dst_len > 128 || cfg->fc_src_len > 128)
		return -EINVAL;
#ifndef CONFIG_IPV6_SUBTREES
	if (cfg->fc_src_len)
		return -EINVAL;
#endif
	if (cfg->fc_ifindex) {
		err = -ENODEV;
		dev = dev_get_by_index(net, cfg->fc_ifindex);
		if (!dev)
			goto out;
		idev = in6_dev_get(dev);
		if (!idev)
			goto out;
	}

	if (cfg->fc_metric == 0)
		cfg->fc_metric = IP6_RT_PRIO_USER;

	err = -ENOBUFS;
	if (cfg->fc_nlinfo.nlh &&
	    !(cfg->fc_nlinfo.nlh->nlmsg_flags & NLM_F_CREATE)) {
		table = fib6_get_table(net, cfg->fc_table);
		if (!table) {
			pr_warn("NLM_F_CREATE should be specified when creating new route\n");
			table = fib6_new_table(net, cfg->fc_table);
		}
	} else {
		table = fib6_new_table(net, cfg->fc_table);
	}

	if (!table)
		goto out;

	rt = ip6_dst_alloc(net, NULL, (cfg->fc_flags & RTF_ADDRCONF) ? 0 : DST_NOCOUNT, table);

	if (!rt) {
		err = -ENOMEM;
		goto out;
	}

	if (cfg->fc_flags & RTF_EXPIRES)
		rt6_set_expires(rt, jiffies +
				clock_t_to_jiffies(cfg->fc_expires));
	else
		rt6_clean_expires(rt);

	if (cfg->fc_protocol == RTPROT_UNSPEC)
		cfg->fc_protocol = RTPROT_BOOT;
	rt->rt6i_protocol = cfg->fc_protocol;

	addr_type = ipv6_addr_type(&cfg->fc_dst);

	if (addr_type & IPV6_ADDR_MULTICAST)
		rt->dst.input = ip6_mc_input;
	else if (cfg->fc_flags & RTF_LOCAL)
		rt->dst.input = ip6_input;
	else
		rt->dst.input = ip6_forward;

	rt->dst.output = ip6_output;

	ipv6_addr_prefix(&rt->rt6i_dst.addr, &cfg->fc_dst, cfg->fc_dst_len);
	rt->rt6i_dst.plen = cfg->fc_dst_len;
	if (rt->rt6i_dst.plen == 128) {
		rt->dst.flags |= DST_HOST;
		dst_metrics_set_force_overwrite(&rt->dst);
	}

#ifdef CONFIG_IPV6_SUBTREES
	ipv6_addr_prefix(&rt->rt6i_src.addr, &cfg->fc_src, cfg->fc_src_len);
	rt->rt6i_src.plen = cfg->fc_src_len;
#endif

	rt->rt6i_metric = cfg->fc_metric;

	/* We cannot add true routes via loopback here,
	   they would result in kernel looping; promote them to reject routes
	 */
	if ((cfg->fc_flags & RTF_REJECT) ||
	    (dev && (dev->flags & IFF_LOOPBACK) &&
	     !(addr_type & IPV6_ADDR_LOOPBACK) &&
	     !(cfg->fc_flags & RTF_LOCAL))) {
		/* hold loopback dev/idev if we haven't done so. */
		if (dev != net->loopback_dev) {
			if (dev) {
				dev_put(dev);
				in6_dev_put(idev);
			}
			dev = net->loopback_dev;
			dev_hold(dev);
			idev = in6_dev_get(dev);
			if (!idev) {
				err = -ENODEV;
				goto out;
			}
		}
		rt->rt6i_flags = RTF_REJECT|RTF_NONEXTHOP;
		switch (cfg->fc_type) {
		case RTN_BLACKHOLE:
			rt->dst.error = -EINVAL;
			rt->dst.output = dst_discard_sk;
			rt->dst.input = dst_discard;
			break;
		case RTN_PROHIBIT:
			rt->dst.error = -EACCES;
			rt->dst.output = ip6_pkt_prohibit_out;
			rt->dst.input = ip6_pkt_prohibit;
			break;
		case RTN_THROW:
		default:
			rt->dst.error = (cfg->fc_type == RTN_THROW) ? -EAGAIN
					: -ENETUNREACH;
			rt->dst.output = ip6_pkt_discard_out;
			rt->dst.input = ip6_pkt_discard;
			break;
		}
		goto install_route;
	}

	if (cfg->fc_flags & RTF_GATEWAY) {
		const struct in6_addr *gw_addr;
		int gwa_type;

		gw_addr = &cfg->fc_gateway;
		rt->rt6i_gateway = *gw_addr;
		gwa_type = ipv6_addr_type(gw_addr);

		if (gwa_type != (IPV6_ADDR_LINKLOCAL|IPV6_ADDR_UNICAST)) {
			struct rt6_info *grt;

			/* IPv6 strictly inhibits using not link-local
			   addresses as nexthop address.
			   Otherwise, router will not able to send redirects.
			   It is very good, but in some (rare!) circumstances
			   (SIT, PtP, NBMA NOARP links) it is handy to allow
			   some exceptions. --ANK
			 */
			err = -EINVAL;
			if (!(gwa_type & IPV6_ADDR_UNICAST))
				goto out;

			grt = rt6_lookup(net, gw_addr, NULL, cfg->fc_ifindex, 1);

			err = -EHOSTUNREACH;
			if (!grt)
				goto out;
			if (dev) {
				if (dev != grt->dst.dev) {
					ip6_rt_put(grt);
					goto out;
				}
			} else {
				dev = grt->dst.dev;
				idev = grt->rt6i_idev;
				dev_hold(dev);
				in6_dev_hold(grt->rt6i_idev);
			}
			if (!(grt->rt6i_flags & RTF_GATEWAY))
				err = 0;
			ip6_rt_put(grt);

			if (err)
				goto out;
		}
		err = -EINVAL;
		if (!dev || (dev->flags & IFF_LOOPBACK))
			goto out;
	}

	err = -ENODEV;
	if (!dev)
		goto out;

	if (!ipv6_addr_any(&cfg->fc_prefsrc)) {
		if (!ipv6_chk_addr(net, &cfg->fc_prefsrc, dev, 0)) {
			err = -EINVAL;
			goto out;
		}
		rt->rt6i_prefsrc.addr = cfg->fc_prefsrc;
		rt->rt6i_prefsrc.plen = 128;
	} else
		rt->rt6i_prefsrc.plen = 0;

	rt->rt6i_flags = cfg->fc_flags;

install_route:
	rt->dst.dev = dev;
	rt->rt6i_idev = idev;
	rt->rt6i_table = table;

	cfg->fc_nlinfo.nl_net = dev_net(dev);

	return __ip6_ins_rt(rt, &cfg->fc_nlinfo, cfg->fc_mx, cfg->fc_mx_len);

out:
	if (dev)
		dev_put(dev);
	if (idev)
		in6_dev_put(idev);
	if (rt)
		dst_free(&rt->dst);
	return err;
}


static int fib6_clean_tohost(struct rt6_info *rt, void *arg)
{
	struct in6_addr *gateway = (struct in6_addr *)arg;

	if ((((rt->rt6i_flags & RTF_RA_ROUTER) == RTF_RA_ROUTER) ||
	     ((rt->rt6i_flags & RTF_CACHE_GATEWAY) == RTF_CACHE_GATEWAY)) &&
	     ipv6_addr_equal(gateway, &rt->rt6i_gateway)) {
		return -1;
	}
	return 0;
}


int ipv6_route_ioctl(struct net *net, unsigned int cmd, void __user *arg)
{
	struct fib6_config cfg;
	struct in6_rtmsg rtmsg;
	int err;

	switch (cmd) {
	case SIOCADDRT:		/* Add a route */
	case SIOCDELRT:		/* Delete a route */
		if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
			return -EPERM;
		err = copy_from_user(&rtmsg, arg,
				     sizeof(struct in6_rtmsg));
		if (err)
			return -EFAULT;

		rtmsg_to_fib6_config(net, &rtmsg, &cfg);

		rtnl_lock();
		switch (cmd) {
		case SIOCADDRT:
			err = ip6_route_add(&cfg);
			break;
		case SIOCDELRT:
			err = ip6_route_del(&cfg);
			break;
		default:
			err = -EINVAL;
		}
		rtnl_unlock();

		return err;
	}

	return -EINVAL;
}


jpc_ms_t *jpc_getms(jas_stream_t *in, jpc_cstate_t *cstate)
{
	jpc_ms_t *ms;
	const jpc_mstabent_t *mstabent;
	jas_stream_t *tmpstream;

	if (!(ms = jpc_ms_create(0))) {
		return 0;
	}

	/* Get the marker type. */
	if (jpc_getuint16(in, &ms->id) || ms->id < JPC_MS_MIN ||
	  ms->id > JPC_MS_MAX) {
		jpc_ms_destroy(ms);
		return 0;
	}

	mstabent = jpc_mstab_lookup(ms->id);
	ms->ops = &mstabent->ops;

	/* Get the marker segment length and parameters if present. */
	/* Note: It is tacitly assumed that a marker segment cannot have
	  parameters unless it has a length field.  That is, there cannot
	  be a parameters field without a length field and vice versa. */
	if (JPC_MS_HASPARMS(ms->id)) {
		/* Get the length of the marker segment. */
		if (jpc_getuint16(in, &ms->len) || ms->len < 3) {
			jpc_ms_destroy(ms);
			return 0;
		}
		/* Calculate the length of the marker segment parameters. */
		ms->len -= 2;
		/* Create and prepare a temporary memory stream from which to
		  read the marker segment parameters. */
		/* Note: This approach provides a simple way of ensuring that
		  we never read beyond the end of the marker segment (even if
		  the marker segment length is errantly set too small). */
		if (!(tmpstream = jas_stream_memopen(0, 0))) {
			jpc_ms_destroy(ms);
			return 0;
		}
		if (jas_stream_copy(tmpstream, in, ms->len) ||
		  jas_stream_seek(tmpstream, 0, SEEK_SET) < 0) {
			jas_stream_close(tmpstream);
			jpc_ms_destroy(ms);
			return 0;
		}
		/* Get the marker segment parameters. */
		if ((*ms->ops->getparms)(ms, cstate, tmpstream)) {
			ms->ops = 0;
			jpc_ms_destroy(ms);
			jas_stream_close(tmpstream);
			return 0;
		}

		if (jas_getdbglevel() > 0) {
			jpc_ms_dump(ms, stderr);
		}

		if (JAS_CAST(jas_ulong, jas_stream_tell(tmpstream)) != ms->len) {
			jas_eprintf(
			  "warning: trailing garbage in marker segment (%ld bytes)\n",
			  ms->len - jas_stream_tell(tmpstream));
		}

		/* Close the temporary stream. */
		jas_stream_close(tmpstream);

	} else {
		/* There are no marker segment parameters. */
		ms->len = 0;

		if (jas_getdbglevel() > 0) {
			jpc_ms_dump(ms, stderr);
		}
	}

	/* Update the code stream state information based on the type of
	  marker segment read. */
	/* Note: This is a bit of a hack, but I'm not going to define another
	  type of virtual function for this one special case. */
	if (ms->id == JPC_MS_SIZ) {
		cstate->numcomps = ms->parms.siz.numcomps;
	}

	return ms;
}


static bool megasas_frame_is_ieee_sgl(MegasasCmd *cmd)
{
    return cmd->flags & MFI_FRAME_IEEE_SGL;
}


static void megasas_frame_set_cmd_status(MegasasState *s,
                                         unsigned long frame, uint8_t v)
{
    PCIDevice *pci = &s->parent_obj;
    stb_pci_dma(pci, frame + offsetof(struct mfi_frame_header, cmd_status), v);
}


static void megasas_scsi_realize(PCIDevice *dev, Error **errp)
{
    DeviceState *d = DEVICE(dev);
    MegasasState *s = MEGASAS(dev);
    MegasasBaseClass *b = MEGASAS_DEVICE_GET_CLASS(s);
    uint8_t *pci_conf;
    int i, bar_type;
    Error *err = NULL;
    int ret;

    pci_conf = dev->config;

    /* PCI latency timer = 0 */
    pci_conf[PCI_LATENCY_TIMER] = 0;
    /* Interrupt pin 1 */
    pci_conf[PCI_INTERRUPT_PIN] = 0x01;

    if (s->msi != ON_OFF_AUTO_OFF) {
        ret = msi_init(dev, 0x50, 1, true, false, &err);
        /* Any error other than -ENOTSUP(board's MSI support is broken)
         * is a programming error */
        assert(!ret || ret == -ENOTSUP);
        if (ret && s->msi == ON_OFF_AUTO_ON) {
            /* Can't satisfy user's explicit msi=on request, fail */
            error_append_hint(&err, "You have to use msi=auto (default) or "
                    "msi=off with this machine type.\n");
            error_propagate(errp, err);
            return;
        } else if (ret) {
            /* With msi=auto, we fall back to MSI off silently */
            s->msi = ON_OFF_AUTO_OFF;
            error_free(err);
        }
    }

    memory_region_init_io(&s->mmio_io, OBJECT(s), &megasas_mmio_ops, s,
                          "megasas-mmio", 0x4000);
    memory_region_init_io(&s->port_io, OBJECT(s), &megasas_port_ops, s,
                          "megasas-io", 256);
    memory_region_init_io(&s->queue_io, OBJECT(s), &megasas_queue_ops, s,
                          "megasas-queue", 0x40000);

    if (megasas_use_msix(s) &&
        msix_init(dev, 15, &s->mmio_io, b->mmio_bar, 0x2000,
                  &s->mmio_io, b->mmio_bar, 0x3800, 0x68)) {
        s->msix = ON_OFF_AUTO_OFF;
    }
    if (pci_is_express(dev)) {
        pcie_endpoint_cap_init(dev, 0xa0);
    }

    bar_type = PCI_BASE_ADDRESS_SPACE_MEMORY | PCI_BASE_ADDRESS_MEM_TYPE_64;
    pci_register_bar(dev, b->ioport_bar,
                     PCI_BASE_ADDRESS_SPACE_IO, &s->port_io);
    pci_register_bar(dev, b->mmio_bar, bar_type, &s->mmio_io);
    pci_register_bar(dev, 3, bar_type, &s->queue_io);

    if (megasas_use_msix(s)) {
        msix_vector_use(dev, 0);
    }

    s->fw_state = MFI_FWSTATE_READY;
    if (!s->sas_addr) {
        s->sas_addr = ((NAA_LOCALLY_ASSIGNED_ID << 24) |
                       IEEE_COMPANY_LOCALLY_ASSIGNED) << 36;
        s->sas_addr |= (pci_bus_num(dev->bus) << 16);
        s->sas_addr |= (PCI_SLOT(dev->devfn) << 8);
        s->sas_addr |= PCI_FUNC(dev->devfn);
    }
    if (!s->hba_serial) {
        s->hba_serial = g_strdup(MEGASAS_HBA_SERIAL);
    }
    if (s->fw_sge >= MEGASAS_MAX_SGE - MFI_PASS_FRAME_SIZE) {
        s->fw_sge = MEGASAS_MAX_SGE - MFI_PASS_FRAME_SIZE;
    } else if (s->fw_sge >= 128 - MFI_PASS_FRAME_SIZE) {
        s->fw_sge = 128 - MFI_PASS_FRAME_SIZE;
    } else {
        s->fw_sge = 64 - MFI_PASS_FRAME_SIZE;
    }
    if (s->fw_cmds > MEGASAS_MAX_FRAMES) {
        s->fw_cmds = MEGASAS_MAX_FRAMES;
    }
    trace_megasas_init(s->fw_sge, s->fw_cmds,
                       megasas_is_jbod(s) ? "jbod" : "raid");

    if (megasas_is_jbod(s)) {
        s->fw_luns = MFI_MAX_SYS_PDS;
    } else {
        s->fw_luns = MFI_MAX_LD;
    }
    s->producer_pa = 0;
    s->consumer_pa = 0;
    for (i = 0; i < s->fw_cmds; i++) {
        s->frames[i].index = i;
        s->frames[i].context = -1;
        s->frames[i].pa = 0;
        s->frames[i].state = s;
    }

    scsi_bus_new(&s->bus, sizeof(s->bus), DEVICE(dev),
                 &megasas_scsi_info, NULL);
    if (!d->hotplugged) {
        scsi_bus_legacy_handle_cmdline(&s->bus, errp);
    }
}


static int megasas_handle_abort(MegasasState *s, MegasasCmd *cmd)
{
    uint64_t abort_ctx = le64_to_cpu(cmd->frame->abort.abort_context);
    hwaddr abort_addr, addr_hi, addr_lo;
    MegasasCmd *abort_cmd;

    addr_hi = le32_to_cpu(cmd->frame->abort.abort_mfi_addr_hi);
    addr_lo = le32_to_cpu(cmd->frame->abort.abort_mfi_addr_lo);
    abort_addr = ((uint64_t)addr_hi << 32) | addr_lo;

    abort_cmd = megasas_lookup_frame(s, abort_addr);
    if (!abort_cmd) {
        trace_megasas_abort_no_cmd(cmd->index, abort_ctx);
        s->event_count++;
        return MFI_STAT_OK;
    }
    if (!megasas_use_queue64(s)) {
        abort_ctx &= (uint64_t)0xFFFFFFFF;
    }
    if (abort_cmd->context != abort_ctx) {
        trace_megasas_abort_invalid_context(cmd->index, abort_cmd->index,
                                            abort_cmd->context);
        s->event_count++;
        return MFI_STAT_ABORT_NOT_POSSIBLE;
    }
    trace_megasas_abort_frame(cmd->index, abort_cmd->index);
    megasas_abort_command(abort_cmd);
    if (!s->event_cmd || abort_cmd != s->event_cmd) {
        s->event_cmd = NULL;
    }
    s->event_count++;
    return MFI_STAT_OK;
}


static void megasas_register_types(void)
{
    int i;

    type_register_static(&megasas_info);
    for (i = 0; i < ARRAY_SIZE(megasas_devices); i++) {
        const MegasasInfo *info = &megasas_devices[i];
        TypeInfo type_info = {};

        type_info.name = info->name;
        type_info.parent = TYPE_MEGASAS_BASE;
        type_info.class_data = (void *)info;
        type_info.class_init = megasas_class_init;

        type_register(&type_info);
    }
}


static int megasas_setup_inquiry(uint8_t *cdb, int pg, int len)
{
    memset(cdb, 0, 6);
    cdb[0] = INQUIRY;
    if (pg > 0) {
        cdb[1] = 0x1;
        cdb[2] = pg;
    }
    cdb[3] = (len >> 8) & 0xff;
    cdb[4] = (len & 0xff);
    return len;
}


static plist_t parse_bin_node_at_index(struct bplist_data *bplist, uint32_t node_index)
{
    int i = 0;
    const char* ptr = NULL;
    plist_t plist = NULL;
    const char* idx_ptr = NULL;

    if (node_index > bplist->num_objects)
        return NULL;

    idx_ptr = bplist->offset_table + node_index * bplist->offset_size;
    if (idx_ptr < bplist->offset_table ||
        idx_ptr >= bplist->offset_table + bplist->num_objects * bplist->offset_size)
        return NULL;

    ptr = bplist->data + UINT_TO_HOST(idx_ptr, bplist->offset_size);
    /* make sure the node offset is in a sane range */
    if ((ptr < bplist->data) || (ptr >= bplist->offset_table)) {
        return NULL;
    }

    /* store node_index for current recursion level */
    bplist->used_indexes[bplist->level] = node_index;
    /* recursion check */
    if (bplist->level > 0) {
        for (i = bplist->level-1; i >= 0; i--) {
            if (bplist->used_indexes[i] == bplist->used_indexes[bplist->level]) {
                fprintf(stderr, "Recursion detected in binary plist. Aborting.\n");
                return NULL;
            }
        }
    }

    /* finally parse node */
    bplist->level++;
    plist = parse_bin_node(bplist, &ptr);
    bplist->level--;
    return plist;
}


static plist_t parse_bin_node(struct bplist_data *bplist, const char** object)
{
    uint16_t type = 0;
    uint64_t size = 0;

    if (!object)
        return NULL;

    type = (**object) & BPLIST_MASK;
    size = (**object) & BPLIST_FILL;
    (*object)++;

    switch (type)
    {

    case BPLIST_NULL:
        switch (size)
        {

        case BPLIST_TRUE:
        {
            plist_data_t data = plist_new_plist_data();
            data->type = PLIST_BOOLEAN;
            data->boolval = TRUE;
            data->length = 1;
            return node_create(NULL, data);
        }

        case BPLIST_FALSE:
        {
            plist_data_t data = plist_new_plist_data();
            data->type = PLIST_BOOLEAN;
            data->boolval = FALSE;
            data->length = 1;
            return node_create(NULL, data);
        }

        case BPLIST_NULL:
        default:
            return NULL;
        }

    case BPLIST_UINT:
        if (*object - bplist->data + (uint64_t)(1 << size) >= bplist->size)
            return NULL;
        return parse_uint_node(object, size);

    case BPLIST_REAL:
        if (*object - bplist->data + (uint64_t)(1 << size) >= bplist->size)
            return NULL;
        return parse_real_node(object, size);

    case BPLIST_DATE:
        if (3 != size)
            return NULL;
        if (*object - bplist->data + (uint64_t)(1 << size) >= bplist->size)
            return NULL;
        return parse_date_node(object, size);

    case BPLIST_DATA:
        if (BPLIST_FILL == size) {
            plist_t size_node = parse_bin_node(bplist, object);
            if (plist_get_node_type(size_node) != PLIST_UINT)
                return NULL;
            plist_get_uint_val(size_node, &size);
            plist_free(size_node);
        }

        if (*object - bplist->data + size >= bplist->size)
            return NULL;
        return parse_data_node(object, size);

    case BPLIST_STRING:
        if (BPLIST_FILL == size) {
            plist_t size_node = parse_bin_node(bplist, object);
            if (plist_get_node_type(size_node) != PLIST_UINT)
                return NULL;
            plist_get_uint_val(size_node, &size);
            plist_free(size_node);
        }

        if (*object - bplist->data + size >= bplist->size)
            return NULL;
        return parse_string_node(object, size);

    case BPLIST_UNICODE:
        if (BPLIST_FILL == size) {
            plist_t size_node = parse_bin_node(bplist, object);
            if (plist_get_node_type(size_node) != PLIST_UINT)
                return NULL;
            plist_get_uint_val(size_node, &size);
            plist_free(size_node);
        }

        if (*object - bplist->data + size * 2 >= bplist->size)
            return NULL;
        return parse_unicode_node(object, size);

    case BPLIST_SET:
    case BPLIST_ARRAY:
        if (BPLIST_FILL == size) {
            plist_t size_node = parse_bin_node(bplist, object);
            if (plist_get_node_type(size_node) != PLIST_UINT)
                return NULL;
            plist_get_uint_val(size_node, &size);
            plist_free(size_node);
        }

        if (*object - bplist->data + size >= bplist->size)
            return NULL;
        return parse_array_node(bplist, object, size);

    case BPLIST_UID:
        return parse_uid_node(object, size);

    case BPLIST_DICT:
        if (BPLIST_FILL == size) {
            plist_t size_node = parse_bin_node(bplist, object);
            if (plist_get_node_type(size_node) != PLIST_UINT)
                return NULL;
            plist_get_uint_val(size_node, &size);
            plist_free(size_node);
        }

        if (*object - bplist->data + size >= bplist->size)
            return NULL;
        return parse_dict_node(bplist, object, size);
    default:
        return NULL;
    }
    return NULL;
}


static plist_t parse_uint_node(const char **bnode, uint8_t size)
{
    plist_data_t data = plist_new_plist_data();

    size = 1 << size;			// make length less misleading
    switch (size)
    {
    case sizeof(uint8_t):
    case sizeof(uint16_t):
    case sizeof(uint32_t):
    case sizeof(uint64_t):
        memcpy(&data->intval, *bnode, size);
        data->intval = UINT_TO_HOST(&data->intval, size);
        data->length = sizeof(uint64_t);
        break;
    case 16:
        memcpy(&data->intval, (*bnode)+8, sizeof(uint64_t));
        data->intval = UINT_TO_HOST(&data->intval, sizeof(uint64_t));
        data->length = size;
        break;
    default:
        free(data);
        return NULL;
    };

    (*bnode) += size;
    data->type = PLIST_UINT;

    return node_create(NULL, data);
}


static void write_uint(bytearray_t * bplist, uint64_t val)
{
    uint64_t size = 16;
    uint8_t *buff = NULL;

    buff = (uint8_t *) malloc(sizeof(uint8_t) + size);
    buff[0] = BPLIST_UINT | 4;
    memset(buff + 1, '\0', 8);
    memcpy(buff + 9, &val, 8);
    byte_convert(buff + 9, 8);
    byte_array_append(bplist, buff, sizeof(uint8_t) + size);
    free(buff);
}


static void test_send_s32(GOutputStream *os, gint32 v)
{
    v = GINT32_TO_BE(v);
    g_assert(g_output_stream_write_all(os, &v, 4, NULL, NULL, NULL));
}


static void test_helper_error(VncConnection *conn,
                              const char *str,
                              gpointer opaque)
{
    struct GVncTest *test = opaque;
    test->error = g_strdup(str);
}


static void test_recv_u8(GInputStream *is, guint8 v)
{
    guint8 e;
    g_assert(g_input_stream_read_all(is, &e, 1, NULL, NULL, NULL));
    g_assert(e == v);
}


static void test_hextile_bounds_server(GInputStream *is, GOutputStream *os)
{
    test_common_bounds_server(is, os);

    /* Message type & pad */
    test_send_u8(os, 0);
    test_send_u8(os, 0);

    /* num rect */
    test_send_u16(os, 1);
    /* x, y, w, h */
    test_send_u16(os, 90);
    test_send_u16(os, 90);
    test_send_u16(os, 10);
    test_send_u16(os, 10);

    /* encoding=hextile */
    test_send_s32(os, 5);

    /* tile type */
    test_send_u8(os, 0x18);

    /* num rect */
    test_send_u8(os, 1);

    /* fg pix */
    test_send_u32(os, 0x12345678);

    /* x, y */
    test_send_u8(os, 0xff);
    test_send_u8(os, 0xff);
}


static void test_recv_s32(GInputStream *is, gint32 v)
{
    gint32 e;
    g_assert(g_input_stream_read_all(is, &e, 4, NULL, NULL, NULL));
    e = GINT32_FROM_BE(e);
    g_assert(e == v);
}