// tapset for kernel static markers
// Copyright (C) 2005-2014 Red Hat Inc.
// Copyright (C) 2005-2007 Intel Corporation.
//
// This file is part of systemtap, and is free software.  You can
// redistribute it and/or modify it under the terms of the GNU General
// Public License (GPL); either version 2, or (at your option) any
// later version.

#include "session.h"
#include "tapsets.h"
#include "translate.h"
#include "util.h"

#include <cerrno>
#include <cstdlib>
#include <cstring>
#include <string>

extern "C" {
#include <fnmatch.h>
}


using namespace std;
using namespace __gnu_cxx;


static const string TOK_KERNEL("kernel");
static const string TOK_MARK("mark");
static const string TOK_FORMAT("format");


// ------------------------------------------------------------------------
// statically inserted macro-based derived probes
// ------------------------------------------------------------------------

struct mark_arg
{
  bool str;
  bool isptr;
  string c_type;
  exp_type stp_type;
};

struct mark_derived_probe: public derived_probe
{
  mark_derived_probe (systemtap_session &s,
                      const string& probe_name, const string& probe_format,
                      probe* base_probe, probe_point* location);

  systemtap_session& sess;
  string probe_name, probe_format;
  vector <struct mark_arg *> mark_args;
  bool target_symbol_seen;

  void join_group (systemtap_session& s);
  void print_dupe_stamp (ostream& o);
  void initialize_probe_context_vars (translator_output* o);
  void getargs (std::list<std::string> &arg_set) const;

  void parse_probe_format ();
};


struct mark_derived_probe_group: public generic_dpg<mark_derived_probe>
{
public:
  void emit_module_decls (systemtap_session& s);
  void emit_module_init (systemtap_session& s);
  void emit_module_exit (systemtap_session& s);
};


struct mark_var_expanding_visitor: public var_expanding_visitor
{
  mark_var_expanding_visitor(systemtap_session& s, const string& pn,
                             vector <struct mark_arg *> &mark_args):
    sess (s), probe_name (pn), mark_args (mark_args),
    target_symbol_seen (false) {}
  systemtap_session& sess;
  string probe_name;
  vector <struct mark_arg *> &mark_args;
  bool target_symbol_seen;

  void visit_target_symbol (target_symbol* e);
  void visit_target_symbol_arg (target_symbol* e);
  void visit_target_symbol_context (target_symbol* e);
};


void
mark_var_expanding_visitor::visit_target_symbol_arg (target_symbol* e)
{
  string argnum_s = e->name.substr(4,e->name.length()-4);
  int argnum = atoi (argnum_s.c_str());

  if (argnum < 1 || argnum > (int)mark_args.size())
    throw SEMANTIC_ERROR (_("invalid marker argument number"), e->tok);

  if (is_active_lvalue (e))
    throw SEMANTIC_ERROR(_("write to marker parameter not permitted"), e->tok);

  e->assert_no_components("marker");

  // Remember that we've seen a target variable.
  target_symbol_seen = true;

  symbol* sym = new symbol;
  sym->tok = e->tok;
  sym->name = "__mark_arg" + lex_cast(argnum);
  provide (sym);
}


void
mark_var_expanding_visitor::visit_target_symbol_context (target_symbol* e)
{
  string sname = e->name;

  if (is_active_lvalue (e))
    throw SEMANTIC_ERROR(_F("write to marker '%s' not permitted", sname.c_str()), e->tok);

  e->assert_no_components("marker");

  if (e->name == "$format" || e->name == "$name") {
    // Synthesize an embedded expression.
    embedded_expr *expr = new embedded_expr;
    expr->tok = e->tok;

    if (e->name == "$format")
      expr->code = string("/* string */ /* pure */ ")
	+ string("c->ips.kmark.marker_format ? c->ips.kmark.marker_format : \"\"");
    else
      expr->code = string("/* string */ /* pure */ ")
	+ string("c->ips.kmark.marker_name ? c->ips.kmark.marker_name : \"\"");

    provide (expr);
  }
 else if (e->name == "$$vars" || e->name == "$$parms")
  {
     //copy from tracepoint
     print_format* pf = print_format::create(e->tok, "sprintf");

     for (unsigned i = 0; i < mark_args.size(); ++i)
        {
          if (i > 0)
            pf->raw_components += " ";
          pf->raw_components += "$arg" + lex_cast(i+1);
          target_symbol *tsym = new target_symbol;
          tsym->tok = e->tok;
          tsym->name = "$arg" + lex_cast(i+1);

          tsym->saved_conversion_error = 0;
          expression *texp = require (tsym); //same treatment as tracepoint
          assert (!tsym->saved_conversion_error);
          switch (mark_args[i]->stp_type)
           {
             case pe_long:
               pf->raw_components += mark_args[i]->isptr ? "=%p" : "=%#x";
               break;
             case pe_string:
               pf->raw_components += "=%s";
               break;
             default:
               pf->raw_components += "=%#x";
               break;
            }
          pf->args.push_back(texp);
        }
     pf->components = print_format::string_to_components(pf->raw_components);
     provide (pf);
  }
}

void
mark_var_expanding_visitor::visit_target_symbol (target_symbol* e)
{
  assert(e->name.size() > 0 && e->name[0] == '$');

  try
    {
      if (e->addressof)
        throw SEMANTIC_ERROR(_("cannot take address of marker variable"), e->tok);

      if (startswith(e->name, "$arg"))
        visit_target_symbol_arg (e);
      else if (e->name == "$format" || e->name == "$name"
               || e->name == "$$parms" || e->name == "$$vars")
        visit_target_symbol_context (e);
      else
        throw SEMANTIC_ERROR (_("invalid target symbol for marker, $argN, $name, $format, $$parms or $$vars expected"),
                              e->tok);
    }
  catch (const semantic_error &er)
    {
      e->chain (er);
      provide (e);
    }
}


mark_derived_probe::mark_derived_probe (systemtap_session &s,
                                        const string& p_n,
                                        const string& p_f,
                                        probe* base, probe_point* loc):
  derived_probe (base, loc, true /* .components soon rewritten */),
  sess (s), probe_name (p_n), probe_format (p_f),
  target_symbol_seen (false)
{
  // create synthetic probe point name; preserve condition
  vector<probe_point::component*> comps;
  comps.push_back (new probe_point::component (TOK_KERNEL));
  comps.push_back (new probe_point::component (TOK_MARK, new literal_string (probe_name)));
  comps.push_back (new probe_point::component (TOK_FORMAT, new literal_string (probe_format)));
  this->sole_location()->components = comps;

  // expand the marker format
  parse_probe_format();

  // Now expand the local variables in the probe body
  mark_var_expanding_visitor v (sess, name, mark_args);
  v.replace (this->body);
  target_symbol_seen = v.target_symbol_seen;
  if (target_symbol_seen)
    for (unsigned i = 0; i < mark_args.size(); ++i)
      {
	vardecl* v = new vardecl;
	v->name = "__mark_arg" + lex_cast(i+1);
	v->tok = this->tok;
	v->set_arity(0, this->tok);
	v->type = mark_args[i]->stp_type;
	v->synthetic = true;
	this->locals.push_back (v);
      }

  if (sess.verbose > 2)
    clog << "marker-based " << name << " mark=" << probe_name << " fmt='" << probe_format
         << "'" << endl;
}


static int
skip_atoi(const char **s)
{
  int i = 0;
  while (isdigit(**s))
    i = i * 10 + *((*s)++) - '0';
  return i;
}


void
mark_derived_probe::parse_probe_format()
{
  const char *fmt = probe_format.c_str();
  int qualifier;		// 'h', 'l', or 'L' for integer fields
  mark_arg *arg;

  for (; *fmt ; ++fmt)
    {
      if (*fmt != '%')
        {
	  /* Skip text */
	  continue;
	}

repeat:
      ++fmt;

      // skip conversion flags (if present)
      switch (*fmt)
        {
	case '-':
	case '+':
	case ' ':
	case '#':
	case '0':
	  goto repeat;
	}

      // skip minimum field witdh (if present)
      if (isdigit(*fmt))
	skip_atoi(&fmt);

      // skip precision (if present)
      if (*fmt == '.')
        {
	  ++fmt;
	  if (isdigit(*fmt))
	    skip_atoi(&fmt);
	}

      // get the conversion qualifier (if present)
      qualifier = -1;
      if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L')
        {
	  qualifier = *fmt;
	  ++fmt;
	  if (qualifier == 'l' && *fmt == 'l')
	    {
	      qualifier = 'L';
	      ++fmt;
	    }
	}

      // get the conversion type
      switch (*fmt)
        {
	case 'c':
	  arg = new mark_arg;
	  arg->str = false;
	  arg->isptr = false;
	  arg->c_type = "int";
	  arg->stp_type = pe_long;
	  mark_args.push_back(arg);
	  continue;

	case 's':
	  arg = new mark_arg;
	  arg->str = true;
	  arg->isptr = false;
	  arg->c_type = "char *";
	  arg->stp_type = pe_string;
	  mark_args.push_back(arg);
	  continue;

	case 'p':
	  arg = new mark_arg;
	  arg->str = false;
	  arg->isptr = true;
	  // This should really be 'void *'.  But, then we'll get a
	  // compile error when we assign the void pointer to an
	  // integer without a cast.  So, we use 'long' instead, since
	  // it should have the same size as 'void *'.
	  arg->c_type = "long";
	  arg->stp_type = pe_long;
	  mark_args.push_back(arg);
	  continue;

	case '%':
	  continue;

	case 'o':
	case 'X':
	case 'x':
	case 'd':
	case 'i':
	case 'u':
	  // fall through...
	  break;

	default:
	  if (!*fmt)
	    --fmt;
	  continue;
	}

      arg = new mark_arg;
      arg->str = false;
      arg->isptr = false;
      arg->stp_type = pe_long;
      switch (qualifier)
        {
	case 'L':
	  arg->c_type = "long long";
	  break;

	case 'l':
	  arg->c_type = "long";
	  break;

	case 'h':
	  arg->c_type = "short";
	  break;

	default:
	  arg->c_type = "int";
	  break;
	}
      mark_args.push_back(arg);
    }
}


void
mark_derived_probe::join_group (systemtap_session& s)
{
  if (! s.mark_derived_probes)
    {
      s.mark_derived_probes = new mark_derived_probe_group ();

      // Make sure <linux/marker.h> is included early.
      embeddedcode *ec = new embeddedcode;
      ec->tok = NULL;
      ec->code = string("#if ! defined(CONFIG_MARKERS)\n")
	+ string("#error \"Need CONFIG_MARKERS!\"\n")
	+ string("#endif\n")
	+ string("#include <linux/marker.h>\n");

      s.embeds.push_back(ec);
    }
  s.mark_derived_probes->enroll (this);
  this->group = s.mark_derived_probes;
}


void
mark_derived_probe::print_dupe_stamp (ostream& o)
{
  if (target_symbol_seen)
    for (unsigned i = 0; i < mark_args.size(); i++)
      o << mark_args[i]->c_type << " __mark_arg" << (i+1) << endl;
}


void
mark_derived_probe::initialize_probe_context_vars (translator_output* o)
{
  // If we haven't seen a target symbol for this probe, quit.
  if (! target_symbol_seen)
    return;

  bool deref_fault_needed = false;
  for (unsigned i = 0; i < mark_args.size(); i++)
    {
      string localname = "l->" +
        sess.up->c_localname("__mark_arg" + lex_cast(i+1));
      switch (mark_args[i]->stp_type)
        {
	case pe_long:
	  o->newline() << localname << " = va_arg(*c->ips.kmark.mark_va_list, "
		       << mark_args[i]->c_type << ");";
	  break;

	case pe_string:
	  // We're assuming that this is a kernel string (this code is
	  // basically the guts of kernel_string), not a user string.
	  o->newline() << "{ " << mark_args[i]->c_type
		       << " tmp_str = va_arg(*c->ips.kmark.mark_va_list, "
		       << mark_args[i]->c_type << ");";
	  o->newline() << "kderef_string (" << localname
		       << ", tmp_str, MAXSTRINGLEN); }";
	  deref_fault_needed = true;
	  break;

	default:
	  throw SEMANTIC_ERROR (_("cannot expand unknown type"));
	  break;
	}
    }
  if (deref_fault_needed)
    // Need to report errors?
    o->newline() << "deref_fault: ;";
}

void
mark_derived_probe::getargs(std::list<std::string> &arg_set) const
{
  //PR11761: hard-coded the basic variables
  arg_set.push_back("$name:string");
  arg_set.push_back("$format:string");
  for (unsigned i = 0; i < mark_args.size(); i++)
    {
      string localname = "$arg" + lex_cast(i+1);
      switch (mark_args[i]->stp_type)
        {
        case pe_long:
          arg_set.push_back(localname+":long");
          break;
        case pe_string:
          arg_set.push_back(localname+":string");
          break;
        default:
          arg_set.push_back(localname+":unknown");
          break;
        }
    }
}


void
mark_derived_probe_group::emit_module_decls (systemtap_session& s)
{
  if (probes.empty())
    return;

  s.op->newline() << "/* ---- marker probes ---- */";

  s.op->newline() << "static struct stap_marker_probe {";
  s.op->newline(1) << "const char * const name;";
  s.op->newline() << "const char * const format;";
  s.op->newline() << "const struct stap_probe * const probe;";
  s.op->newline(-1) << "} stap_marker_probes [" << probes.size() << "] = {";
  s.op->indent(1);
  for (unsigned i=0; i < probes.size(); i++)
    {
      s.op->newline () << "{";
      s.op->line() << " .name=" << lex_cast_qstring(probes[i]->probe_name) << ",";
      s.op->line() << " .format=" << lex_cast_qstring(probes[i]->probe_format) << ",";
      s.op->line() << " .probe=" << common_probe_init (probes[i]) << ",";
      s.op->line() << " },";
    }
  s.op->newline(-1) << "};";
  s.op->newline();


  // Emit the marker callback function
  s.op->newline();
  s.op->newline() << "static void enter_marker_probe (void *probe_data, void *call_data, const char *fmt, va_list *args) {";
  s.op->newline(1) << "struct stap_marker_probe *smp = (struct stap_marker_probe *)probe_data;";
  common_probe_entryfn_prologue (s, "STAP_SESSION_RUNNING", "smp->probe",
				 "stp_probe_type_marker");
  s.op->newline() << "c->ips.kmark.marker_name = smp->name;";
  s.op->newline() << "c->ips.kmark.marker_format = smp->format;";
  s.op->newline() << "c->ips.kmark.mark_va_list = args;";
  s.op->newline() << "(*smp->probe->ph) (c);";
  s.op->newline() << "c->ips.kmark.mark_va_list = NULL;";

  common_probe_entryfn_epilogue (s, true, otf_safe_context(s));
  s.op->newline(-1) << "}";

  return;
}


void
mark_derived_probe_group::emit_module_init (systemtap_session &s)
{
  if (probes.size () == 0)
    return;

  s.op->newline() << "/* init marker probes */";
  s.op->newline() << "for (i=0; i<" << probes.size() << "; i++) {";
  s.op->newline(1) << "struct stap_marker_probe *smp = &stap_marker_probes[i];";
  s.op->newline() << "probe_point = smp->probe->pp;";
  s.op->newline() << "rc = marker_probe_register(smp->name, smp->format, enter_marker_probe, smp);";
  s.op->newline() << "if (rc) {";
  s.op->newline(1) << "for (j=i-1; j>=0; j--) {"; // partial rollback
  s.op->newline(1) << "struct stap_marker_probe *smp2 = &stap_marker_probes[j];";
  s.op->newline() << "marker_probe_unregister(smp2->name, enter_marker_probe, smp2);";
  s.op->newline(-1) << "}";
  s.op->newline() << "break;"; // don't attempt to register any more probes
  s.op->newline(-1) << "}";
  s.op->newline(-1) << "}"; // for loop
}


void
mark_derived_probe_group::emit_module_exit (systemtap_session& s)
{
  if (probes.empty())
    return;

  s.op->newline() << "/* deregister marker probes */";
  s.op->newline() << "for (i=0; i<" << probes.size() << "; i++) {";
  s.op->newline(1) << "struct stap_marker_probe *smp = &stap_marker_probes[i];";
  s.op->newline() << "marker_probe_unregister(smp->name, enter_marker_probe, smp);";
  s.op->newline(-1) << "}"; // for loop
}


struct mark_builder: public derived_probe_builder
{
private:
  bool cache_initialized;
  typedef multimap<string, string> mark_cache_t;
  typedef multimap<string, string>::const_iterator mark_cache_const_iterator_t;
  typedef pair<mark_cache_const_iterator_t, mark_cache_const_iterator_t>
    mark_cache_const_iterator_pair_t;
  mark_cache_t mark_cache;

  string suggest_marks(systemtap_session& sess,
                       const string& mark);

public:
  mark_builder(): cache_initialized(false) {}

  void build_no_more (systemtap_session &s)
  {
    if (! mark_cache.empty())
      {
        if (s.verbose > 3)
          clog << _("mark_builder releasing cache") << endl;
	mark_cache.clear();
      }
  }

  void build(systemtap_session & sess,
             probe * base,
             probe_point * location,
             literal_map_t const & parameters,
             vector<derived_probe *> & finished_results);
};


string
mark_builder::suggest_marks(systemtap_session& sess,
                           const string& mark)
{
  if (mark.empty() || mark_cache.empty())
    return "";

  set<string> marks;

  // Collect all markers from cache
  for (mark_cache_const_iterator_t it = mark_cache.begin();
       it != mark_cache.end(); it++)
    marks.insert(it->first);

  if (sess.verbose > 2)
    clog << "suggesting from " << marks.size()
         << " kernel marks" << endl;

  if (marks.empty())
    return "";

  return levenshtein_suggest(mark, marks, 5); // print top 5 marks only
}

void
mark_builder::build(systemtap_session & sess,
		    probe * base,
		    probe_point *loc,
		    literal_map_t const & parameters,
		    vector<derived_probe *> & finished_results)
{
  string mark_str_val;
  bool has_mark_str = get_param (parameters, TOK_MARK, mark_str_val);
  string mark_format_val;
  bool has_mark_format = get_param (parameters, TOK_FORMAT, mark_format_val);
  assert (has_mark_str);
  (void) has_mark_str;

  if (! cache_initialized)
    {
      cache_initialized = true;
      string module_markers_path = sess.kernel_build_tree + "/Module.markers";
      
      ifstream module_markers;
      module_markers.open(module_markers_path.c_str(), ifstream::in);
      if (! module_markers)
        {
	  if (sess.verbose>3)
            //TRANSLATORS: specific path cannot be opened
	    clog << module_markers_path << _(" cannot be opened: ")
		 << strerror(errno) << endl;
	  return;
	}

      string name, module, format;
      do
        {
	  module_markers >> name >> module;
	  getline(module_markers, format);

	  // trim leading whitespace
	  string::size_type notwhite = format.find_first_not_of(" \t");
	  format.erase(0, notwhite);

	  // If the format is empty, make sure we add back a space
	  // character, which is what MARK_NOARGS expands to.
	  if (format.length() == 0)
	    format = " ";

	  if (sess.verbose>3)
	    clog << "'" << name << "' '" << module << "' '" << format
		 << "'" << endl;

	  if (mark_cache.count(name) > 0)
	    {
	      // If we have 2 markers with the same we've got 2 cases:
	      // different format strings or duplicate format strings.
	      // If an existing marker in the cache doesn't have the
	      // same format string, add this marker.
	      mark_cache_const_iterator_pair_t ret;
	      mark_cache_const_iterator_t it;
	      bool matching_format_string = false;

	      ret = mark_cache.equal_range(name);
	      for (it = ret.first; it != ret.second; ++it)
	        {
		  if (format == it->second)
		    {
		      matching_format_string = true;
		      break;
		    }
		}

	      if (! matching_format_string)
	        mark_cache.insert(pair<string,string>(name, format));
	  }
	  else
	    mark_cache.insert(pair<string,string>(name, format));
	}
      while (! module_markers.eof());
      module_markers.close();
    }

  unsigned results_pre = finished_results.size();

  // Search marker list for matching markers
  for (mark_cache_const_iterator_t it = mark_cache.begin();
       it != mark_cache.end(); it++)
    {
      // Below, "rc" has negative polarity: zero iff matching.
      int rc = fnmatch(mark_str_val.c_str(), it->first.c_str(), 0);
      if (! rc)
        {
	  bool add_result = true;

	  // Match format strings (if the user specified one)
	  if (has_mark_format && fnmatch(mark_format_val.c_str(),
					 it->second.c_str(), 0))
	    add_result = false;

	  if (add_result)
	    {
	      derived_probe *dp
		= new mark_derived_probe (sess,
					  it->first, it->second,
					  base, loc);
	      finished_results.push_back (dp);
	    }
	}
    }

  if (results_pre == finished_results.size() && !loc->from_glob)
    {
      string sugs = suggest_marks(sess, mark_str_val);
      if (!sugs.empty())
        throw SEMANTIC_ERROR (_NF("no match (similar mark: %s)",
                                  "no match (similar marks: %s)",
                                  sugs.find(',') == string::npos,
                                  sugs.c_str()));
    }
}



void
register_tapset_mark(systemtap_session& s)
{
  match_node* root = s.pattern_root;
  derived_probe_builder *builder = new mark_builder();

  root = root->bind(TOK_KERNEL);
  root = root->bind_str(TOK_MARK);

  root->bind(builder);
  root->bind_str(TOK_FORMAT)->bind(builder);
}

/* vim: set sw=2 ts=8 cino=>4,n-2,{2,^-2,t0,(0,u0,w1,M1 : */
