#!/usr/bin/env python3

# Copyright (c) 2022 Firebuild Inc.
# All rights reserved.
# Free for personal use and commercial trial.
# Non-trial commercial use requires licenses available from https://firebuild.com.
# Modification and redistribution are permitted, but commercial use of
# derivative works is subject to the same requirements of this license
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.


import os
import sys
from jinja2 import Environment, FileSystemLoader


if len(sys.argv) != 2:
  print("Usage: ./generate_interceptors outputdir", file=sys.stderr)
  exit(1)
outdir = sys.argv[1]

supported_platforms = ['linux', 'darwin']
if sys.platform.startswith('linux'):
  target = "linux"
elif sys.platform.startswith('darwin'):
  target = "darwin"
else:
  print("Unsupported platform: {}".format(platform.system()))
  exit(1)


libc_outputs = ['decl.h', 'def.c', 'impl.c', 'list.txt', 'reset.c']
syscall_outputs = ['decl.h', 'def.c', 'impl_syscalls.c.inc', 'reset.c']
env = Environment(loader=FileSystemLoader('.'),
                  line_statement_prefix='###',
                  trim_blocks=True,
                  lstrip_blocks=True,
                  keep_trailing_newline=True,
                  extensions=['jinja2.ext.do'])

# Use a practically unique temporary filename, see #314
tmpsuffix = ".tmp." + str(os.getpid())

# Time related functions defined on 32 bit architectures
time64_functions = set({
  "__adjtime64",
  "__clock_adjtime64",
  "__clock_gettime64",
  "__clock_settime64",
  "__futimens64",
  "__futimes64",
  "__futimesat64",
  "__gettimeofday64",
  "__lutimes64",
  "__ntp_gettime64",
  "__ntp_gettimex64",
  "__recvmmsg64",
  "__recvmsg64",
  "__sendmmsg64",
  "__sendmsg64",
  "__settimeofday64",
  "__time64",
  "__utime64",
  "__utimensat64",
  "__utimes64"
})

remapped_time64_functions = set(
  [x.replace("64","").replace("__","") for x in time64_functions] +
  ["ioctl",
   "fcntl",
   "fcntl64",
   "fstat64",
   "fstatat64",
   "lstat64",
   "stat64",
   "wait3",
   "wait4"]
)

remapped_file64_functions = set({
  "creat",
  "fallocate",
  "fcntl",
  "fgetpos",
  "fopen",
  "freopen",
  "fseeko",
  "fsetpos",
  "fstat",
  "fstatat",
  "fstatfs",
  "fstatvfs",
  "ftello",
  "ftruncate",
  "lockf",
  "lseek",
  "lstat",
  "mkostemp",
  "mkostemps",
  "mkstemp",
  "mkstemps",
  "open",
  "__open_2",
  "openat",
  "__openat_2",
  "posix_fallocate",
  "pread",
  "preadv",
  "preadv2",
  "pwrite",
  "pwritev",
  "pwritev2",
  "__recvmmsg",
  "__recvmsg",
  "scandir",
  "scandirat",
  "__sendmmsg",
  "__sendmsg",
  "stat",
  "statfs",
  "statvfs",
  "tmpfile",
  "truncate",
})

# Those functions are remapped to 64 bit variants, but interception is safe
# because the affected passed and returned values are passed and returned as
# references and the interception does not use or alter their value.
safe_remapped_functions = set({
  "adjtime",
  "clock_adjtime",
  "clock_gettime",
  "clock_settime",
  "creat",
  "fcntl",
  "fcntl64",
  "fopen",
  "freopen",
  "fstatfs",
  "fstatvfs",
  "gettimeofday",
  "ioctl",
  "mkostemp",
  "mkostemps",
  "mkstemp",
  "mkstemps",
  "ntp_gettime",
  "ntp_gettimex",
  "open",
  "__open_2",
  "openat",
  "__openat_2",
  "recvmmsg",
  "recvmsg",
  "scandir",
  "scandirat",
  "__sendmmsg",
  "sendmmsg",
  "sendmsg",
  "statfs",
  "statvfs",
  "time",
  "tmpfile",
  "utime",
  "utimes",
  "wait3",
  "wait4"
})
# Best effort to try to debug as many parameters as easily doable.
def add_debug_to_dict(type, name, dict):
  debug_type_to_when_and_how = {
    "int":           ("before", "%d"),
    "unsigned int":  ("before", "%u"),
    "mode_t":        ("before", "%\" PRImode \""),
    "uid_t":         ("before", "%u"),
    "gid_t":         ("before", "%u"),
    "long":          ("before", "%ld"),
    "ssize_t":       ("before", "%\" PRIssize \""),
    "off_t":         ("before", "%\" PRIoff \""),
    "off64_t":       ("before", "%\" PRIoff64 \""),
    "unsigned long": ("before", "%lu"),
    "size_t":        ("before", "%\" PRIsize \""),
    "const char *":  ("before", "\\\"%s\\\""),
    "char *":        ("after",  "\\\"%s\\\""),  # assume the call modifies, so log at the end
    "FILE *":        ("before", "%p"),
    "DIR *":         ("before", "%p"),
  }

  if type in debug_type_to_when_and_how:
    (when, fmt) = debug_type_to_when_and_how[type]

    if name == 'ret':
      when = "after"

    dict['debug_' + when + '_fmt'] += ", " + name + "=" + fmt
    dict['debug_' + when + '_args'] += ", " + name


# Split the C parameter specification of a function, at spaces that are outside of parentheses.
# E.g. "int (*compar)(void *, void *), void *arg" -> ["int (*compar)(void *, void *)", "void *arg"]
def split_param_spec(sig):
  sig = sig.strip()
  if not sig:
    return []

  ret = []
  start_pos = 0
  pos = 0
  nest = 0
  while pos <= len(sig):
    if pos == len(sig) or (sig[pos] == ',' and nest == 0):
      ret.append(sig[start_pos:pos].strip())
      start_pos = pos + 1
    elif sig[pos] == '(':
      nest += 1
    elif sig[pos] == ')':
      nest -= 1
    pos += 1
  return ret


# For one parameter specification of a C function, extract the type and the name into a dictionary.
#
# E.g. "int pipefd[2]" ->    name: "pipefd"
#                            type: "int[2]"
#                   type_and_name: "int pipefd[2]"
#                          vatype: "int *"
#                 vatype_and_name: "int *pipefd"
#
# E.g. "int (*compar)(void *, void *)" ->   name: "compar"
#                                   type, vatype: "int (*)(void *, void *)"
#                 type_and_name, vatype_and_name: "int (*compar)(void *, void *)"
#
def get_arg(sig):
  arg = {}
  sig = sig.strip()
  name_end = len(sig)
  dimension = 0
  # Handle array declaration, e.g. "[2]" in "int pipefd[2]".
  while name_end > 0 and sig[name_end - 1] == ']':
    name_end = sig.rindex('[', 0, name_end)
    dimension += 1
  subscript_begin = name_end
  # Handle function pointer parameter, e.g. "int (*compar)(void *, void *)".
  # Walk backwards from the end, locate the beginning of the parameter list.
  if name_end > 0 and sig[name_end - 1] == ')':
    pos = name_end - 1
    nest = 1
    while pos > 0 and nest > 0:
      if sig[pos - 1] == ')':
        nest += 1
      elif sig[pos - 1] == '(':
        nest -= 1
      pos -= 1
    # pos now points to the parameter list, e.g. "(void *, void *)". Still need to walk back through a ')'.
    name_end = sig.rindex(')', 0, pos)
  # name_end is now the end of the variable name. Let's find the sequence of alphanumeric + '_' characters.
  name_start = name_end
  while name_start > 0 and (sig[name_start - 1].isalnum() or sig[name_start - 1] == '_'):
      name_start -= 1
  # The variable name is between name_start and name_end, the rest is the signature.
  arg['name'] = sig[name_start:name_end].strip()
  arg['type'] = sig[0:name_start].strip() + sig[name_end:].strip()
  arg['type_and_name'] = sig[0:name_start] + arg['name'] + sig[name_end:].strip()  # it's essentially sig
  arg['vatype'] = sig[0:name_start].strip() + dimension * '*' + sig[name_end:subscript_begin].strip()
  arg['vatype_and_name'] = sig[0:name_start] + ('*' * dimension) + arg['name'] + sig[name_end:subscript_begin].strip()
  return arg


# Given a method signature (return type, types and names of parameters)
# as strings, generates various values for the convenience of templates.
# Supports arrays, but does not support function pointers.
#
# For the debug info to work, requires a space before the start,
# e.g. "char *" and not "char*".
#
# E.g.
#           rettype = "int"
#               sig = "const char *pathname, int flags, int pipefd[2], ..."
# ->
#              args = [{
#                                      'name': "pathname",
#                                      'type': "const char *",
#                             'type_and_name': "const char *pathname",
#                                    'vatype': "const char *",
#                           'vatype_and_name': "const char *pathname",
#                      }, {
#                                      'name': "flags"
#                                      'type': "int",
#                             'type_and_name': "int flags",
#                                    'vatype': "int",
#                           'vatype_and_name': "int flags",
#                      }, {
#                                      'name': "pipefd",
#                                      'type': "int[2]",
#                             'type_and_name': "int pipefd[2]",
#                                    'vatype': "int *",
#                           'vatype_and_name': "int *pipefd",
#                      }]
#            vararg = True
#         types_str = "const char *, int, int[2], ..."
#           sig_str = "const char *pathname, int flags, int pipefd[2], ..."
#         names_str = "pathname, flags, pipefd"
#
#  debug_before_fmt ≈ ", pathname = \"%s\", flags = %d"
# debug_before_args ≈ ", pathname, flags"
#   debug_after_fmt ≈ ", ret = %d"
#  debug_after_args ≈ ", ret"
def add_signature_to_dict(rettype, sig, dict):
  dict['args'] = []
  dict['vararg'] = False
  dict['sig_str'] = sig

  dict['debug_before_fmt'] = ''
  dict['debug_before_args'] = ''
  dict['debug_after_fmt'] = ''
  dict['debug_after_args'] = ''

  params = split_param_spec(sig)
  for type_and_name in params:
    type_and_name = type_and_name.strip()
    if type_and_name == '...':
      dict['vararg'] = True
    else:
      arg = get_arg(type_and_name)
      dict['args'].append(arg)

      add_debug_to_dict(arg['type'], arg['name'], dict)

  dict['types_str'] = ', '.join(arg['type'] for arg in dict['args'])
  if dict['vararg']:
    dict['types_str'] += ', ...'
  dict['names_str'] = ', '.join(arg['name'] for arg in dict['args'])

  add_debug_to_dict(rettype, 'ret', dict)

  return dict

def extend_or_add_ifdef_guard(fun_dict, base_guard, extension):
  if 'ifdef_guard' not in fun_dict or fun_dict['ifdef_guard'] is None:
    fun_dict['ifdef_guard'] = base_guard + " && " + extension
  else:
    fun_dict['ifdef_guard'] = fun_dict['ifdef_guard'] + " && " + extension

def extend_before_lines(fun_dict, extension):
  if 'before_lines' not in fun_dict or fun_dict['before_lines'] is None:
    fun_dict['before_lines'] = extension
  else:
    fun_dict['before_lines'] = fun_dict['before_lines'] + extension

# Keep track of the functions we've already generated.
generated={}


# Generate stuff for the given libc / kernel method.
#
# Makes sure to exit with an error if there's a skip() or another
# generate() call somewhere else for the same method.
#
# rettype: method's return type, as string
# funcs: one or more function names, or syscall names if start with "SYS_",
#        as (list of) string
# sig: the function signature, as string
# tpl: the template file to use, default is 'tpl.c'
# msg: the message type, default is the first func
# success: the success condition, default depends on rettype
# dict: further parameters to pass to the template,
#       see the template for their documentation
def generate(rettype, funcs, sig, **dict):
  if type(funcs) == str:
    funcs = [funcs]

  if 'platforms' in dict:
    for platform in dict['platforms']:
      if platform not in supported_platforms:
        print("Unsupported platform: {}".format(platform))
        exit(1)
    if target not in dict['platforms']:
      return

  if 'tpl' not in dict:
    dict['tpl'] = 'tpl.c'
  else:
    dict['tpl'] = 'tpl_' + dict['tpl'] + '.c'
  template = env.get_template(dict['tpl'])

  dict['target'] = target

  if 'msg' not in dict:
    candidate = funcs[0]
    if dict['tpl'] == 'tpl_once.c':
      candidate = 'gen_call'
    if candidate.startswith("__"):
      candidate = candidate.lstrip("_")
    for suffix in ("_nocancel", "64"):
      if candidate.endswith(suffix):
        candidate = candidate[:-len(suffix)]
    dict['msg'] = candidate

  if 'success' not in dict:
    if rettype == 'void':
      dict['success'] = "true /* default success condition for void rettype */"
    elif '*' in rettype:
      dict['success'] = "ret != NULL /* default success condition for pointer rettype */"
    else:
      dict['success'] = "ret >= 0 /* default success condition for scalar rettype */"

  add_signature_to_dict(rettype, sig, dict)

  if 'msg_skip_fields' in dict:
    for msg_skip in dict['msg_skip_fields']:
      if msg_skip != "error_no" and msg_skip not in [arg['name'] for arg in dict['args']]:
        print("generate_interceptors: invalid msg_skip_fields value '" + msg_skip + "' for func '" + funcs[0] + "'")
        exit(1)

  for func in funcs:
    fun_dict = dict.copy()
    if func in generated and func not in ["gettimeofday", "__gettimeofday", "vfork", "SYS_vfork", "__vfork"]:
      print("generate_interceptors: Error: Already generated '" + func + "'", file=sys.stderr)
      exit(1)
    generated[func] = True

    if func[:4] == 'SYS_':
      syscall = True
      outputs = syscall_outputs
      call_ic_orig_func = "ic_orig_" + func
    else:
      syscall = False
      outputs = libc_outputs
      call_ic_orig_func = "get_ic_orig_" + func + "()"
      if target == "darwin" and func in {"__fstat", "__fstatat", "__lstat", "__stat"}:
         if 'ifdef_guard' not in fun_dict or fun_dict['ifdef_guard'] is None:
           fun_dict['ifdef_guard'] = "#if defined(__aarch64__)"
      if func.endswith("_time64") or func in time64_functions:
        extend_or_add_ifdef_guard(fun_dict, glibc_ge(2, 34), "defined(__TIMESIZE) &&  (__TIMESIZE == 32)")
      if target == "linux" and func in remapped_time64_functions \
         and func not in safe_remapped_functions:
        extend_before_lines(fun_dict, ["#if defined(_TIME_BITS) && (_TIME_BITS == 64)",
                                       "  assert(0 && \"intercepting " + func + "() when 64bit time variant is the default is not supported.\");",
                                       "#endif"])
      if target == "linux" and func in remapped_file64_functions \
         and func not in safe_remapped_functions:
        extend_before_lines(fun_dict, ["#if defined(_FILE_OFFSET_BITS) && (_FILE_OFFSET_BITS == 64)",
                                       "  assert(0 && \"intercepting " + func + "() when 64bit offset variant is the default is not supported.\");",
                                       "#endif"])
      if target == 'darwin' and (func.endswith("_unlocked") or func.endswith("_chk") \
                                 or func.endswith("64") or func.endswith("64v2")):
        continue
    for gen in outputs:
      rendered = template.render(gen=gen, rettype=rettype, func=func, syscall=syscall,
                                 call_ic_orig_func=call_ic_orig_func, **fun_dict)
      if rendered:
        with open(outdir + "/gen_" + gen + tmpsuffix, "a") as f:
          f.write(rendered)


# Do not intercept the given libc / kernel method.
#
# This does two things:
#
# Creates a "#define get_ic_orig_func() func" redirect so that you can always
# call get_ic_orig_foo()(args) in the interceptor code, without worrying whether
# that method is overridden or not.
#
# Also makes sure to exit with an error if there's a
# generate() call somewhere else for the same method.
#
# You can pass multiple function names at once.
def skip(*list):
  for func in list:
    generate('', func, '',
             tpl="skip")


def glibc_ge(major, minor):
  return "#if FB_GLIBC_PREREQ({}, {})".format(major, minor)

def not_glibc_ge(major, minor):
  return "#if !FB_GLIBC_PREREQ({}, {})".format(major, minor)

def apple_ge(major, minor):
  return '#if (defined __APPLE__) && (__MAC_OS_X_VERSION_MIN_REQUIRED >= {}{}00)'.format(str(major).zfill(2), str(minor).zfill(2))

# Initialize the output files with their headers.
# Also truncate them before we'll reopen them plenty of times for appending.
for gen in set(libc_outputs + syscall_outputs):
  with open(outdir + "/gen_" + gen + tmpsuffix, "w") as f:
    f.write("/* Auto-generated by generate_interceptors, do not edit */\n\n")


# Intercept vararg open() and friends
generate("int", ["open", "open64", "__open64", "SYS_open", "__open", "__open_nocancel"] + (["__open64_nocancel"] if target == "linux" else ["open$NOCANCEL"]), "const char *pathname, int flags, ...",
         tpl="open",
         msg_skip_fields=["pathname"])
generate("int", ["openat", "openat64", "SYS_openat"] + (["__openat", "openat$NOCANCEL"] if target == "darwin" else []), "int dirfd, const char *pathname, int flags, ...",
         tpl="open",
         msg_skip_fields=["pathname"],
         msg="open")
# FIXME Intercept SYS_openat2 (#889). It's not the same as the glibc symbols __openat[64]_2.

# Intercept open_2 variants
generate("int", ["__open_2", "__open64_2"], "const char *pathname, int flags",
         platforms=["linux"],
         tpl="open",
         msg_skip_fields=["pathname"],
         msg="open")
generate("int", ["__openat_2", "__openat64_2"], "int dirfd, const char *pathname, int flags",
         platforms=["linux"],
         tpl="open",
         msg_skip_fields=["pathname"],
         msg="open")

# Intercept creat()
generate("int", ["creat", "creat64", "SYS_creat"], "const char *pathname, mode_t mode",
         tpl="open",
         before_lines=["const int flags = O_CREAT | O_WRONLY | O_TRUNC;"],
         msg_skip_fields=["pathname"],
         msg_add_fields=["BUILDER_SET_ABSOLUTE_CANONICAL(open, pathname);",
                         "fbbcomm_builder_open_set_flags(&ic_msg, flags);",
                         "fbbcomm_builder_open_set_pre_open_sent(&ic_msg, pre_open_sent);"],
         msg="open")

# Intercept fopen
def open_ack_condition(msg):
  return "success " \
    "&& !is_path_at_locations(fbbcomm_builder_" + msg + "_get_pathname(&ic_msg), fbbcomm_builder_" + msg + "_get_pathname_len(&ic_msg), &read_only_locations) " \
    "&& !is_path_at_locations(fbbcomm_builder_" + msg + "_get_pathname(&ic_msg), fbbcomm_builder_" + msg + "_get_pathname_len(&ic_msg), &ignore_locations)"

# Note: confusingly open()'s and fopen()'s manual uses the word "mode" for something completely different.
generate("FILE *", ["fopen", "fopen64"], "const char *pathname, const char *mode",
         before_lines=["int open_flags = intercept_fopen_mode_to_open_flags_helper(mode);",
                       "bool pre_open_sent = i_am_intercepting && maybe_send_pre_open(AT_FDCWD, pathname, open_flags);"],
         after_lines=["int fd = safe_fileno(ret);",
                      "if (i_am_intercepting && success) clear_notify_on_read_write_state(fd);",
                      "assert(!voidp_set_contains(&popened_streams, ret));"],
         msg="open",
         msg_skip_fields=["pathname", "mode"],
         msg_add_fields=["fbbcomm_builder_open_set_flags(&ic_msg, open_flags);",
                         "if (open_flags & O_CREAT) fbbcomm_builder_open_set_mode(&ic_msg, 0666);",
                         "if (success) fbbcomm_builder_open_set_ret(&ic_msg, fd);",
                         "BUILDER_SET_ABSOLUTE_CANONICAL(open, pathname);",
                         "fbbcomm_builder_open_set_pre_open_sent(&ic_msg, pre_open_sent);"],
         ack_condition=open_ack_condition("open"))

# Intercept freopen
generate("FILE *", ["freopen", "freopen64"], "const char *pathname, const char *mode, FILE *stream",
         before_lines=["int open_flags = intercept_fopen_mode_to_open_flags_helper(mode);",
                       "bool pre_open_sent = i_am_intercepting && maybe_send_pre_open(AT_FDCWD, pathname, open_flags);",
                       "int oldfd = safe_fileno(stream);",
                       "if (i_am_intercepting) clear_notify_on_read_write_state(oldfd);"],
         after_lines=["int newfd = safe_fileno(ret);",
                      "if (i_am_intercepting && success) clear_notify_on_read_write_state(newfd);"],
         msg_skip_fields=["pathname", "mode", "stream"],
         msg_add_fields=["fbbcomm_builder_freopen_set_flags(&ic_msg, intercept_fopen_mode_to_open_flags_helper(mode));",
                         "if (oldfd >= 0) fbbcomm_builder_freopen_set_oldfd(&ic_msg, oldfd);",
                         "if (success) fbbcomm_builder_freopen_set_ret(&ic_msg, newfd);",
                         "BUILDER_SET_ABSOLUTE_CANONICAL(freopen, pathname);",
                         "fbbcomm_builder_freopen_set_pre_open_sent(&ic_msg, pre_open_sent);"],
         ack_condition=open_ack_condition("freopen"))

# High level stream operation only
generate("FILE *", "fdopen", "int fd, const char *mode",
         msg=None,
         after_lines=["assert(!voidp_set_contains(&popened_streams, ret));"])

generate("int", ["shm_open"] + (["__shm_open"] if target == "darwin" else []), "const char *name, int oflag, " + ("mode_t mode" if target == 'linux' else "..."),
         send_ret_on_success=True,
         tpl="shm_open")
generate("int", "shm_unlink", "const char *name",
         tpl="once")
generate("int", "shmget", "key_t key, size_t size, int shmflg",
         tpl="once")
generate("int", ["memfd_create", "SYS_memfd_create"], "const char *name, unsigned int flags",
         platforms=['linux'],
         after_lines=["if (i_am_intercepting && success) clear_notify_on_read_write_state(ret);"],
         send_ret_on_success=True,
         send_msg_on_error=False)
generate("int", ["timerfd_create", "SYS_timerfd_create"], "int clockid, int flags",
         platforms=['linux'],
         after_lines=["if (i_am_intercepting && success) clear_notify_on_read_write_state(ret);"],
         msg_skip_fields=["clockid"],
         send_ret_on_success=True,
         send_msg_on_error=False)
generate("int", ["epoll_create", "SYS_epoll_create"], "int size",
         platforms=['linux'],
         after_lines=["if (i_am_intercepting && success) clear_notify_on_read_write_state(ret);"],
         msg_skip_fields=["size"],
         send_ret_on_success=True,
         send_msg_on_error=False)
generate("int", ["epoll_create1", "SYS_epoll_create1"], "int flags",
         platforms=['linux'],
         after_lines=["if (i_am_intercepting && success) clear_notify_on_read_write_state(ret);"],
         msg="epoll_create",
         send_ret_on_success=True,
         send_msg_on_error=False)
# SYS_eventfd only takes the initial value as parameter.
# glibc's eventfd() corresponds to SYS_eventfd2 which takes an additional flags parameter.
generate("int", "SYS_eventfd", "unsigned int initval",
         platforms=['linux'],
         msg="eventfd",
         after_lines=["if (i_am_intercepting && success) clear_notify_on_read_write_state(ret);"],
         msg_skip_fields=["initval"],
         msg_add_fields=["fbbcomm_builder_eventfd_set_flags(&ic_msg, 0);"],
         send_ret_on_success=True,
         send_msg_on_error=False)
generate("int", ["eventfd", "SYS_eventfd2"], "unsigned int initval, int flags",
         platforms=['linux'],
         after_lines=["if (i_am_intercepting && success) clear_notify_on_read_write_state(ret);"],
         msg_skip_fields=["initval"],
         send_ret_on_success=True,
         send_msg_on_error=False)
generate("int", "signalfd", "int fd, const sigset_t *mask, int flags",
         platforms=['linux'],
         after_lines=["if (i_am_intercepting && success) clear_notify_on_read_write_state(ret);"],
         msg_skip_fields=["mask"],
         send_ret_on_success=True,
         send_msg_on_error=False)
generate("int", "SYS_signalfd", "int fd, const sigset_t *mask, size_t sizemask",
         platforms=['linux'],
         msg="signalfd",
         after_lines=["if (i_am_intercepting && success) clear_notify_on_read_write_state(ret);"],
         msg_skip_fields=["mask", "sizemask"],
         msg_add_fields=["fbbcomm_builder_signalfd_set_flags(&ic_msg, 0);"],
         send_ret_on_success=True,
         send_msg_on_error=False)
generate("int", "SYS_signalfd4", "int fd, const sigset_t *mask, size_t sizemask, int flags",
         platforms=['linux'],
         msg="signalfd",
         after_lines=["if (i_am_intercepting && success) clear_notify_on_read_write_state(ret);"],
         msg_skip_fields=["mask", "sizemask"],
         send_ret_on_success=True,
         send_msg_on_error=False)

# Intercept close and fclose
# Don't call the actual method on the supervisor connection fd.
generate("int", ["close", "SYS_close", "__close_nocancel"] + (["close$NOCANCEL"] if target == "darwin" else ["__close"]), "int fd",
         before_lines=["if (i_am_intercepting) set_notify_on_read_write_state(fd);"])
generate("int", "fclose", "FILE *stream",
         before_lines=["int fd = safe_fileno(stream); /* save it here, we can't do fileno() after the fclose() */",
                       "if (i_am_intercepting) set_notify_on_read_write_state(fd);",
                       "voidp_set_erase(&popened_streams, stream);"],
         send_msg_condition="fd != -1",
         msg="close",
         msg_skip_fields=["stream"],
         msg_add_fields=["fbbcomm_builder_close_set_fd(&ic_msg, fd);"])

# Intercept closefrom and close_range
generate("void", ["closefrom"], "int lowfd",
         platforms=['linux'],
         tpl="closefrom",
         send_msg_on_error=False)
# The manpage says "unsigned int flags" but in unistd.h it's "int flags".
generate("int", ["close_range", "SYS_close_range"], "unsigned int first, unsigned int last, int flags",
         platforms=['linux'],
         tpl="close_range")

# Unlike fclose(), fcloseall() only closes the high level streams and not the underlying fds.
# So we don't need to notify the supervisor. We need to clear the popened_streams set, though.
generate("int", "fcloseall", "",
         platforms=['linux'],
         msg=None,
         after_lines=["voidp_set_clear(&popened_streams);"])

# Intercept opendir, closedir (no need to intercept fdopendir)
generate("DIR *", "opendir", "const char *pathname",
         after_lines=["if (i_am_intercepting && success) clear_notify_on_read_write_state(dirfd(ret));"],
         msg="open",
         msg_skip_fields=["pathname"],
         msg_add_fields=["fbbcomm_builder_open_set_flags(&ic_msg, O_RDONLY | O_CLOEXEC | O_DIRECTORY);",
                         "if (success) fbbcomm_builder_open_set_ret(&ic_msg, get_ic_orig_dirfd()(ret));",
                         "BUILDER_SET_ABSOLUTE_CANONICAL(open, pathname);",
                         "fbbcomm_builder_open_set_pre_open_sent(&ic_msg, false);"],
         ack_condition=open_ack_condition("open"))
generate("DIR *", "__opendir2", "const char *pathname, int flags",
         after_lines=["if (i_am_intercepting && success) clear_notify_on_read_write_state(dirfd(ret));"],
         platforms=['darwin'],
         msg="open",
         msg_skip_fields=["pathname", "flags"],
         msg_add_fields=["fbbcomm_builder_open_set_flags(&ic_msg, flags | O_RDONLY | O_CLOEXEC | O_DIRECTORY);",
                         "if (success) fbbcomm_builder_open_set_ret(&ic_msg, get_ic_orig_dirfd()(ret));",
                         "BUILDER_SET_ABSOLUTE_CANONICAL(open, pathname);",
                         "fbbcomm_builder_open_set_pre_open_sent(&ic_msg, false);"],
         ack_condition=open_ack_condition("open"))
generate("int", "closedir", "DIR *dirp",
         before_lines=["int fd = safe_dirfd(dirp); /* save it here, we can't do dirfd() after the closedir() */"],
         msg="close",
         msg_skip_fields=["dirp"],
         msg_add_fields=["fbbcomm_builder_close_set_fd(&ic_msg, fd);"])

generate("int", "scandir", "const char *dirp, struct dirent ***namelist, int (*filter)(const struct dirent *), int (*compar)(const struct dirent **, const struct dirent **)",
         msg="scandirat",
         msg_skip_fields=["dirp", "namelist", "filter", "compar"],
         msg_add_fields=["BUILDER_SET_ABSOLUTE_CANONICAL(scandirat, dirp);"])
generate("int", "scandir64", "const char *dirp, struct dirent64 ***namelist, int (*filter)(const struct dirent64 *), int (*compar)(const struct dirent64 **, const struct dirent64 **)",
         msg="scandirat",
         msg_skip_fields=["dirp", "namelist", "filter", "compar"],
         msg_add_fields=["BUILDER_SET_ABSOLUTE_CANONICAL(scandirat, dirp);"])
generate("int", "scandirat", "int dirfd, const char *dirp, struct dirent ***namelist, int (*filter)(const struct dirent *), int (*compar)(const struct dirent **, const struct dirent **)",
         platforms=['linux'],
         msg_skip_fields=["dirp", "namelist", "filter", "compar"],
         msg_add_fields=["BUILDER_MAYBE_SET_ABSOLUTE_CANONICAL(scandirat, dirfd, dirp);"])
generate("int", "scandirat64", "int dirfd, const char *dirp, struct dirent64 ***namelist, int (*filter)(const struct dirent64 *), int (*compar)(const struct dirent64 **, const struct dirent64 **)",
         msg_skip_fields=["dirp", "namelist", "filter", "compar"],
         msg_add_fields=["BUILDER_MAYBE_SET_ABSOLUTE_CANONICAL(scandirat, dirfd, dirp);"])

generate("int", ["mkdir", "SYS_mkdir"], "const char *pathname, mode_t mode",
         msg_skip_fields=["pathname"],
         msg_add_fields=["BUILDER_SET_ABSOLUTE_CANONICAL(mkdir, pathname);"])
generate("int", ["mkdirat", "SYS_mkdirat"], "int dirfd, const char *pathname, mode_t mode",
         msg_skip_fields=["pathname"],
         msg_add_fields=["BUILDER_MAYBE_SET_ABSOLUTE_CANONICAL(mkdir, dirfd, pathname);"],
         msg="mkdir")

# Intercept the remove, rmdir, unlink families
generate("int", ["unlink", "remove", "SYS_unlink"] + (["__unlink"] if target == "darwin" else []), "const char *pathname",
         before_lines=["bool pre_open_sent = i_am_intercepting && maybe_send_pre_open(AT_FDCWD, pathname, O_WRONLY | O_TRUNC);"],
         msg_skip_fields=["pathname"],
         msg_add_fields=["BUILDER_SET_ABSOLUTE_CANONICAL(unlink, pathname);",
                         "fbbcomm_builder_unlink_set_pre_open_sent(&ic_msg, pre_open_sent);"])
generate("int", ["unlinkat", "SYS_unlinkat"] + (["__unlinkat"] if target == "darwin" else []), "int dirfd, const char *pathname, int flags",
         before_lines=["bool pre_open_sent = i_am_intercepting && maybe_send_pre_open(dirfd, pathname, O_WRONLY | O_TRUNC);"],
         msg_skip_fields=["pathname"],
         msg_add_fields=["BUILDER_MAYBE_SET_ABSOLUTE_CANONICAL(unlink, dirfd, pathname);",
                         "fbbcomm_builder_unlink_set_pre_open_sent(&ic_msg, pre_open_sent);"],
         msg="unlink")
generate("int", ["rmdir", "SYS_rmdir"] + (["__rmdir"] if target == "darwin" else []), "const char *pathname",
         before_lines=["bool pre_open_sent = i_am_intercepting && maybe_send_pre_open(AT_FDCWD, pathname, O_WRONLY | O_TRUNC);"],
         msg_skip_fields=["pathname"],
         msg_add_fields=["BUILDER_SET_ABSOLUTE_CANONICAL(rmdir, pathname);",
                         "fbbcomm_builder_rmdir_set_pre_open_sent(&ic_msg, pre_open_sent);"])

# Intercept the rename family
generate("int", ["rename", "SYS_rename"] + (["__rename"] if target == "darwin" else []), "const char *oldpath, const char *newpath",
         before_lines=["if (i_am_intercepting) {",
                       "  send_pre_open_without_ack_request(AT_FDCWD, oldpath);",
                       "  send_pre_open(AT_FDCWD, newpath);",
                       "}"],
         msg_skip_fields=["oldpath", "newpath"],
         msg_add_fields=["BUILDER_SET_ABSOLUTE_CANONICAL(rename, oldpath);",
                         "BUILDER_SET_ABSOLUTE_CANONICAL(rename, newpath);"],
         ack_condition="true")
generate("int", ["renameat", "SYS_renameat"] + (["__renameat"] if target == "darwin" else []), "int olddirfd, const char *oldpath, int newdirfd, const char *newpath",
         before_lines=["if (i_am_intercepting) {",
                       "  send_pre_open_without_ack_request(olddirfd, oldpath);",
                       "  send_pre_open(newdirfd, newpath);",
                       "}"],
         msg_skip_fields=["oldpath", "newpath"],
         msg_add_fields=["BUILDER_MAYBE_SET_ABSOLUTE_CANONICAL(rename, olddirfd, oldpath);",
                         "BUILDER_MAYBE_SET_ABSOLUTE_CANONICAL(rename, newdirfd, newpath);"],
         msg="rename",
         ack_condition="true")
for func_guard in [("SYS_renameat2", None), ("renameat2", glibc_ge(2, 28))]:
  (func, ifdef_guard) = func_guard
  generate("int", func, "int olddirfd, const char *oldpath, int newdirfd, const char *newpath, unsigned int flags",
           platforms=['linux'],
           ifdef_guard=ifdef_guard,
           before_lines=["if (i_am_intercepting) {",
                         "  send_pre_open_without_ack_request(olddirfd, oldpath);",
                         "  send_pre_open(newdirfd, newpath);",
                         "}"],
           msg_skip_fields=["oldpath", "newpath"],
           msg_add_fields=["BUILDER_MAYBE_SET_ABSOLUTE_CANONICAL(rename, olddirfd, oldpath);",
                           "BUILDER_MAYBE_SET_ABSOLUTE_CANONICAL(rename, newdirfd, newpath);"],
           msg="rename",
           ack_condition="true")

# Intercept operations that read from a file descriptor.
# FIXME also intercept mmap with PROT_READ
generate("ssize_t", ["read", "SYS_read", "__read_nocancel"] + (["__read"] if target == "linux" else []), "int fd, void *buf, size_t count",
         tpl="read",
         msg_skip_fields=["buf", "count"])
generate("ssize_t", "__read_chk", "int fd, void *buf, size_t count, size_t fortify_size",
         tpl="read",
         msg_skip_fields=["buf", "count", "fortify_size"])
generate("ssize_t", "readv", "int fd, const struct iovec *iov, int iovcnt",
         tpl="read",
         msg_skip_fields=["iov", "iovcnt"])
# Note: SYS_readv is like readv() but takes longs rather than ints.
generate("ssize_t", "SYS_readv", "long fd, const struct iovec *iov, unsigned long iovcnt",
         tpl="read",
         msg_skip_fields=["iov", "iovcnt"])
generate("ssize_t", "pread", "int fd, void *buf, size_t count, off_t offset",
         tpl="read",
         is_pread="true",
         msg_skip_fields=["buf", "count", "offset"])
generate("ssize_t", "__pread_chk", "int fd, void *buf, size_t count, __off_t offset, size_t fortify_size",
         tpl="read",
         is_pread="true",
         msg_skip_fields=["buf", "count", "offset", "fortify_size"])
generate("ssize_t", ["pread64", "__pread64", "__pread64_nocancel", "SYS_pread64"], "int fd, void *buf, size_t count, off64_t offset",
         platforms=['linux'],
         tpl="read",
         is_pread="true",
         msg_skip_fields=["buf", "count", "offset"])
generate("ssize_t", "__pread64_chk", "int fd, void *buf, size_t count, off64_t offset, size_t fortify_size",
         platforms=['linux'],
         tpl="read",
         is_pread="true",
         msg_skip_fields=["buf", "count", "offset", "fortify_size"])
generate("ssize_t", "preadv", "int fd, const struct iovec *iov, int iovcnt, off_t offset",
         tpl="read",
         is_pread="true",
         msg_skip_fields=["iov", "iovcnt", "offset"])
# Note: SYS_preadv is like preadv() but takes longs rather than ints and splits the offset in two.
# The manpage says "These arguments contain, respectively, the low order and high order 32 bits of offset."
# This seems to be true on 32-bit architectures only. On 64-bit they take 64 bits each, making offset_h unused.
generate("ssize_t", "SYS_preadv", "long fd, const struct iovec *iov, unsigned long iovcnt, unsigned long offset_l, unsigned long offset_h",
         tpl="read",
         is_pread="true",
         msg_skip_fields=["iov", "iovcnt", "offset_l", "offset_h"])
generate("ssize_t", "preadv64", "int fd, const struct iovec *iov, int iovcnt, off64_t offset",
         platforms=['linux'],
         tpl="read",
         is_pread="true",
         msg_skip_fields=["iov", "iovcnt", "offset"])
generate("ssize_t", "preadv2", "int fd, const struct iovec *iov, int iovcnt, off_t offset, int flags",
         platforms=['linux'],
         tpl="read",
         is_pread="offset != -1",
         msg_skip_fields=["iov", "iovcnt", "offset", "flags"])
# Note: SYS_preadv2 is like preadv2() but takes longs rather than ints and splits the offset in two.
# The manpage says "These arguments contain, respectively, the low order and high order 32 bits of offset."
# This seems to be true on 32-bit architectures only. On 64-bit they take 64 bits each, making offset_h unused.
generate("ssize_t", "SYS_preadv2", "long fd, const struct iovec *iov, unsigned long iovcnt, unsigned long offset_l, unsigned long offset_h, int flags",
         platforms=['linux'],
         tpl="read",
         before_lines=["off64_t offset = sizeof(long) >= 8 ? offset_l : (off64_t)offset_h << 32 | offset_l;"],
         is_pread="offset != -1",
         msg_skip_fields=["iov", "iovcnt", "offset_l", "offset_h", "flags"])
generate("ssize_t", "preadv64v2", "int fd, const struct iovec *iov, int iovcnt, off64_t offset, int flags",
         platforms=['linux'],
         tpl="read",
         is_pread="offset != -1",
         msg_skip_fields=["iov", "iovcnt", "offset", "flags"])
generate("size_t", ["fread", "fread_unlocked"] + (["__fread"] if target == "darwin" else []), "void *ptr, size_t size, size_t nmemb, FILE *stream",
         tpl="read",
         before_lines=["int fd = safe_fileno(stream);"],
         send_msg_condition="fd != -1",
         success="ret > 0 || !ferror(stream)",
         msg_skip_fields=["ptr", "size", "nmemb", "stream"],
         msg_add_fields=["fbbcomm_builder_read_from_inherited_set_fd(&ic_msg, fd);"])
generate("size_t", ["__fread_chk", "__fread_unlocked_chk"], "void *ptr, size_t fortify_size, size_t size, size_t nmemb, FILE *stream",
         tpl="read",
         before_lines=["int fd = safe_fileno(stream);"],
         send_msg_condition="fd != -1",
         success="ret > 0 || !ferror(stream)",
         msg_skip_fields=["ptr", "fortify_size", "size", "nmemb", "stream"],
         msg_add_fields=["fbbcomm_builder_read_from_inherited_set_fd(&ic_msg, fd);"])
generate("int", ["fgetc", "fgetc_unlocked", "getc", "getc_unlocked", "getw"], "FILE *stream",
         tpl="read",
         success="ret != EOF || !ferror(stream)",
         before_lines=["int fd = safe_fileno(stream);"],
         send_msg_condition="fd != -1",
         msg_skip_fields=["stream"],
         msg_add_fields=["fbbcomm_builder_read_from_inherited_set_fd(&ic_msg, fd);"])
generate("wint_t", ["fgetwc", "fgetwc_unlocked", "getwc", "getwc_unlocked"] + (["__fgetwc"] if target == "darwin" else []), "FILE *stream",
         tpl="read",
         success="ret != WEOF || !ferror(stream)",
         before_lines=["int fd = safe_fileno(stream);"],
         send_msg_condition="fd != -1",
         msg_skip_fields=["stream"],
         msg_add_fields=["fbbcomm_builder_read_from_inherited_set_fd(&ic_msg, fd);"])
generate("char *", ["fgets", "fgets_unlocked"], "char *s, int size, FILE *stream",
         tpl="read",
         success="ret != NULL || !ferror(stream)",
         before_lines=["int fd = safe_fileno(stream);"],
         send_msg_condition="fd != -1",
         msg_skip_fields=["s", "size", "stream"],
         msg_add_fields=["fbbcomm_builder_read_from_inherited_set_fd(&ic_msg, fd);"])
generate("char *", ["__fgets_chk", "__fgets_unlocked_chk"], "char *s, size_t fortify_size, int size, FILE *stream",
         success="ret != NULL || !ferror(stream)",
         tpl="read",
         before_lines=["int fd = safe_fileno(stream);"],
         send_msg_condition="fd != -1",
         msg_skip_fields=["s", "fortify_size", "size", "stream"],
         msg_add_fields=["fbbcomm_builder_read_from_inherited_set_fd(&ic_msg, fd);"])
generate("wchar_t *", ["fgetws", "fgetws_unlocked"], "wchar_t *s, int size, FILE *stream",
         tpl="read",
         success="ret != NULL || !ferror(stream)",
         before_lines=["int fd = safe_fileno(stream);"],
         send_msg_condition="fd != -1",
         msg_skip_fields=["s", "size", "stream"],
         msg_add_fields=["fbbcomm_builder_read_from_inherited_set_fd(&ic_msg, fd);"])
generate("wchar_t *", ["__fgetws_chk", "__fgetws_unlocked_chk"], "wchar_t *s, size_t fortify_size, int size, FILE *stream",
         success="ret != NULL || !ferror(stream)",
         tpl="read",
         before_lines=["int fd = safe_fileno(stream);"],
         send_msg_condition="fd != -1",
         msg_skip_fields=["s", "fortify_size", "size", "stream"],
         msg_add_fields=["fbbcomm_builder_read_from_inherited_set_fd(&ic_msg, fd);"])
generate("int", ["getchar", "getchar_unlocked"], "",
         tpl="read",
         success="ret != EOF || !ferror(stdin)",
         before_lines=["int fd = safe_fileno(stdin);"],
         msg_add_fields=["fbbcomm_builder_read_from_inherited_set_fd(&ic_msg, fd);"])
generate("wint_t", ["getwchar", "getwchar_unlocked"], "",
         tpl="read",
         success="ret != WEOF || !ferror(stdin)",
         before_lines=["int fd = safe_fileno(stdin);"],
         msg_add_fields=["fbbcomm_builder_read_from_inherited_set_fd(&ic_msg, fd);"])
generate("char *", "gets", "char *s",  # should be never used, see man gets
         tpl="read",
         success="ret != NULL || !ferror(stdin)",
         before_lines=["int fd = safe_fileno(stdin);"],
         msg_skip_fields=["s"],
         msg_add_fields=["fbbcomm_builder_read_from_inherited_set_fd(&ic_msg, fd);"])
generate("char *", "__gets_chk", "char *s, size_t fortify_size",  # should be never used, see man gets
         tpl="read",
         success="ret != NULL || !ferror(stdin)",
         before_lines=["int fd = safe_fileno(stdin);"],
         msg_skip_fields=["s", "fortify_size"],
         msg_add_fields=["fbbcomm_builder_read_from_inherited_set_fd(&ic_msg, fd);"])
generate("FB_SSIZE_T", "getline", "char **lineptr, size_t *n, FILE *stream",
         tpl="read",
         success="ret != EOF || !ferror(stream)",
         before_lines=["int fd = safe_fileno(stream);"],
         send_msg_condition="fd != -1",
         msg_skip_fields=["lineptr", "n", "stream"],
         msg_add_fields=["fbbcomm_builder_read_from_inherited_set_fd(&ic_msg, fd);"])
generate("FB_SSIZE_T", ["getdelim"]+ (["__getdelim"] if target == "linux" else []), "char **lineptr, size_t *n, int delim, FILE *stream",
         tpl="read",
         success="ret != EOF || !ferror(stream)",
         before_lines=["int fd = safe_fileno(stream);"],
         send_msg_condition="fd != -1",
         msg_skip_fields=["lineptr", "n", "delim", "stream"],
         msg_add_fields=["fbbcomm_builder_read_from_inherited_set_fd(&ic_msg, fd);"])
# glibc magic, see #343
generate("int", ["__uflow", "__underflow"], "FILE *stream",
         platforms=["linux"],
         tpl="read",
         before_lines=["int fd = safe_fileno(stream);"],
         send_msg_condition="fd != -1",
         success="true",
         msg_skip_fields=["stream"],
         msg_add_fields=["fbbcomm_builder_read_from_inherited_set_fd(&ic_msg, fd);"])
generate("wint_t", ["__wuflow", "__wunderflow"], "FILE *stream",
         platforms=["linux"],
         tpl="read",
         before_lines=["int fd = safe_fileno(stream);"],
         send_msg_condition="fd != -1",
         success="true",
         msg_skip_fields=["stream"],
         msg_add_fields=["fbbcomm_builder_read_from_inherited_set_fd(&ic_msg, fd);"])
# Handle (__isoc99_)?v?f?w?scanf
for i in ['', '__isoc99_']:
  for v in ['', 'v']:
    for f in ['', 'f']:
      for w in ['', 'w']:
        func = i + v + f + w + "scanf"
        if f == '':
          sig_str = ''
          names_str = ''
          stream="stdin"
          before_lines = ["int fd = safe_fileno(stdin);"]
          msg_skip_fields = []
          msg_add_fields = ["fbbcomm_builder_read_from_inherited_set_fd(&ic_msg, fd);"]
        elif f == 'f':
          sig_str = "FILE *stream, "
          names_str = "stream, "
          stream="stream"
          before_lines = ["int fd = safe_fileno(stream);"]
          send_msg_condition="fd != -1",
          msg_skip_fields = ["stream"]
          msg_add_fields = ["fbbcomm_builder_read_from_inherited_set_fd(&ic_msg, fd);"]
        if w:
          sig_str += "const wchar_t *format, "
        else:
          sig_str += "const char *format, "
        success="ret != EOF || !ferror(" + stream + ")"
        names_str += "format, "
        msg_skip_fields += ["format"]
        if v:
          sig_str += "FB_VA_LIST ap"
          call_orig_lines = None
          msg_skip_fields += ["ap"]
        else:
          sig_str += "..."
          call_orig_lines = ["ret = get_ic_orig_" + i + "v" + f + w + "scanf()(" + names_str + "ap);"]
        if i == '__isoc99_':
          platforms=['linux']
        else:
          platforms=['linux','darwin']
        generate("int", func, sig_str,
                 platforms=platforms,
                 tpl="read",
                 success=success,
                 before_lines=before_lines,
                 call_orig_lines=call_orig_lines,
                 msg_skip_fields=msg_skip_fields,
                 msg_add_fields=msg_add_fields)
        if func == "vfscanf":
          generate("int", "__" + func, sig_str,
                   platforms=["linux"],
                   tpl="read",
                   success=success,
                   before_lines=before_lines,
                   call_orig_lines=call_orig_lines,
                   msg_skip_fields=msg_skip_fields,
                   msg_add_fields=msg_add_fields)

for i in ['', '__isoc99_']:
  for v in ['', 'v']:
    for w in ['', 'w']:
      skip(i + v + "s" + w + "scanf")

generate("ssize_t", ["recv", "SYS_recv"], "int fd, void *buf, size_t len, int flags",
         tpl="read",
         msg_skip_fields=["buf", "len", "flags"])
generate("ssize_t", ["__recv"], "int fd, void *buf, size_t len, int flags",
         platforms=['linux'],
         tpl="read",
         msg_skip_fields=["buf", "len", "flags"])
generate("ssize_t", "__recv_chk", "int fd, void *buf, size_t len, size_t fortify_size, int flags",
         tpl="read",
         msg_skip_fields=["buf", "len", "fortify_size", "flags"])
generate("ssize_t", ["recvfrom", "SYS_recvfrom"] + (["__recvfrom"] if target == "darwin" else []), "int fd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen",
         tpl="read",
         msg_skip_fields=["buf", "len", "flags", "src_addr", "addrlen"])
generate("ssize_t", "__recvfrom_chk", "int fd, void *buf, size_t len, size_t fortify_size, int flags, struct sockaddr *src_addr, socklen_t *addrlen",
         tpl="read",
         msg_skip_fields=["buf", "len", "fortify_size", "flags", "src_addr", "addrlen"])
generate("ssize_t", ["recvmsg", "SYS_recvmsg", "__recvmsg64"] + (["__recvmsg"] if target == "darwin" else []), "int fd, struct msghdr *msg, int flags",
         tpl="recvmsg",
         msg_skip_fields=["msg", "flags"])
# FIXME add SYS_recvmmsg_time64
generate("int", ["recvmmsg", "SYS_recvmmsg", "__recvmmsg64"], "int fd, struct mmsghdr *msgvec, unsigned int vlen, int flags, struct timespec *timeout",
         platforms=['linux'],
         tpl="recvmsg",
         msg_skip_fields=["msgvec", "vlen", "flags", "timeout"])

# Intercept operations that write to a file descriptor.
# FIXME also intercept mmap with PROT_WRITE
generate("ssize_t", ["write", "SYS_write", "__write_nocancel"], "int fd, const void *buf, size_t count",
         tpl="write",
         msg_skip_fields=["buf", "count"])
generate("ssize_t", ["__write"], "int fd, const void *buf, size_t count",
         platforms=['linux'],
         tpl="write",
         msg_skip_fields=["buf", "count"])
generate("ssize_t", "writev", "int fd, const struct iovec *iov, int iovcnt",
         tpl="write",
         msg_skip_fields=["iov", "iovcnt"])
# Note: SYS_writev is like writev() but takes longs rather than ints.
generate("ssize_t", "SYS_writev", "long fd, const struct iovec *iov, unsigned long iovcnt",
         tpl="write",
         msg_skip_fields=["iov", "iovcnt"])
generate("ssize_t", "pwrite", "int fd, const void *buf, size_t count, off_t offset",
         tpl="write",
         is_pwrite="true",
         msg_skip_fields=["buf", "count", "offset"])
generate("ssize_t", ["pwrite64", "__pwrite64", "SYS_pwrite64"], "int fd, const void *buf, size_t count, off64_t offset",
         platforms=['linux'],
         tpl="write",
         is_pwrite="true",
         msg_skip_fields=["buf", "count", "offset"])
generate("ssize_t", "pwritev", "int fd, const struct iovec *iov, int iovcnt, off_t offset",
         tpl="write",
         is_pwrite="true",
         msg_skip_fields=["iov", "iovcnt", "offset"])
# Note: SYS_pwritev is like pwritev() but takes longs rather than ints and splits the offset in two.
# The manpage says "These arguments contain, respectively, the low order and high order 32 bits of offset."
# This seems to be true on 32-bit architectures only. On 64-bit they take 64 bits each, making offset_h unused.
generate("ssize_t", "SYS_pwritev", "long fd, const struct iovec *iov, unsigned long iovcnt, unsigned long offset_l, unsigned long offset_h",
         tpl="write",
         is_pwrite="true",
         msg_skip_fields=["iov", "iovcnt", "offset_l", "offset_h"])
generate("ssize_t", "pwritev64", "int fd, const struct iovec *iov, int iovcnt, off64_t offset",
         platforms=['linux'],
         tpl="write",
         is_pwrite="true",
         msg_skip_fields=["iov", "iovcnt", "offset"])
generate("ssize_t", "pwritev2", "int fd, const struct iovec *iov, int iovcnt, off_t offset, int flags",
         platforms=['linux'],
         tpl="write",
         is_pwrite="offset != -1",
         msg_skip_fields=["iov", "iovcnt", "offset", "flags"])
# Note: SYS_pwritev2 is like pwritev2() but takes longs rather than ints and splits the offset in two
# The manpage says "These arguments contain, respectively, the low order and high order 32 bits of offset."
# This seems to be true on 32-bit architectures only. On 64-bit they take 64 bits each, making offset_h unused.
generate("ssize_t", "SYS_pwritev2", "long fd, const struct iovec *iov, unsigned long iovcnt, unsigned long offset_l, unsigned long offset_h, int flags",
         tpl="write",
         before_lines=["off64_t offset = sizeof(long) >= 8 ? offset_l : (off64_t)offset_h << 32 | offset_l;"],
         is_pwrite="offset != -1",
         msg_skip_fields=["iov", "iovcnt", "offset_l", "offset_h", "flags"])
generate("ssize_t", "pwritev64v2", "int fd, const struct iovec *iov, int iovcnt, off64_t offset, int flags",
         platforms=['linux'],
         tpl="write",
         is_pwrite="offset != -1",
         msg_skip_fields=["iov", "iovcnt", "offset", "flags"])
generate("size_t", ["fwrite", "fwrite_unlocked"], "const void *ptr, size_t size, size_t nmemb, FILE *stream",
         tpl="write",
         before_lines=["int fd = safe_fileno(stream);"],
         send_msg_condition="fd != -1",
         success="ret > 0 || !ferror(stream)",
         msg_skip_fields=["ptr", "size", "nmemb", "stream"],
         msg_add_fields=["fbbcomm_builder_write_to_inherited_set_fd(&ic_msg, fd);"])
generate("int", ["fputc", "fputc_unlocked", "putc", "putc_unlocked", "putw"], "int c, FILE *stream",
         tpl="write",
         before_lines=["int fd = safe_fileno(stream);"],
         send_msg_condition="fd != -1",
         msg_skip_fields=["c", "stream"],
         msg_add_fields=["fbbcomm_builder_write_to_inherited_set_fd(&ic_msg, fd);"])
generate("wint_t", ["fputwc", "fputwc_unlocked", "putwc", "putwc_unlocked"], "wchar_t wc, FILE *stream",
         tpl="write",
         success="ret != WEOF",
         before_lines=["int fd = safe_fileno(stream);"],
         send_msg_condition="fd != -1",
         msg_skip_fields=["wc", "stream"],
         msg_add_fields=["fbbcomm_builder_write_to_inherited_set_fd(&ic_msg, fd);"])
generate("int", ["fputs", "fputs_unlocked"], "const char *s, FILE *stream",
         tpl="write",
         before_lines=["int fd = safe_fileno(stream);"],
         send_msg_condition="fd != -1",
         msg_skip_fields=["s", "stream"],
         msg_add_fields=["fbbcomm_builder_write_to_inherited_set_fd(&ic_msg, fd);"])
generate("int", ["fputws", "fputws_unlocked"], "const wchar_t *s, FILE *stream",
         tpl="write",
         before_lines=["int fd = safe_fileno(stream);"],
         send_msg_condition="fd != -1",
         msg_skip_fields=["s", "stream"],
         msg_add_fields=["fbbcomm_builder_write_to_inherited_set_fd(&ic_msg, fd);"])
generate("int", ["putchar", "putchar_unlocked"], "int c",
         tpl="write",
         before_lines=["int fd = safe_fileno(stdout);"],
         msg_skip_fields=["c"],
         msg_add_fields=["fbbcomm_builder_write_to_inherited_set_fd(&ic_msg, fd);"])
generate("wint_t", ["putwchar", "putwchar_unlocked"], "wchar_t wc",
         tpl="write",
         success="ret != WEOF",
         before_lines=["int fd = safe_fileno(stdout);"],
         msg_skip_fields=["wc"],
         msg_add_fields=["fbbcomm_builder_write_to_inherited_set_fd(&ic_msg, fd);"])
generate("int", "puts", "const char *s",
         tpl="write",
         before_lines=["int fd = safe_fileno(stdout);"],
         msg_skip_fields=["s"],
         msg_add_fields=["fbbcomm_builder_write_to_inherited_set_fd(&ic_msg, fd);"])
# glibc magic, see #343
generate("int", "__overflow", "FILE *stream, int ch",
         platforms=["linux"],
         tpl="write",
         before_lines=["int fd = safe_fileno(stream);"],
         send_msg_condition="fd != -1",
         success="true",
         msg_skip_fields=["stream", "ch"],
         msg_add_fields=["fbbcomm_builder_write_to_inherited_set_fd(&ic_msg, fd);"])
generate("wint_t", "__woverflow", "FILE *stream, wint_t ch",
         platforms=["linux"],
         tpl="write",
         before_lines=["int fd = safe_fileno(stream);"],
         send_msg_condition="fd != -1",
         success="true",
         msg_skip_fields=["stream", "ch"],
         msg_add_fields=["fbbcomm_builder_write_to_inherited_set_fd(&ic_msg, fd);"])
# There's no public verror() and verror_at_line().
# Construct the message ourselves so that we don't have to rely on
# gcc's __builtin_apply() with its arbitrarily guessed size parameter.
generate("void", "error", "int status, int errnum, const char *format, ...",
         platforms=['linux'],
         tpl="error",
         before_lines=["int fd = safe_fileno(stderr);"],
         call_orig_lines=["int msglen = vsnprintf(NULL, 0, format, ap);",
                          "va_end(ap);",
                          "char msgbuf[msglen + 1];",
                          "va_start(ap, format);",
                          "vsnprintf(msgbuf, msglen + 1, format, ap);",
                          "get_ic_orig_error()(status, errnum, \"%s\", msgbuf);"],
         msg_skip_fields=["status", "errnum", "format"],
         msg_add_fields=["fbbcomm_builder_write_to_inherited_set_fd(&ic_msg, fd);"])
generate("void", "error_at_line", "int status, int errnum, const char *filename, unsigned int linenum, const char *format, ...",
         platforms=['linux'],
         tpl="error",
         before_lines=["int fd = safe_fileno(stderr);"],
         call_orig_lines=["int msglen = vsnprintf(NULL, 0, format, ap);",
                          "va_end(ap);",
                          "char msgbuf[msglen + 1];",
                          "va_start(ap, format);",
                          "vsnprintf(msgbuf, msglen + 1, format, ap);",
                          "get_ic_orig_error_at_line()(status, errnum, filename, linenum, \"%s\", msgbuf);"],
         msg_skip_fields=["status", "errnum", "filename", "linenum", "format"],
         msg_add_fields=["fbbcomm_builder_write_to_inherited_set_fd(&ic_msg, fd);"])
generate("void", ["herror", "perror"], "const char *s",
         tpl="write",
         before_lines=["int fd = safe_fileno(stderr);"],
         msg_skip_fields=["s"],
         msg_add_fields=["fbbcomm_builder_write_to_inherited_set_fd(&ic_msg, fd);"])
# Handle v?(err|warn)x?
for v in ['', 'v']:
  for e in ['err', 'warn']:
    for x in ['', 'x']:
      func = v + e + x
      if e == 'err':
        sig_str = "int status, "
        names_str = "status, "
        tpl = "error"
        msg_skip_fields = ["status"]
      else:
        sig_str = ''
        names_str = ''
        tpl = "write"
        msg_skip_fields = []
      sig_str += "const char *format, "
      names_str += "format, "
      msg_skip_fields += ["format"]
      if v:
        sig_str += "va_list ap"
        call_orig_lines = None
        msg_skip_fields += ["ap"]
      else:
        sig_str += "..."
        call_orig_lines = ["get_ic_orig_v" + e + x + "()(" + names_str + "ap);"]
      generate("void", func, sig_str,
         tpl=tpl,
         before_lines=["int fd = safe_fileno(stderr);"],
         call_orig_lines=call_orig_lines,
         msg_skip_fields=msg_skip_fields,
         msg_add_fields=["fbbcomm_builder_write_to_inherited_set_fd(&ic_msg, fd);"])
# Handle (__)?v?[fd]?w?printf(_chk)?
for v in ['', 'v']:
  for f in ['', 'f', 'd']:
    for w in ['', 'w']:
      for (c1, c2) in [('', ''), ('__', '_chk')]:
        if f == 'd' and w == 'w':
          # No [v]dwprintf() in glibc
          continue
        func = c1 + v + f + w + "printf" + c2
        if f == '':
          sig_str = ''
          names_str = ''
          before_lines = ["int fd = safe_fileno(stdout);"]
          msg_skip_fields = []
          msg_add_fields = ["fbbcomm_builder_write_to_inherited_set_fd(&ic_msg, fd);"]
        elif f == 'f':
          sig_str = "FILE *stream, "
          names_str = "stream, "
          before_lines = ["int fd = safe_fileno(stream);"]
          send_msg_condition="fd != -1",
          msg_skip_fields = ["stream"]
          msg_add_fields = ["fbbcomm_builder_write_to_inherited_set_fd(&ic_msg, fd);"]
        elif f == 'd':
          sig_str = "int fd, "
          names_str = "fd, "
          before_lines = []
          msg_skip_fields = []
          msg_add_fields = []
        if c1:
          sig_str += "int fortify_flag, "
          names_str += "fortify_flag, "
          msg_skip_fields += ["fortify_flag"]
        if w:
          sig_str += "const wchar_t *format, "
        else:
          sig_str += "const char *format, "
        names_str += "format, "
        msg_skip_fields += ["format"]
        if v:
          sig_str += "FB_VA_LIST ap"
          call_orig_lines = None
          msg_skip_fields += ["ap"]
        else:
          sig_str += "..."
          call_orig_lines = ["ret = get_ic_orig_" + c1 + "v" + f + w + "printf" + c2 + "()(" + names_str + "ap);"]
        generate("int", func, sig_str,
                 tpl="write",
                 before_lines=before_lines,
                 call_orig_lines=call_orig_lines,
                 msg_skip_fields=msg_skip_fields,
                 msg_add_fields=msg_add_fields)
for v in ['', 'v']:
  for w in ['', 'w', 'n']:
    for (c1, c2) in [('', ''), ('__', '_chk')]:
      skip(c1 + v + "s" + w + "printf" + c2)
for v in ['', 'v']:
  for (c1, c2) in [('', ''), ('__', '_chk')]:
    skip(c1 + v + "asprintf" + c2)

for func_guard in [(["send", "SYS_send"], None),
                   ("__send", "#if !defined(__aarch64__) || (defined(__linux__) && !__GLIBC_PREREQ(2, 34))")]:
  (func, ifdef_guard) = func_guard
  generate("ssize_t", func, "int fd, const void *buf, size_t len, int flags",
           platforms=(["linux"] if func == "__send" else ["darwin", "linux"]),
           ifdef_guard=ifdef_guard,
           tpl="write",
           msg_skip_fields=["buf", "len", "flags"])
generate("ssize_t", ["sendto", "SYS_sendto"] + (["__sendto"] if target == "darwin" else []), "int fd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen",
         tpl="write",
         msg_skip_fields=["buf", "len", "flags", "dest_addr", "addrlen"])
generate("ssize_t", ["sendmsg", "SYS_sendmsg", "__sendmsg64"] + (["__sendmsg"] if target == "darwin" else []), "int fd, const struct msghdr *msg, int flags",
         tpl="write",
         msg_skip_fields=["msg", "flags"])
generate("int", ["sendmmsg", "SYS_sendmmsg", "__sendmmsg", "__sendmmsg64"], "int fd, struct mmsghdr *msgvec, unsigned int vlen, int flags",
         platforms=['linux'],
         tpl="write",
         msg_skip_fields=["msgvec", "vlen", "flags"])

# TODO(rbalint) report as read and write without disabling shortcutting
generate("ssize_t", ["sendfile64", "SYS_sendfile64"], "int out_fd, int in_fd, off64_t *offset, size_t count",
         tpl="copy_file_range")
generate("ssize_t", ["sendfile", "SYS_sendfile"], "int out_fd, int in_fd, off_t *offset, size_t count",
         platforms=['linux'],
         tpl="copy_file_range")
generate("int", ["sendfile", "SYS_sendfile"], "int fd, int s, off_t offset, off_t *len, struct sf_hdtr *hdtr, int flags",
         platforms=['darwin'],
         tpl="copy_file_range")
generate("ssize_t", ["copy_file_range", "SYS_copy_file_range"], "int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t len, unsigned int flags",
         platforms=['linux'],
         tpl="copy_file_range")

# Intercept querying or modifying the file offset
# FIXME What's up with llseek, SYS_llseek, _llseek, __lseek, _IO_*seek* etc.?
generate("off_t", ["lseek", "SYS_lseek", "__lseek"], "int fd, off_t offset, int whence",
         tpl="seek",
         modify_offset="offset != 0 || whence != SEEK_CUR",
         msg_skip_fields=["offset", "whence"])
generate("off64_t", "lseek64", "int fd, off64_t offset, int whence",
         tpl="seek",
         modify_offset="offset != 0 || whence != SEEK_CUR",
         msg_skip_fields=["offset", "whence"])
generate("int", "fseek", "FILE *stream, long offset, int whence",
         tpl="seek",
         before_lines=["int fd = safe_fileno(stream);"],
         modify_offset="offset != 0 || whence != SEEK_CUR",
         msg_skip_fields=["stream", "offset", "whence"],
         msg_add_fields=["fbbcomm_builder_seek_in_inherited_set_fd(&ic_msg, fd);"])
generate("int", "fseeko", "FILE *stream, off_t offset, int whence",
         tpl="seek",
         before_lines=["int fd = safe_fileno(stream);"],
         modify_offset="offset != 0 || whence != SEEK_CUR",
         msg_skip_fields=["stream", "offset", "whence"],
         msg_add_fields=["fbbcomm_builder_seek_in_inherited_set_fd(&ic_msg, fd);"])
for func_guard in [("fseeko64", None), ("__fseeko64", glibc_ge(2, 28))]:
  (func, ifdef_guard) = func_guard
  generate("int", func, "FILE *stream, off64_t offset, int whence",
           ifdef_guard=ifdef_guard,
           tpl="seek",
           before_lines=["int fd = safe_fileno(stream);"],
           modify_offset="offset != 0 || whence != SEEK_CUR",
           msg_skip_fields=["stream", "offset", "whence"],
           msg_add_fields=["fbbcomm_builder_seek_in_inherited_set_fd(&ic_msg, fd);"])
generate("long", "ftell", "FILE *stream",
         tpl="seek",
         before_lines=["int fd = safe_fileno(stream);"],
         modify_offset="false",
         msg_skip_fields=["stream"],
         msg_add_fields=["fbbcomm_builder_seek_in_inherited_set_fd(&ic_msg, fd);"])
generate("off_t", "ftello", "FILE *stream",
         tpl="seek",
         before_lines=["int fd = safe_fileno(stream);"],
         modify_offset="false",
         msg_skip_fields=["stream"],
         msg_add_fields=["fbbcomm_builder_seek_in_inherited_set_fd(&ic_msg, fd);"])
for func_guard in [("ftello64", None), ("__ftello64", glibc_ge(2, 28))]:
  (func, ifdef_guard) = func_guard
  generate("off64_t", func, "FILE *stream",
           ifdef_guard=ifdef_guard,
           tpl="seek",
           before_lines=["int fd = safe_fileno(stream);"],
           modify_offset="false",
           msg_skip_fields=["stream"],
           msg_add_fields=["fbbcomm_builder_seek_in_inherited_set_fd(&ic_msg, fd);"])
generate("int", "fgetpos", "FILE *stream, fpos_t *pos",
         tpl="seek",
         before_lines=["int fd = safe_fileno(stream);"],
         modify_offset="false",
         msg_skip_fields=["stream", "pos"],
         msg_add_fields=["fbbcomm_builder_seek_in_inherited_set_fd(&ic_msg, fd);"])
generate("int", "fgetpos64", "FILE *stream, fpos64_t *pos",
         tpl="seek",
         before_lines=["int fd = safe_fileno(stream);"],
         modify_offset="false",
         msg_skip_fields=["stream", "pos"],
         msg_add_fields=["fbbcomm_builder_seek_in_inherited_set_fd(&ic_msg, fd);"])
generate("int", "fsetpos", "FILE *stream, const fpos_t *pos",
         tpl="seek",
         before_lines=["int fd = safe_fileno(stream);"],
         modify_offset="true",
         msg_skip_fields=["stream", "pos"],
         msg_add_fields=["fbbcomm_builder_seek_in_inherited_set_fd(&ic_msg, fd);"])
generate("int", "fsetpos64", "FILE *stream, const fpos64_t *pos",
         tpl="seek",
         before_lines=["int fd = safe_fileno(stream);"],
         modify_offset="true",
         msg_skip_fields=["stream", "pos"],
         msg_add_fields=["fbbcomm_builder_seek_in_inherited_set_fd(&ic_msg, fd);"])
generate("void", "rewind", "FILE *stream",
         tpl="seek",
         before_lines=["int fd = safe_fileno(stream);"],
         modify_offset="true",
         msg_skip_fields=["stream"],
         msg_add_fields=["fbbcomm_builder_seek_in_inherited_set_fd(&ic_msg, fd);"])

# Intercept chdir and fchdir
generate("int", ["chdir", "SYS_chdir"], "const char *pathname",
         msg_skip_fields=["pathname"],
         # Don't make pathname absolute, since the macro would calculate it after calling chdir
         msg_add_fields=["BUILDER_SET_CANONICAL(chdir, pathname);",
                         "if (success) {",
                         "  char* getcwd_ret = get_ic_orig_getcwd()(ic_cwd, FB_PATH_BUFSIZE);",
                         "  (void) getcwd_ret;",
                         "  ic_cwd_len = strlen(ic_cwd);",
                         "}"])
generate("int", ["fchdir", "SYS_fchdir"], "int fd",
         after_lines=["if (success) {",
                      "  char* getcwd_ret = get_ic_orig_getcwd()(ic_cwd, FB_PATH_BUFSIZE);",
                      "  (void) getcwd_ret;",
                      "  ic_cwd_len = strlen(ic_cwd);",
                      "}"])

# Intercept the chmod family
generate("int", ["chmod", "SYS_chmod"] + (["__chmod"] if target == "darwin" else []), "const char *pathname, mode_t mode",
         msg="fchmodat",
         msg_skip_fields=["pathname"],
         msg_add_fields=["BUILDER_SET_ABSOLUTE_CANONICAL(fchmodat, pathname);"])
generate("int", "lchmod", "const char *pathname, mode_t mode",
         msg="fchmodat",
         msg_skip_fields=["pathname"],
         msg_add_fields=["BUILDER_SET_ABSOLUTE_CANONICAL(fchmodat, pathname);",
                         "fbbcomm_builder_fchmodat_set_flags(&ic_msg, AT_SYMLINK_NOFOLLOW);"])
generate("int", "fchmodat", "int fd, const char *pathname, mode_t mode, int flags",
         msg="fchmodat",
         msg_skip_fields=["pathname"],
         msg_add_fields=["BUILDER_MAYBE_SET_ABSOLUTE_CANONICAL(fchmodat, fd, pathname);"])
# Note: SYS_fchmodat doesn't have a flags parameter
generate("int", "SYS_fchmodat", "int fd, const char *pathname, mode_t mode",
         msg="fchmodat",
         msg_skip_fields=["pathname"],
         msg_add_fields=["BUILDER_MAYBE_SET_ABSOLUTE_CANONICAL(fchmodat, fd, pathname);",
                         "fbbcomm_builder_fchmodat_set_flags(&ic_msg, 0);"])
generate("int", ["fchmod", "SYS_fchmod"] + (["__fchmod"] if target == "darwin" else []), "int fd, mode_t mode",
         msg="fchmodat")

# Intercept the chown family
# FIXME SYS_chown32, SYS_lchown32, SYS_fchown32
generate("int", ["chown", "SYS_chown"], "const char *pathname, uid_t owner, gid_t group",
         msg="fchownat",
         msg_skip_fields=["pathname"],
         msg_add_fields=["BUILDER_SET_ABSOLUTE_CANONICAL(fchownat, pathname);"])
generate("int", ["lchown", "SYS_lchown"] + (["__lchown"] if target == "darwin" else []), "const char *pathname, uid_t owner, gid_t group",
         msg="fchownat",
         msg_skip_fields=["pathname"],
         msg_add_fields=["BUILDER_SET_ABSOLUTE_CANONICAL(fchownat, pathname);",
                         "fbbcomm_builder_fchownat_set_flags(&ic_msg, AT_SYMLINK_NOFOLLOW);"])
generate("int", ["fchownat", "SYS_fchownat"], "int fd, const char *pathname, uid_t owner, gid_t group, int flags",
         msg="fchownat",
         msg_skip_fields=["pathname"],
         msg_add_fields=["BUILDER_MAYBE_SET_ABSOLUTE_CANONICAL(fchownat, fd, pathname);"])
generate("int", ["fchown", "SYS_fchown"], "int fd, uid_t owner, gid_t group",
         msg="fchownat")

# Intercept the link, symlink and readlink families
generate("int", ["link", "SYS_link"], "const char *oldpath, const char *newpath",
         msg_skip_fields = ["oldpath", "newpath"],
         msg_add_fields = ["BUILDER_SET_ABSOLUTE_CANONICAL(link, oldpath);",
                           "BUILDER_SET_ABSOLUTE_CANONICAL(link, newpath);"])
generate("int", ["linkat", "SYS_linkat"], "int olddirfd, const char *oldpath, int newdirfd, const char *newpath, int flags",
         msg_skip_fields = ["oldpath", "newpath"],
         msg_add_fields = ["BUILDER_MAYBE_SET_ABSOLUTE_CANONICAL(link, olddirfd, oldpath);",
                           "BUILDER_MAYBE_SET_ABSOLUTE_CANONICAL(link, newdirfd, newpath);"],
         msg="link")
generate("int", ["symlink", "SYS_symlink"], "const char *target, const char *newpath",
         msg_skip_fields = ["newpath"],
         msg_add_fields = ["BUILDER_SET_ABSOLUTE_CANONICAL(symlink, newpath);"])
generate("int", ["symlinkat", "SYS_symlinkat"], "const char *target, int newdirfd, const char *newpath",
         msg_skip_fields = ["newpath"],
         msg_add_fields = ["BUILDER_MAYBE_SET_ABSOLUTE_CANONICAL(symlink, newdirfd, newpath);"],
         msg="symlink")

generate("ssize_t", ["readlink", "SYS_readlink"], "const char *pathname, char *buf, size_t bufsiz",
         tpl="readlink",
         msg_skip_fields=["pathname", "buf"],
         msg_add_fields = ["BUILDER_SET_ABSOLUTE_CANONICAL(readlink, pathname);"])
generate("ssize_t", "__readlink_chk", "const char *pathname, char *buf, size_t bufsiz, size_t fortify_size",
         tpl="readlink",
         msg="readlink",
         msg_skip_fields=["pathname", "buf", "fortify_size"],
         msg_add_fields = ["BUILDER_SET_ABSOLUTE_CANONICAL(readlink, pathname);"])
generate("ssize_t", ["readlinkat", "SYS_readlinkat"], "int dirfd, const char *pathname, char *buf, size_t bufsiz",
         tpl="readlink",
         msg="readlink",
         msg_skip_fields=["pathname", "buf"],
         msg_add_fields = ["BUILDER_MAYBE_SET_ABSOLUTE_CANONICAL(readlink, dirfd, pathname);"])
generate("ssize_t", "__readlinkat_chk", "int dirfd, const char *pathname, char *buf, size_t bufsiz, size_t fortify_size",
         tpl="readlink",
         msg="readlink",
         msg_skip_fields=["pathname", "buf", "fortify_size"],
         msg_add_fields = ["BUILDER_MAYBE_SET_ABSOLUTE_CANONICAL(readlink, dirfd, pathname);"])
# FIXME realpath

# Intercept the stat family
# FIXME SYS_oldstat, SYS_oldlstat, SYS_oldfstat, SYS_osf_*stat*
# mysterious hacks prior to glibc 2.33
generate("int", "__xstat", "int ver, const char *pathname, struct stat *stat_buf",
         platforms=["linux"],
         msg="fstatat",
         msg_skip_fields=["ver", "pathname", "stat_buf"],
         msg_add_fields=["BUILDER_SET_ABSOLUTE_CANONICAL(fstatat, pathname);",
                         "if (success) {",
                         "  fbbcomm_builder_fstatat_set_st_mode(&ic_msg, stat_buf->st_mode);",
                         "  fbbcomm_builder_fstatat_set_st_size(&ic_msg, stat_buf->st_size);",
                         "}"])
generate("int", "__xstat64", "int ver, const char *pathname, struct stat64 *stat_buf",
         platforms=['linux'],
         msg="fstatat",
         msg_skip_fields=["ver", "pathname", "stat_buf"],
         msg_add_fields=["BUILDER_SET_ABSOLUTE_CANONICAL(fstatat, pathname);",
                         "if (success) {",
                         "  fbbcomm_builder_fstatat_set_st_mode(&ic_msg, stat_buf->st_mode);",
                         "  fbbcomm_builder_fstatat_set_st_size(&ic_msg, stat_buf->st_size);",
                         "}"])
generate("int", "__lxstat", "int ver, const char *pathname, struct stat *stat_buf",
         platforms=['linux'],
         msg="fstatat",
         msg_skip_fields=["ver", "pathname", "stat_buf"],
         msg_add_fields=["BUILDER_SET_ABSOLUTE_CANONICAL(fstatat, pathname);",
                         "fbbcomm_builder_fstatat_set_flags(&ic_msg, AT_SYMLINK_NOFOLLOW);",
                         "if (success) {",
                         "  fbbcomm_builder_fstatat_set_st_mode(&ic_msg, stat_buf->st_mode);",
                         "  fbbcomm_builder_fstatat_set_st_size(&ic_msg, stat_buf->st_size);",
                         "}"])
generate("int", "__lxstat64", "int ver, const char *pathname, struct stat64 *stat_buf",
         platforms=['linux'],
         msg="fstatat",
         msg_skip_fields=["ver", "pathname", "stat_buf"],
         msg_add_fields=["BUILDER_SET_ABSOLUTE_CANONICAL(fstatat, pathname);",
                         "fbbcomm_builder_fstatat_set_flags(&ic_msg, AT_SYMLINK_NOFOLLOW);",
                         "if (success) {",
                         "  fbbcomm_builder_fstatat_set_st_mode(&ic_msg, stat_buf->st_mode);",
                         "  fbbcomm_builder_fstatat_set_st_size(&ic_msg, stat_buf->st_size);",
                         "}"])
generate("int", "__fxstat", "int ver, int fd, struct stat *stat_buf",
         platforms=['linux'],
         msg="fstatat",
         msg_skip_fields=["ver", "stat_buf"],
         msg_add_fields=["if (success) {",
                         "  fbbcomm_builder_fstatat_set_st_mode(&ic_msg, stat_buf->st_mode);",
                         "  fbbcomm_builder_fstatat_set_st_size(&ic_msg, stat_buf->st_size);",
                         "}"])
generate("int", "__fxstat64", "int ver, int fd, struct stat64 *stat_buf",
         platforms=['linux'],
         msg="fstatat",
         msg_skip_fields=["ver", "stat_buf"],
         msg_add_fields=["if (success) {",
                         "  fbbcomm_builder_fstatat_set_st_mode(&ic_msg, stat_buf->st_mode);",
                         "  fbbcomm_builder_fstatat_set_st_size(&ic_msg, stat_buf->st_size);",
                         "}"])
generate("int", "__fxstatat", "int ver, int fd, const char *pathname, struct stat *stat_buf, int flags",
         platforms=['linux'],
         msg="fstatat",
         msg_skip_fields=["ver", "pathname", "stat_buf"],
         msg_add_fields=["BUILDER_MAYBE_SET_ABSOLUTE_CANONICAL(fstatat, fd, pathname);",
                         "if (success) {",
                         "  fbbcomm_builder_fstatat_set_st_mode(&ic_msg, stat_buf->st_mode);",
                         "  fbbcomm_builder_fstatat_set_st_size(&ic_msg, stat_buf->st_size);",
                         "}"])
generate("int", "__fxstatat64", "int ver, int fd, const char *pathname, struct stat64 *stat_buf, int flags",
         platforms=['linux'],
         msg="fstatat",
         msg_skip_fields=["ver", "pathname", "stat_buf"],
         msg_add_fields=["BUILDER_MAYBE_SET_ABSOLUTE_CANONICAL(fstatat, fd, pathname);",
                         "if (success) {",
                         "  fbbcomm_builder_fstatat_set_st_mode(&ic_msg, stat_buf->st_mode);",
                         "  fbbcomm_builder_fstatat_set_st_size(&ic_msg, stat_buf->st_size);",
                         "}"])
# cleaned up beginning with glibc 2.33
generate("int", ["stat"] + (["__stat"] if target == "darwin" else []), "const char *pathname, struct stat *stat_buf",
         msg="fstatat",
         msg_skip_fields=["pathname", "stat_buf"],
         msg_add_fields=["BUILDER_SET_ABSOLUTE_CANONICAL(fstatat, pathname);",
                         "if (success) {",
                         "  fbbcomm_builder_fstatat_set_st_mode(&ic_msg, stat_buf->st_mode);",
                         "  fbbcomm_builder_fstatat_set_st_size(&ic_msg, stat_buf->st_size);",
                         "}"])
generate("int", ["stat64", "SYS_stat64", "__stat64_time64"], "const char *pathname, struct stat64 *stat_buf",
         msg="fstatat",
         msg_skip_fields=["pathname", "stat_buf"],
         msg_add_fields=["BUILDER_SET_ABSOLUTE_CANONICAL(fstatat, pathname);",
                         "if (success) {",
                         "  fbbcomm_builder_fstatat_set_st_mode(&ic_msg, stat_buf->st_mode);",
                         "  fbbcomm_builder_fstatat_set_st_size(&ic_msg, stat_buf->st_size);",
                         "}"])
generate("int", ["lstat"] + (["__lstat"] if target == "darwin" else []), "const char *pathname, struct stat *stat_buf",
         msg="fstatat",
         msg_skip_fields=["pathname", "stat_buf"],
         msg_add_fields=["BUILDER_SET_ABSOLUTE_CANONICAL(fstatat, pathname);",
                         "fbbcomm_builder_fstatat_set_flags(&ic_msg, AT_SYMLINK_NOFOLLOW);",
                         "if (success) {",
                         "  fbbcomm_builder_fstatat_set_st_mode(&ic_msg, stat_buf->st_mode);",
                         "  fbbcomm_builder_fstatat_set_st_size(&ic_msg, stat_buf->st_size);",
                         "}"])
generate("int", ["lstat64", "SYS_lstat64", "__lstat64_time64"], "const char *pathname, struct stat64 *stat_buf",
         msg="fstatat",
         msg_skip_fields=["pathname", "stat_buf"],
         msg_add_fields=["BUILDER_SET_ABSOLUTE_CANONICAL(fstatat, pathname);",
                         "fbbcomm_builder_fstatat_set_flags(&ic_msg, AT_SYMLINK_NOFOLLOW);",
                         "if (success) {",
                         "  fbbcomm_builder_fstatat_set_st_mode(&ic_msg, stat_buf->st_mode);",
                         "  fbbcomm_builder_fstatat_set_st_size(&ic_msg, stat_buf->st_size);",
                         "}"])
generate("int", ["fstat", "SYS_newfstat"] + (["__fstat"] if target == "darwin" else []), "int fd, struct stat *stat_buf",
         msg="fstatat",
         msg_skip_fields=["stat_buf"],
         msg_add_fields=["if (success) {",
                         "  fbbcomm_builder_fstatat_set_st_mode(&ic_msg, stat_buf->st_mode);",
                         "  fbbcomm_builder_fstatat_set_st_size(&ic_msg, stat_buf->st_size);",
                         "}"])
for func_guard in [(["fstat64", "SYS_fstat64", "__fstat64_time64"], None),
                   ("__fstat64", glibc_ge(2, 33))]:
  (func, ifdef_guard) = func_guard
  generate("int", func, "int fd, struct stat64 *stat_buf",
           ifdef_guard=ifdef_guard,
           msg="fstatat",
           msg_skip_fields=["stat_buf"],
           msg_add_fields=["if (success) {",
                           "  fbbcomm_builder_fstatat_set_st_mode(&ic_msg, stat_buf->st_mode);",
                           "  fbbcomm_builder_fstatat_set_st_size(&ic_msg, stat_buf->st_size);",
                           "}"])
generate("int", ["fstatat", "SYS_newfstatat"] + (["__fstatat"] if target == "darwin" else []), "int fd, const char *pathname, struct stat *stat_buf, int flags",
         msg="fstatat",
         msg_skip_fields=["pathname", "stat_buf"],
         msg_add_fields=["BUILDER_MAYBE_SET_ABSOLUTE_CANONICAL(fstatat, fd, pathname);",
                         "if (success) {",
                         "  fbbcomm_builder_fstatat_set_st_mode(&ic_msg, stat_buf->st_mode);",
                         "  fbbcomm_builder_fstatat_set_st_size(&ic_msg, stat_buf->st_size);",
                         "}"])
generate("int", ["fstatat64", "SYS_fstatat64", "__fstatat64_time64"], "int fd, const char *pathname, struct stat64 *stat_buf, int flags",
         msg="fstatat",
         msg_skip_fields=["pathname", "stat_buf"],
         msg_add_fields=["BUILDER_MAYBE_SET_ABSOLUTE_CANONICAL(fstatat, fd, pathname);",
                         "if (success) {",
                         "  fbbcomm_builder_fstatat_set_st_mode(&ic_msg, stat_buf->st_mode);",
                         "  fbbcomm_builder_fstatat_set_st_size(&ic_msg, stat_buf->st_size);",
                         "}"])
# since glibc 2.28
for func_guard in [("SYS_statx", None), ("statx", glibc_ge(2, 28))]:
  (func, ifdef_guard) = func_guard
  generate("int", func, "int fd, const char *pathname, int flags, unsigned int mask, struct statx *statx_buf",
           ifdef_guard=ifdef_guard,
           # TODO(rbalint) have separate statx() message with mask also sent
           msg="fstatat",
           # Always query type and mode to let the supervisor have valid stx_mode
           before_lines=["mask |= STATX_TYPE | STATX_MODE | STATX_SIZE;"],
           msg_skip_fields=["pathname", "mask", "statx_buf"],
           msg_add_fields=["BUILDER_MAYBE_SET_ABSOLUTE_CANONICAL(fstatat, fd, pathname);",
                           "if (success) {",
                           "  fbbcomm_builder_fstatat_set_st_mode(&ic_msg, statx_buf->stx_mode);",
                           "  fbbcomm_builder_fstatat_set_st_size(&ic_msg, statx_buf->stx_size);",
                           # TODO(rbalint) handle when type and mode are not returned (e.g. because they are not requested by the intercepted application, just by the interceptor modifying the mask)
                           "}"])
# Treat isfdtype() as a fstat()
generate("int", "isfdtype", "int fd, int fdtype",
         platforms=['linux'],
         msg="fstatat",
         msg_skip_fields=["fdtype"])

# Disable shortcutting on the statfs family
# FIXME SYS_osf_*statfs*
generate("int", ["ustat", "SYS_ustat"], "dev_t dev, struct ustat *ubuf",
         platforms=['linux'],
         msg="statfs",
         msg_skip_fields=["dev", "ubuf"])
# Note: SYS_statfs64 is like statfs64() but also takes a size parameter.
generate("int", "SYS_statfs64", "const char *pathname, size_t sz, struct statfs64 *buf",
         platforms=['linux'],
         msg="statfs",
         msg_skip_fields=["pathname", "sz", "buf"],
         msg_add_fields=["BUILDER_SET_ABSOLUTE_CANONICAL(statfs, pathname);"])
generate("int", ["statfs", "SYS_statfs", "__statfs"], "const char *pathname, struct statfs *buf",
         platforms=['linux'],
         msg="statfs",
         msg_skip_fields=["pathname", "buf"],
         msg_add_fields=["BUILDER_SET_ABSOLUTE_CANONICAL(statfs, pathname);"])
generate("int", "statfs64", "const char *pathname, struct statfs64 *buf",
         platforms=['linux'],
         msg="statfs",
         msg_skip_fields=["pathname", "buf"],
         msg_add_fields=["BUILDER_SET_ABSOLUTE_CANONICAL(statfs, pathname);"])
# Note: SYS_fstatfs64 is like fstatfs64() but also takes a size parameter.
generate("int", "SYS_fstatfs64", "int fd, size_t sz, struct statfs64 *buf",
         platforms=['linux'],
         msg="statfs",
         msg_skip_fields=["fd", "sz", "buf"])
generate("int", ["fstatfs", "SYS_fstatfs"], "int fd, struct statfs *buf",
         platforms=['linux'],
         msg="statfs",
         msg_skip_fields=["fd", "buf"])
generate("int", "fstatfs64", "int fd, struct statfs64 *buf",
         platforms=['linux'],
         msg="statfs",
         msg_skip_fields=["fd", "buf"])
generate("int", "statvfs", "const char *pathname, struct statvfs *buf",
         platforms=['linux'],
         msg="statfs",
         msg_skip_fields=["pathname", "buf"],
         msg_add_fields=["BUILDER_SET_ABSOLUTE_CANONICAL(statfs, pathname);"])
generate("int", "statvfs64", "const char *pathname, struct statvfs64 *buf",
         platforms=['linux'],
         msg="statfs",
         msg_skip_fields=["pathname", "buf"],
         msg_add_fields=["BUILDER_SET_ABSOLUTE_CANONICAL(statfs, pathname);"])
generate("int", "fstatvfs", "int fd, struct statvfs *buf",
         platforms=['linux'],
         msg="statfs",
         msg_skip_fields=["fd", "buf"])
generate("int", "fstatvfs64", "int fd, struct statvfs64 *buf",
         platforms=['linux'],
         msg="statfs",
         msg_skip_fields=["fd", "buf"])

# Intercept lockf */
generate("int", "lockf", "int fd, int cmd, off_t len")
generate("int", "lockf64", "int fd, int cmd, off64_t len",
         platforms=['linux'],
         msg="lockf")

# Intercept fcntl and fcntl64
# The return value is selectively set from tpl_fcntl.c for certain commands.
for func_guard in [(["fcntl", "SYS_fcntl64", "SYS_fcntl", "__fcntl", "__fcntl_time64"], None),
                   ("fcntl64", glibc_ge(2, 28))]:
  (func, ifdef_guard) = func_guard
  generate("int", func, "int fd, int cmd, ...",
           ifdef_guard=ifdef_guard,
           msg="fcntl",
           tpl="fcntl")
generate("int", ["__fcntl_nocancel", "fcntl$NOCANCEL"], "int fd, int cmd, ...",
         platforms=['darwin'],
         msg="fcntl",
         tpl="fcntl")

# Intercept ioctl - cmd is long in glibc, int in the Linux kernel
generate("int", ["ioctl", "__ioctl_time64"] + (["__ioctl"] if target == "darwin" else []), "int fd, unsigned long cmd, ...",
         tpl="ioctl",
         send_ret_on_success=True)
generate("int", "SYS_ioctl", "int fd, unsigned int cmd, ...",
         tpl="ioctl",
         msg="ioctl",
         send_ret_on_success=True)

# Intercept the dup family
generate("int", ["dup", "SYS_dup"], "int oldfd",
         after_lines=["if (i_am_intercepting && success) copy_notify_on_read_write_state(ret, oldfd);"],
         send_ret_on_success=True)
generate("int", ["dup2", "SYS_dup2"], "int oldfd, int newfd",
         tpl="dup2",
         msg="dup3")
generate("int", "__dup2", "int oldfd, int newfd",
         platforms=['linux'],
         tpl="dup2",
         msg="dup3")
generate("int", ["dup3", "SYS_dup3"], "int oldfd, int newfd, int flags",
         platforms=['linux'],
         tpl="dup2",
         msg="dup3")


# Intercept access variants
generate("int", ["access", "SYS_access"], "const char *pathname, int mode",
         msg="faccessat",
         msg_skip_fields=["pathname"],
         msg_add_fields=["BUILDER_SET_ABSOLUTE_CANONICAL(faccessat, pathname);"])
generate("int", ["euidaccess", "eaccess"], "const char *pathname, int mode",
         platforms=['linux'],
         msg="faccessat",
         msg_skip_fields=["pathname"],
         msg_add_fields=["BUILDER_SET_ABSOLUTE_CANONICAL(faccessat, pathname);",
                         "fbbcomm_builder_faccessat_set_flags(&ic_msg, AT_EACCESS);"])
# SYS_faccessat doesn't take a flags parameter.
# glibc's faccessat() corresponds to SYS_faccessat2 which takes an additional flags parameter.
generate("int", "SYS_faccessat", "int dirfd, const char *pathname, int mode",
         msg="faccessat",
         msg_skip_fields=["pathname"],
         msg_add_fields=["BUILDER_MAYBE_SET_ABSOLUTE_CANONICAL(faccessat, dirfd, pathname);",
                         "fbbcomm_builder_faccessat_set_flags(&ic_msg, 0);"])
generate("int", ["faccessat", "SYS_faccessat2"], "int dirfd, const char *pathname, int mode, int flags",
         msg_skip_fields=["pathname"],
         msg_add_fields=["BUILDER_MAYBE_SET_ABSOLUTE_CANONICAL(faccessat, dirfd, pathname);"])

# Intercept the (l)utime family
# FIXME SYS_osf_utimes, SYS_utimensat_time{32,64}, SYS_futimesat_time32
generate("int", ["utime", "SYS_utime", "__utime64"], "const char *pathname, const struct utimbuf *times",
         msg_skip_fields=["pathname", "times"],
         msg_add_fields=["BUILDER_SET_ABSOLUTE_CANONICAL(utime, pathname);",
                         "fbbcomm_builder_utime_set_all_utime_now(&ic_msg, times == NULL);"])
generate("int", ["utimes", "SYS_utimes", "__utimes64"], "const char *pathname, const struct timeval " + ("times[2]" if target == 'linux' else "*times"),
         msg="utime",
         msg_skip_fields=["pathname", "times"],
         msg_add_fields=["BUILDER_SET_ABSOLUTE_CANONICAL(utime, pathname);",
                         "fbbcomm_builder_utime_set_all_utime_now(&ic_msg, times == NULL);"])
generate("int", ["lutimes", "__lutimes64"], "const char *pathname, const struct timeval " + ("times[2]" if target == 'linux' else "*times"),
         msg="utime",
         msg_skip_fields=["pathname", "times"],
         msg_add_fields=["BUILDER_SET_ABSOLUTE_CANONICAL(utime, pathname);",
                         "fbbcomm_builder_utime_set_all_utime_now(&ic_msg, times == NULL);",
                         "fbbcomm_builder_utime_set_flags(&ic_msg, AT_SYMLINK_NOFOLLOW);"])
generate("int", ["utimensat", "__utimensat64"], "int dirfd, const char *pathname, const struct timespec times[2], int flags",
         msg="utime",
         msg_skip_fields=["pathname", "times"],
         msg_add_fields=["BUILDER_MAYBE_SET_ABSOLUTE_CANONICAL(utime, dirfd, pathname);",
                         "fbbcomm_builder_utime_set_all_utime_now(&ic_msg, times == NULL || (times[0].tv_nsec == UTIME_NOW && times[1].tv_nsec == UTIME_NOW));"])
generate("int", "SYS_utimensat", "int dirfd, const char *pathname, const struct timespec times[2], int flags",
         msg="utime",
         msg_skip_fields=["pathname", "times"],
         msg_add_fields=["if (pathname) BUILDER_MAYBE_SET_ABSOLUTE_CANONICAL(utime, dirfd, pathname);",
                         "fbbcomm_builder_utime_set_all_utime_now(&ic_msg, times == NULL || (times[0].tv_nsec == UTIME_NOW && times[1].tv_nsec == UTIME_NOW));"])
generate("int", ["futimesat", "SYS_futimesat", "__futimesat64"], "int dirfd, const char *pathname, const struct timeval times[2]",
         platforms=['linux'],
         msg="utime",
         msg_skip_fields=["pathname", "times"],
         msg_add_fields=["if (pathname) BUILDER_MAYBE_SET_ABSOLUTE_CANONICAL(utime, dirfd, pathname);",
                         "fbbcomm_builder_utime_set_all_utime_now(&ic_msg, times == NULL);"])

# Intercept the futime family
generate("int", ["futimes", "__futimes64"], "int fd, const struct timeval " + ("times[2]" if target == 'linux' else "*times"),
         msg="futime",
         msg_skip_fields=["times"],
         msg_add_fields=["fbbcomm_builder_futime_set_all_utime_now(&ic_msg, times == NULL);"])
generate("int", ["futimens", "__futimens64"], "int fd, const struct timespec times[2]",
         msg="futime",
         msg_skip_fields=["times"],
         msg_add_fields=["fbbcomm_builder_futime_set_all_utime_now(&ic_msg, times == NULL || (times[0].tv_nsec == UTIME_NOW && times[1].tv_nsec == UTIME_NOW));"])

# Intercept dlopen
# The new library's constructor may generate intercepted calls.
# Also note that dlopen() does not set errno, and we always want to notify the supervisor,
# in send_msg_condition we have to skip the default exception on EINTR and EFAULT.
generate("void *", "dlopen", "const char *filename, int flag",
         tpl="dlopen",
         send_msg_condition="filename && !(flag & RTLD_NOLOAD)",
         ack_condition="new_libs_count > 0",
         # msg_add_fields is set in the template
         msg_skip_fields=["filename", "error_no"])
generate("void *", "dlmopen", "Lmid_t lmid, const char *filename, int flag",
         platforms=['linux'],
         tpl="dlopen",
         msg="dlopen",
         send_msg_condition="filename && !(flag & RTLD_NOLOAD)",
         ack_condition="new_libs_count > 0",
         # msg_add_fields is set in the template
         msg_skip_fields=["lmid", "filename", "error_no"])

# Intercept the pipe family
generate("int", ["pipe", "SYS_pipe", "__pipe"], "int pipefd[2]",
         tpl="pipe",
         before_lines=["int flags = 0;"],
         after_lines=["if (i_am_intercepting && success) { clear_notify_on_read_write_state(pipefd[0]); clear_notify_on_read_write_state(pipefd[1]); }"],
         msg_skip_fields=["pipefd"],
         msg_add_fields=["if (success) {",
                         "  fbbcomm_builder_pipe_fds_set_fd0(&ic_msg, pipefd[0]);",
                         "  fbbcomm_builder_pipe_fds_set_fd1(&ic_msg, pipefd[1]);",
                         "}"])
generate("int", ["pipe2", "SYS_pipe2"], "int pipefd[2], int flags",
         platforms=['linux'],
         tpl="pipe",
         after_lines=["if (i_am_intercepting && success) { clear_notify_on_read_write_state(pipefd[0]); clear_notify_on_read_write_state(pipefd[1]); }"],
         msg_skip_fields=["pipefd"],
         msg_add_fields=["if (success) {",
                         "  fbbcomm_builder_pipe_fds_set_fd0(&ic_msg, pipefd[0]);",
                         "  fbbcomm_builder_pipe_fds_set_fd1(&ic_msg, pipefd[1]);",
                         "}"])

# Intercept the truncate family
generate("int", ["truncate64", "SYS_truncate64"], "const char *pathname, off64_t len",
         before_lines=["if (i_am_intercepting) maybe_send_pre_open(AT_FDCWD, pathname, O_WRONLY | O_TRUNC);"],
         platforms=['linux'],
         msg="truncate",
         msg_skip_fields=["pathname", "len"],
         msg_add_fields=["BUILDER_SET_ABSOLUTE_CANONICAL(truncate, pathname);"])
generate("int", ["truncate", "SYS_truncate"], "const char *pathname, off_t len",
         before_lines=["if (i_am_intercepting) maybe_send_pre_open(AT_FDCWD, pathname, O_WRONLY | O_TRUNC);"],
         msg_skip_fields=["pathname", "len"],
         msg_add_fields=["BUILDER_SET_ABSOLUTE_CANONICAL(truncate, pathname);"])
# For our purposes, ftruncate() is just like pwrite(): modifies the file's contents
# in a way other than simply writing sequential data at the current offset.
generate("int", ["ftruncate64", "SYS_ftruncate64"], "int fd, off64_t len",
         platforms=['linux'],
         tpl="write",
         is_pwrite="true",
         msg_skip_fields=["len"])
generate("int", ["ftruncate", "SYS_ftruncate"], "int fd, off_t len",
         tpl="write",
         is_pwrite="true",
         msg_skip_fields=["len"])

# Intercept the fallocate() family
generate("int", "posix_fallocate", "int fd, off_t offset, off_t len",
         platforms=['linux'],
         is_pwrite="true",
         msg_skip_fields=["offset", "len"],
         tpl="write")
generate("int", "posix_fallocate64", "int fd, off64_t offset, off64_t len",
         platforms=['linux'],
         is_pwrite="true",
         msg_skip_fields=["offset", "len"],
         tpl="write")
generate("int", ["fallocate", "SYS_fallocate"], "int fd, int mode, off_t offset, off_t len",
         platforms=['linux'],
         is_pwrite="true",
         msg_skip_fields=["mode", "offset", "len"],
         tpl="write")
generate("int", "fallocate64", "int fd, int mode, off64_t offset, off64_t len",
         platforms=['linux'],
         is_pwrite="true",
         msg_skip_fields=["mode", "offset", "len"],
         tpl="write")

# Intercept the mkstemp family
# Note: these update the pathname template in place.
generate("int", ["mkstemp", "mkstemp64"], "char *pathname",
         after_lines=["if (i_am_intercepting && success) clear_notify_on_read_write_state(ret);"],
         send_msg_on_error=False,
         msg="open",
         msg_skip_fields=["pathname"],
         msg_add_fields=["fbbcomm_builder_open_set_flags(&ic_msg, O_RDWR | O_CREAT | O_EXCL);",
                         "fbbcomm_builder_open_set_mode(&ic_msg, 0600);",
                         "BUILDER_SET_ABSOLUTE_CANONICAL(open, pathname);",
                         "fbbcomm_builder_open_set_pre_open_sent(&ic_msg, false);"
                         "fbbcomm_builder_open_set_tmp_file(&ic_msg, true);"],
         send_ret_on_success=True,
         ack_condition="true")
generate("int", ["mkostemp", "mkostemp64"], "char *pathname, int flags",
         after_lines=["if (i_am_intercepting && success) clear_notify_on_read_write_state(ret);"],
         send_msg_on_error=False,
         msg="open",
         msg_skip_fields=["pathname", "flags"],
         msg_add_fields=["fbbcomm_builder_open_set_flags(&ic_msg, O_RDWR | O_CREAT | O_EXCL | (flags & (O_APPEND | O_CLOEXEC | O_SYNC)));",
                         "fbbcomm_builder_open_set_mode(&ic_msg, 0600);",
                         "BUILDER_SET_ABSOLUTE_CANONICAL(open, pathname);",
                         "fbbcomm_builder_open_set_pre_open_sent(&ic_msg, false);"
                         "fbbcomm_builder_open_set_tmp_file(&ic_msg, true);"],
         send_ret_on_success=True,
         ack_condition="true")
generate("int", ["mkstemps", "mkstemps64"], "char *pathname, int suffixlen",
         after_lines=["if (i_am_intercepting && success) clear_notify_on_read_write_state(ret);"],
         send_msg_on_error=False,
         msg="open",
         msg_skip_fields=["pathname", "suffixlen"],
         msg_add_fields=["fbbcomm_builder_open_set_flags(&ic_msg, O_RDWR | O_CREAT | O_EXCL);",
                         "fbbcomm_builder_open_set_mode(&ic_msg, 0600);",
                         "BUILDER_SET_ABSOLUTE_CANONICAL(open, pathname);",
                         "fbbcomm_builder_open_set_pre_open_sent(&ic_msg, false);"
                         "fbbcomm_builder_open_set_tmp_file(&ic_msg, true);"],
         send_ret_on_success=True,
         ack_condition="true")
generate("int", ["mkostemps", "mkostemps64"], "char *pathname, int suffixlen, int flags",
         after_lines=["if (i_am_intercepting && success) clear_notify_on_read_write_state(ret);"],
         send_msg_on_error=False,
         msg="open",
         msg_skip_fields=["pathname", "suffixlen", "flags"],
         msg_add_fields=["fbbcomm_builder_open_set_flags(&ic_msg, O_RDWR | O_CREAT | O_EXCL | (flags & (O_APPEND | O_CLOEXEC | O_SYNC)));",
                         "fbbcomm_builder_open_set_mode(&ic_msg, 0600);",
                         "BUILDER_SET_ABSOLUTE_CANONICAL(open, pathname);",
                         "fbbcomm_builder_open_set_pre_open_sent(&ic_msg, false);"
                         "fbbcomm_builder_open_set_tmp_file(&ic_msg, true);"],
         send_ret_on_success=True,
         ack_condition="true")
generate("char *", "mkdtemp", "char *pathname",
         send_msg_on_error=False,
         msg="mkdir",
         msg_skip_fields=["pathname"],
         msg_add_fields=["fbbcomm_builder_mkdir_set_mode(&ic_msg, 0700);",
                         "BUILDER_SET_ABSOLUTE_CANONICAL(mkdir, pathname);",
                         "fbbcomm_builder_mkdir_set_tmp_dir(&ic_msg, true);"],
         ack_condition="true")
# FIXME needs support on the supervisor for starting to track an fd without knowing the backing file
generate("FILE *", ["tmpfile", "tmpfile64"], "",
         tpl="once")
# "Never use these functions"
# FIXME Supporting them would require supervisor work, or two messages (exclusive open, then close)
generate("char *", ["mktemp"]+ (["__mktemp"] if target == "linux" else []), "char *template",
         success="template[0] != '\\0'",
         msg_skip_fields=["template"],
         send_msg_on_error=False,
         msg_add_fields=["BUILDER_SET_ABSOLUTE_CANONICAL(mktemp, template);"],
         diagnostic_ignored = ["-Warray-parameter="])
generate("char *", ["tmpnam"] + (["tmpnam_r"] if target == "linux" else []), "char *template",
         msg="mktemp",
         msg_skip_fields=["template"],
         send_msg_on_error=False,
         msg_add_fields=["BUILDER_SET_ABSOLUTE_CANONICAL(mktemp, template);"],
         diagnostic_ignored = ["-Warray-parameter="])
generate("char *", ["tempnam"], "const char *dir, const char *pfx",
         tpl="once")

generate("int", ["kqueue"], "",
         platforms=["darwin"],
         send_ret_on_success=True)

# Disable shortcutting on chroot
generate("int", ["chroot", "SYS_chroot"], "const char *path",
         tpl="once")

# Skip bridge between low and high level io
skip("fileno", "dirfd")

# Skip directory reading
skip("readdir", "readdir64", "readdir_r", "readdir64_r", "SYS_readdir", "rewinddir", "seekdir", "telldir")
skip("getdents", "SYS_getdents", "getdents64", "SYS_getdents64", "getdirentries", "getdirentries64")
# FIXME implement scandir(at)(64)

# Disable shortcutting on mknod
# mysterious hacks prior to glibc 2.33 (also note the pointer "dev_t *")
generate("int", "__xmknod", "int ver, const char *path, mode_t mode, dev_t *dev",
         platforms=["linux"],
         tpl="once")
generate("int", "__xmknodat", "int ver, int dirfd, const char *path, mode_t mode, dev_t *dev",
         platforms=["linux"],
         tpl="once")
# cleaned up beginning with glibc 2.33
generate("int", ["mknod", "SYS_mknod"], "const char *path, mode_t mode, dev_t dev",
         tpl="once")
generate("int", ["mknodat", "SYS_mknodat"], "int dirfd, const char *path, mode_t mode, dev_t dev",
         platforms=['linux'],
         tpl="once")
generate("int", ["mknodat", "SYS_mknodat"], "int dirfd, const char *path, mode_t mode, dev_t dev",
         platforms=['darwin'],
         ifdef_guard=apple_ge(13, 0),
         tpl="once")

# Disable shortcutting on mkfifo
generate("int", "mkfifo", "const char *pathname, mode_t mode",
         tpl="once")
generate("int", "mkfifoat", "int dirfd, const char *pathname, mode_t mode",
         platforms=['linux'],
         tpl="once")

# Intercept the exit family.
# - _exit() and _Exit() terminate the process immediately, no exit handlers are run.
#   So we need to modify the supervisor about the exit code and resource usage before execuing it.
# - exit() calls the atexit() / on_exit() handlers first. We'll notify the supervisor using the
#   hopefully last on_exit() handler, which is our on_exit_handler() method.
# - quick_exit() calls the at_quick_exit() handlers, which, similarly to the atexit() handlers,
#   do not receive the exit status. So notify the supervisor before calling this method, just like
#   for _exit().
generate("void", ["exit"] + (["__exit"] if target == "darwin" else []), "int status",
         tpl="exit",
         no_saved_errno=True)
generate("void", ["_exit", "_Exit", "SYS_exit"], "int status",
         tpl="_exit",
         no_saved_errno=True)
generate("void", "quick_exit", "int status",
         platforms=['linux'],
         tpl="_exit",
         no_saved_errno=True)
# No wrapper for exit_group() in glibc 2.30.
skip("exit_group")
generate("void", "SYS_exit_group", "int status",
         tpl="_exit")

# Intercept fork() and variants
generate("pid_t", ["fork", "SYS_fork"], "",
         tpl="fork")
# _Fork is glibc specific
# TODO(rbalint) __fork is not intercepted on OS X because fork() also calls it and intercepting
# only __fork hangs "debugging with trace markers and report generation" test
generate("pid_t", ["__fork", "_Fork"], "",
         platforms=['linux'],
         tpl="_fork")
generate("pid_t", ["vfork", "SYS_vfork", "__vfork"], "",
         ifdef_guard=glibc_ge(2, 34),
         tpl="_fork")
# NOTE: Mapping to fork() breaks the ABI since the fork handlers were not supposed to run
#       OTOH the fork handlers notify the supervisor about the call.
generate("pid_t", ["vfork", "SYS_vfork", "__vfork"], "",
         ifdef_guard=not_glibc_ge(2, 34),
         tpl="fork")

# Intercept system
generate("int", "system", "const char *cmd",
         tpl="system")

# Intercept popen and pclose
generate("FILE *", "popen", "const char *cmd, const char *type",
         tpl="popen")
generate("int", "pclose", "FILE *stream",
         tpl="pclose",
         # Maybe we pclose() an fopen()ed file, in that case send the "close" message (from tpl_pclose.c)
         # but don't send the "pclose" message. See #642 for more info.
         send_msg_condition="was_popened && (success || (errno != EINTR && errno != EFAULT))",
         msg_skip_fields=["stream"],
         msg_add_fields=["fbbcomm_builder_pclose_set_fd(&ic_msg, fd);"],
         send_ret_on_success=True,
         ack_condition="true")

# Intercept the posix_spawn family
posix_spawn_params =  "pid_t *pid, const char *file, " \
  + "const posix_spawn_file_actions_t *file_actions, " \
  + "const posix_spawnattr_t *attrp, " \
  + "char *const argv[], char *const envp[]"
generate("int", ["posix_spawn", "posix_spawnp"] + (["__posix_spawn"] if target == "darwin" else []), posix_spawn_params,
         tpl="posix_spawn",
         success="ret == 0")
generate("int", ["pidfd_spawn", "pidfd_spawnp"], posix_spawn_params,
         tpl="posix_spawn",
         ifdef_guard=glibc_ge(2, 39),
         success="ret == 0")

generate("int", ["posix_spawn_file_actions_init", "posix_spawn_file_actions_destroy"], "posix_spawn_file_actions_t *file_actions",
         tpl="posix_spawn_file_actions",
         success="ret == 0")
generate("int", "posix_spawn_file_actions_addopen", "posix_spawn_file_actions_t *file_actions, int fd, const char *pathname, int flags, mode_t mode",
         tpl="posix_spawn_file_actions",
         success="ret == 0")
generate("int", "posix_spawn_file_actions_addclose", "posix_spawn_file_actions_t *file_actions, int fd",
         tpl="posix_spawn_file_actions",
         success="ret == 0")
generate("int", "posix_spawn_file_actions_addclosefrom_np", "posix_spawn_file_actions_t *file_actions, int lowfd",
         platforms=['linux'],
         tpl="posix_spawn_file_actions",
         success="ret == 0")
generate("int", "posix_spawn_file_actions_adddup2", "posix_spawn_file_actions_t *file_actions, int oldfd, int newfd",
         tpl="posix_spawn_file_actions",
         success="ret == 0")
generate("int", "posix_spawn_file_actions_addchdir_np", "posix_spawn_file_actions_t *file_actions, const char *pathname",
         ifdef_guard=glibc_ge(2, 29) + " || defined(__APPLE__)",
         tpl="posix_spawn_file_actions",
         success="ret == 0")
generate("int", "posix_spawn_file_actions_addfchdir_np", "posix_spawn_file_actions_t *file_actions, int fd",
         ifdef_guard=glibc_ge(2, 29) + " || defined(__APPLE__)",
         tpl="posix_spawn_file_actions",
         success="ret == 0")
generate("int", "posix_spawn_file_actions_addinherit_np", "posix_spawn_file_actions_t *file_actions, int filedes",
         platforms=['darwin'],
         tpl="posix_spawn_file_actions",
         success="ret == 0")
generate("int", "posix_spawn_file_actions_addtcsetpgrp_np", "posix_spawn_file_actions_t *file_actions, int filedes",
         platforms=['linux'],
         ifdef_guard=glibc_ge(2, 35),
         tpl="posix_spawn_file_actions",
         success="ret == 0")

# Insert a trace marker for clone and pthread_create
generate("int", ["clone", "__clone"], "int (*fn)(void *), void *stack, int flags, void *arg, ...",
         platforms=['linux'],
         tpl="clone")
# Don't bother spelling out SYS_clone's parameters, it's heavily architecture dependent
generate("int", "SYS_clone", "...",
         platforms=['linux'],
         tpl="clone")
generate("int", "pthread_create", "pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg",
         tpl="pthread_create")

# Intercept the wait family
# Need to wait for the supervisor to ACK that it has processed what the child had done
# Lock only for the communication, see #338
# FIXME SYS_osf_old_wait, SYS_osf_wait4, SYS_osf_waitid
generate("pid_t", ["wait"], "int *wstatus",
         success="ret > 0",
         before_lines=["int wstatus_fallback;",
                       "if (!wstatus) wstatus = &wstatus_fallback;"],
         global_lock='after',
         send_msg_on_error=False,
         msg="wait",
         msg_skip_fields=["wstatus"],
         msg_add_fields=["fbbcomm_builder_wait_set_pid(&ic_msg, ret);",
                         "fbbcomm_builder_wait_set_wstatus(&ic_msg, *wstatus);"],
         ack_condition="true")
generate("pid_t", ["__wait"], "int *wstatus",
         platforms=['linux'],
         success="ret > 0",
         before_lines=["int wstatus_fallback;",
                       "if (!wstatus) wstatus = &wstatus_fallback;"],
         global_lock='after',
         send_msg_on_error=False,
         msg="wait",
         msg_skip_fields=["wstatus"],
         msg_add_fields=["fbbcomm_builder_wait_set_pid(&ic_msg, ret);",
                         "fbbcomm_builder_wait_set_wstatus(&ic_msg, *wstatus);"],
         ack_condition="true")
generate("pid_t", ["waitpid", "SYS_waitpid"], "pid_t pid, int *wstatus, int options",
         global_lock='after',
         before_lines=["int wstatus_fallback;",
                       "if (!wstatus) wstatus = &wstatus_fallback;"],
         success="ret > 0",
         send_msg_on_error=False,
         send_msg_condition="success && !WIFSTOPPED(*wstatus) && !WIFCONTINUED(*wstatus)",
         msg="wait",
         msg_skip_fields=["pid", "wstatus", "options"],
         msg_add_fields=["fbbcomm_builder_wait_set_pid(&ic_msg, ret);",
                         "fbbcomm_builder_wait_set_wstatus(&ic_msg, *wstatus);"],
         ack_condition="true")
generate("pid_t", ["__waitpid"], "pid_t pid, int *wstatus, int options",
         platforms=['linux'],
         global_lock='after',
         before_lines=["int wstatus_fallback;",
                       "if (!wstatus) wstatus = &wstatus_fallback;"],
         success="ret > 0",
         send_msg_on_error=False,
         send_msg_condition="success && !WIFSTOPPED(*wstatus) && !WIFCONTINUED(*wstatus)",
         msg="wait",
         msg_skip_fields=["pid", "wstatus", "options"],
         msg_add_fields=["fbbcomm_builder_wait_set_pid(&ic_msg, ret);",
                         "fbbcomm_builder_wait_set_wstatus(&ic_msg, *wstatus);"],
         ack_condition="true")
generate("pid_t", ["wait3", "__wait3_time64"], "int *wstatus, int options, struct rusage *rusage",
         platforms=["linux"],  # OS X Libc calls wait4 which is also intercepted
         global_lock='after',
         before_lines=["int wstatus_fallback;",
                       "if (!wstatus) wstatus = &wstatus_fallback;"],
         success="ret > 0",
         send_msg_on_error=False,
         send_msg_condition="success && !WIFSTOPPED(*wstatus) && !WIFCONTINUED(*wstatus)",
         msg="wait",
         msg_skip_fields=["wstatus", "options", "rusage"],
         msg_add_fields=["fbbcomm_builder_wait_set_pid(&ic_msg, ret);",
                         "fbbcomm_builder_wait_set_wstatus(&ic_msg, *wstatus);"],
         ack_condition="true")
# __wait4 hangs the tests on MacOS
generate("pid_t", ["wait4", "SYS_wait4", "__wait4_time64"], "pid_t pid, int *wstatus, int options, struct rusage *rusage",
         global_lock='after',
         before_lines=["int wstatus_fallback;",
                       "if (!wstatus) wstatus = &wstatus_fallback;"],
         success="ret > 0",
         send_msg_on_error=False,
         send_msg_condition="success && !WIFSTOPPED(*wstatus) && !WIFCONTINUED(*wstatus)",
         msg="wait",
         msg_skip_fields=["pid", "wstatus", "options", "rusage"],
         msg_add_fields=["fbbcomm_builder_wait_set_pid(&ic_msg, ret);",
                         "fbbcomm_builder_wait_set_wstatus(&ic_msg, *wstatus);"],
         ack_condition="true")
# Note: See BUGS in the waitid(2) manual page for the infop==NULL case.
generate("int", "waitid", "idtype_t idtype, id_t id, siginfo_t *infop, int options",
         global_lock='after',
         before_lines=["siginfo_t infop_fallback;",
                       "if (!infop) infop = &infop_fallback;"],
         send_msg_on_error=False,
         send_msg_condition="success && !((options & WNOHANG) && (infop->si_pid == 0)) && infop->si_code != CLD_STOPPED && infop->si_code != CLD_TRAPPED && infop->si_code != CLD_CONTINUED",
         msg="wait",
         msg_skip_fields=["idtype", "id", "infop", "options"],
         msg_add_fields=["fbbcomm_builder_wait_set_pid(&ic_msg, infop->si_pid);",
                         "fbbcomm_builder_wait_set_si_code(&ic_msg, infop->si_code);",
                         "fbbcomm_builder_wait_set_si_status(&ic_msg, infop->si_status);"],
         ack_condition="true")
# Note: Takes one more parameter than the libc wrapper.
generate("int", "SYS_waitid", "idtype_t idtype, id_t id, siginfo_t *infop, int options, struct rusage *usage",
         global_lock='after',
         before_lines=["siginfo_t infop_fallback;",
                       "if (!infop) infop = &infop_fallback;"],
         send_msg_on_error=False,
         send_msg_condition="success && !((options & WNOHANG) && (infop->si_pid == 0)) && infop->si_code != CLD_STOPPED && infop->si_code != CLD_TRAPPED && infop->si_code != CLD_CONTINUED",
         msg="wait",
         msg_skip_fields=["idtype", "id", "infop", "options", "usage"],
         msg_add_fields=["fbbcomm_builder_wait_set_pid(&ic_msg, infop->si_pid);",
                         "fbbcomm_builder_wait_set_si_code(&ic_msg, infop->si_code);",
                         "fbbcomm_builder_wait_set_si_status(&ic_msg, infop->si_status);"],
         ack_condition="true")

# Intercept the exec family
# FIXME SYS_osf_execve
generate("int", ["execl", "execlp"], "const char *file, const char *arg, ...",
         tpl="exec")
# Note: execlpe exists on some systems, but not in glibc
generate("int", "execle", "const char *file, const char *arg, ...",
         tpl="exec")
generate("int", ["execv", "execvp"], "const char *file, char *const argv[]",
         tpl="exec")
generate("int", ["execve", "SYS_execve"] + (["__execve"] if target == "darwin" else []), "const char *file, char *const argv[], char *const envp[]",
         tpl="exec")
generate("int", "execvpe", "const char *file, char *const argv[], char *const envp[]",
         platforms=['linux'],
         tpl="exec")
# Note: execveat() appeared in glibc 2.34.
generate("int", ["execveat", "SYS_execveat"], "int dirfd, const char *file, char *const argv[], char *const envp[], int flags",
         platforms=['linux'],
         tpl="exec")
generate("int", "fexecve", "int fd, char *const argv[], char *const envp[]",
         platforms=['linux'],
         tpl="exec")

# Signal handling
# FIXME SYS_osf_signal, SYS_osf_old_sigsetmask, SYS_osf_old_sigaction
generate("sighandler_t", ["signal", "sigset"], "int signum, " + ("sighandler_t" if target == 'linux' else 'sig_t') + " handler",
         tpl="signal",
         success="1")
generate("long int", ["SYS_signal"], "int signum, sighandler_t handler",
         tpl="signal",
         success="1")
generate("int", ["sigaction", "SYS_sigaction"], "int signum, const struct sigaction *act, struct sigaction *oldact",
         tpl="signal")
# TODO(rbalint) intercepting __sigaction breaks "parallel make" test on OS X
generate("int", ["__sigaction"], "int signum, const struct sigaction *act, struct sigaction *oldact",
         platforms=['linux'],
         tpl="signal")

# futex() doesn't have a glibc wrapper, pthread_mutex_[un]lock() maps into syscall(SYS_futex, ...).
# Don't need to notify the supervisor about these, stay out of the way as much as possible.
generate("long", "SYS_futex", "uint32_t *uaddr, int futex_op, uint32_t val, const struct timespec *timeout, uint32_t *uaddr2, uint32_t val3",
         tpl="marker_only")

# OS X's dyld sets up thread local variables after initializing shared libraries
# causing crashes when the variables are accessed too early.
# Luckily dyld's RuntimeState::setUpTLVs() calls pthread_key_create right before setting up
# thread local variables and the only reason for intercepting pthread_key_create is waiting for that
# and marking thread locals usable.
generate("int", "pthread_key_create", "pthread_key_t *key, void (*destr_function) (void *)",
         platforms=['darwin'],
         before_lines=["#if !defined(FB_ALWAYS_USE_THREAD_LOCAL)",
                       "thread_locals_usable = true;",
                       "#endif"],
         tpl="marker_only")

# Skip the getcwd family: The initial working directory is part of the fingerprint
skip("getcwd", "__getcwd_chk", "SYS_getcwd", "getwd", "__getwd_chk", "get_current_dir_name")

# Time and alarm handling: These are fine, no action required
# TODO(rbalint) those may affect output if the process measures time that way
# usually the calls can be ignored
skip("clock", "clock_getres", "SYS_clock_getres", "SYS_clock_getres_time64", "clock_getcpuclockid")
skip("sleep", "usleep", "nanosleep", "SYS_nanosleep", "clock_nanosleep", "SYS_clock_nanosleep", "SYS_clock_nanosleep_time64")
skip("pause", "SYS_pause", "alarm", "SYS_alarm", "ualarm", "getitimer", "SYS_getitimer", "setitimer", "SYS_setitimer")
skip("timer_create", "SYS_timer_create", "timer_delete", "SYS_timer_delete", "timer_getoverrun", "SYS_timer_getoverrun")
skip("timer_settime", "SYS_timer_settime", "SYS_timer_settime_time64", "timer_gettime", "SYS_timer_gettime", "SYS_timer_gettime_time64")

# Getting high entropy randomness should disable shortcutting
generate("ssize_t", ["getrandom", "SYS_getrandom"], "void* buf, size_t buflen, unsigned int flags",
         platforms=['linux'],
         msg_skip_fields = ["buf", "buflen"])
generate("int", "getentropy", "void* buffer, size_t length",
         msg="getrandom",
         msg_skip_fields = ["buffer", "length"],
         # implemented by glibc as getrandom(..., 0)
         msg_add_fields=["fbbcomm_builder_getrandom_set_flags(&ic_msg, 0);"])
generate("uint32_t", "arc4random", "",
         msg="getrandom",
         # implemented by glibc as getrandom(..., 0)
         msg_add_fields=["fbbcomm_builder_getrandom_set_flags(&ic_msg, 0);"],
         success="true",
         send_ret_on_success=False,
         no_saved_errno=True)
generate("void", "arc4random_buf", "void* buf, size_t nbytes",
         msg="getrandom",
         msg_skip_fields=["buf", "nbytes"],
         # implemented by glibc as getrandom(..., 0)
         msg_add_fields=["fbbcomm_builder_getrandom_set_flags(&ic_msg, 0);"],
         success="true",
         send_ret_on_success=False,
         no_saved_errno=True)
generate("uint32_t", "arc4random_uniform", "uint32_t upper_bound",
         msg="getrandom",
         msg_skip_fields = ["upper_bound"],
         # implemented by glibc as getrandom(..., 0)
         msg_add_fields=["fbbcomm_builder_getrandom_set_flags(&ic_msg, 0);"],
         success="true",
         send_ret_on_success=False,
         no_saved_errno=True)

# Querying current time - probably disable shortcutting, let the supervisor decide
generate("time_t", ["time", "SYS_time", "__time64"], "time_t *loc",
         msg="clock_gettime",
         tpl="once")
generate("int", "ftime", "struct timeb *tp",
         msg="clock_gettime",
         tpl="once")
# sys/time.h declares the funcion with void *tz and not matching that generates an error
generate("int", ["gettimeofday", "__gettimeofday"], "struct timeval *tv, struct timezone *tz",
         platforms=['linux'],
         ifdef_guard=not_glibc_ge(2, 31),
         msg="clock_gettime",
         tpl="once")
generate("int", ["gettimeofday", "__gettimeofday"], "struct timeval *tv, void *tz",
         platforms=['darwin'],
         msg="clock_gettime",
         tpl="once")
generate("int", ["gettimeofday", "__gettimeofday"], "struct timeval *tv, void *tz",
         platforms=['linux'],
         ifdef_guard=glibc_ge(2, 31),
         msg="clock_gettime",
         tpl="once")
generate("int", "__gettimeofday64", "struct timeval *tv, void *tz",
         platforms=['linux'],
         ifdef_guard=glibc_ge(2, 34),
         msg="clock_gettime",
         tpl="once")
generate("int", "SYS_gettimeofday", "struct timeval *tv, void *tz",
         msg="clock_gettime",
         tpl="once")
# FIXME SYS_clock_gettime64
generate("int", ["clock_gettime", "SYS_clock_gettime64", "SYS_clock_gettime", "__clock_gettime64"], "clockid_t clk_id, struct timespec *tp",
         msg="clock_gettime",
         tpl="once")
generate("int", "__clock_gettime", "clockid_t clk_id, struct timespec *tp",
         platforms=['linux'],
         msg="clock_gettime",
         tpl="once")
# FIXME SYS_osf_ntp_gettime
generate("int", ["ntp_gettime", "ntp_gettimex", "__ntp_gettime64", "__ntp_gettimex64"], "struct ntptimeval *ntv",
         platforms=['linux'],
         msg="clock_gettime",
         tpl="once")

# Disable setting the time
generate("int", "stime", "const time_t *t",
         platforms=['linux'],
         tpl="once")
generate("int", ["settimeofday", "SYS_settimeofday", "__settimeofday64"] + (["__settimeofday"] if target == "darwin" else []), "const struct timeval *tv, const struct timezone *tz",
         tpl="once")
# FIXME SYS_clock_settime64
generate("int", ["clock_settime", "SYS_clock_settime64", "SYS_clock_settime", "__clock_settime64"], "clockid_t clk_id, const struct timespec *tp",
         tpl="once")
# FIXME SYS_osf_adjtime, SYS_old_adjtimex
generate("int", ["adjtime", "__adjtime64"], "const struct timeval *delta, struct timeval *olddelta",
         tpl="once")
generate("int", ["adjtimex", "SYS_adjtimex", "__adjtimex"], "struct timex *buf",
         platforms=['linux'],
         tpl="once")
# FIXME SYS_clock_adjtime64
generate("int", ["clock_adjtime", "SYS_clock_adjtime64", "SYS_clock_adjtime", "__clock_adjtime64"], "clockid_t clk_id, struct timex *buf",
         platforms=['linux'],
         tpl="once")
# FIXME SYS_osf_ntp_adjtime, SYS_osf_utc_adjtime
generate("int", "ntp_adjtime", "struct timex *buf",
         platforms=['linux'],
         tpl="once")

# Skip scheduling parameters
skip("nice", "SYS_nice", "getpriority", "SYS_getpriority", "setpriority", "SYS_setpriority")
skip("getrusage", "SYS_getrusage", "profil", "SYS_profil", "acct", "SYS_acct")

# Process stuff
skip("prctl", "SYS_prctl")
skip("getpid", "SYS_getpid", "getppid", "SYS_getppid", "gettid", "SYS_gettid")
skip("getpgrp", "SYS_getpgrp", "getpgid", "__getpgid", "SYS_getpgid", "setpgrp", "SYS_setpgrp", "setpgid", "SYS_setpgid")
skip("getsid", "SYS_getsid", "setsid", "SYS_setsid")
skip("SYS_capget")
generate("int", ["pidfd_open", "SYS_pidfd_open"], "pid_t pid, unsigned int flags",
         platforms=['linux'],
         tpl="once")
# can be skipped, since pidfd_open already disables shortcutting
skip("pidfd_getfd", "SYS_pidfd_getfd", "pidfd_send_signal", "SYS_pidfd_send_signal")
generate("mode_t", ["umask", "SYS_umask"], "mode_t mask",
         success="true",
         send_ret_on_success=True,
         send_msg_on_error=False)
# Note: getumask is gone since glibc 2.24

# Pretend that seccomp() is not implemented.
# This allows interception of a few commands that otherwise would use seccomp sandboxing, such as man
# and friends.
generate("int", "SYS_seccomp", "unsigned int operation, unsigned int flags, void *args",
         msg_skip_fields=["operation", "flags", "args"],
         msg="seccomp",
         call_orig_lines=["if (i_am_intercepting) {",
                          "  (void)operation; (void)flags; (void)args;",
                          "  errno = EINVAL; ret =  -1;"
                          "} else {",
                          "  ret = ic_orig_SYS_seccomp(operation, flags, args);",
                          "}",
                          ])

# FIXME {get,set,p}rlimit

# User and group stuff
# FIXME shall we notify the supervisor?
# FIXME SYS_*32
skip("getuid", "SYS_getuid", "getgid", "SYS_getgid", "geteuid", "SYS_geteuid", "getegid", "SYS_getegid")
skip("getresuid", "SYS_getresuid", "getresgid", "SYS_getresgid")
skip("getgroups", "SYS_getgroups", "group_member")
generate("int", ["setuid", "SYS_setuid", "seteuid"], "uid_t uid",
         tpl="once")
generate("int", ["setgid", "SYS_setgid", "setegid"], "gid_t gid",
         tpl="once")
generate("int", ["setreuid", "SYS_setreuid"] + (["__setreuid"] if target == "darwin" else []), "uid_t uid, uid_t euid",
         tpl="once")
generate("int", ["setregid", "SYS_setregid"] + (["__setregid"] if target == "darwin" else []), "gid_t gid, gid_t egid",
         tpl="once")
generate("int", ["setresuid", "SYS_setresuid"], "uid_t uid, uid_t euid, uid_t suid",
         platforms=['linux'],
         tpl="once")
generate("int", ["setresgid", "SYS_setresgid"], "gid_t gid, gid_t egid, gid_t sgid",
         platforms=['linux'],
         tpl="once")
generate("int", ["setgroups", "SYS_setgroups"], ("size_t" if target == 'linux' else "int") + " size, const gid_t *list",
         tpl="once")

# Shells
skip("getusershell", "endusershell", "setusershell")

# FIXME What to do with the getpwent() family?

# Tty stuff
skip("ttyname", "ttyname_r", "ttyslot", "isatty", "vhangup", "SYS_vhangup", "getpass")
skip("tcgetpgrp", "tcsetpgrp", "getlogin", "getlogin_r"," __getlogin_r_chk")
skip("ctermid", "cuserid")
generate("int", ["setlogin"] + (["__setlogin"] if target == "darwin" else []), "const char *name",
         tpl="once")

# Revoke - glibc function exists, no manpage. Does it work?
skip("revoke")

# System stuff
skip("daemon")
skip("getpagesize", "__getpagesize", "SYS_getpagesize")
skip("getdtablesize", "SYS_getdtablesize")

# Memory management
skip("malloc", "calloc", "realloc", "free", "brk", "SYS_brk", "sbrk")
# FIXME intercept mmap
skip("mmap", "SYS_mmap", "SYS_mmap2", "mremap", "SYS_mremap", "munmap", "SYS_munmap")

# Hostname stuff - report gethostname, getdomainname, gethostid.
# The supervisor might decide whether to disable shortcutting, or ignore this difference
generate("int", ["gethostname", "SYS_gethostname"], "char *name, size_t len")
generate("int", "__gethostname_chk", "char *name, size_t len, size_t fortify_size",
         tpl="once")
generate("int", "getdomainname", "char *name, " + ("size_t" if target == 'linux' else "int") + " len",
         tpl="once")
generate("int", "__getdomainname_chk", "char *name, size_t len, size_t fortify_size",
         tpl="once")
# FIXME SYS_osf_gethostid
generate("long", "gethostid", "",
         tpl="once")

# Disable shortcutting on sethostname, setdomainname, sethostid
generate("int", ["sethostname", "SYS_sethostname"], "const char *name, " + ("size_t" if target == 'linux' else "int") + " len",
         tpl="once")
generate("int", ["setdomainname", "SYS_setdomainname"], "const char *name, " + ("size_t" if target == 'linux' else "int") + " len",
         tpl="once")
# FIXME SYS_osf_gethostid
generate("int" if target == 'linux' else "void", "sethostid", "long hostid",
         tpl="once")

generate("int", ["socket", "SYS_socket"]+ (["__socket"] if target == "linux" else []), "int domain, int type, int protocol",
         after_lines=["if (i_am_intercepting && success) clear_notify_on_read_write_state(ret);"],
         send_ret_on_success=True)
generate("int", ["connect", "SYS_connect", "__connect"], "int sockfd, const struct sockaddr *addr, socklen_t addrlen",
         msg_skip_fields=["addr", "addrlen"],
         msg_add_fields=["if (((struct sockaddr*)addr)->sa_family == AF_UNIX) {",
                         "  const char * const sun_path = ((struct sockaddr_un*)addr)->sun_path;",
                         "  fbbcomm_builder_connect_set_addr_with_length(&ic_msg, sun_path, strnlen(sun_path, sizeof(((struct sockaddr_un*)addr)->sun_path)));",
                         "}"],
         )
generate("int", ["bind", "SYS_bind"] + (["__bind"] if target == "darwin" else []), "int sockfd, const struct sockaddr *addr, socklen_t addrlen",
         tpl="once")
generate("int", ["listen", "SYS_listen"] + (["__listen"] if target == "darwin" else []), "int sockfd, int backlog",
         tpl="once")
generate("int", ["socketpair", "SYS_socketpair"] + (["__socketpair"] if target == "darwin" else []), "int domain, int type, int protocol, int sv[2]",
         after_lines=["if (i_am_intercepting && success) {",
                      "  clear_notify_on_read_write_state(sv[0]);",
                      "  clear_notify_on_read_write_state(sv[1]);",
                      "}"],
         msg_skip_fields=["sv"],
         msg_add_fields=["if (success) {",
                         "  fbbcomm_builder_socketpair_set_fd0(&ic_msg, sv[0]);",
                         "  fbbcomm_builder_socketpair_set_fd1(&ic_msg, sv[1]);",
                         "}"])
generate("int", "SYS_socketcall", "int call, unsigned long *args",
         tpl="once")

# Intercept the pathconf family
# FIXME Sometimes a retval == -1 does not denote error. Same for sysconf.
# FIXME SYS_osf_[f]pathconf
generate("long", "pathconf", "const char *path, int name",
         send_ret_on_success=True)
generate("long", "fpathconf", "int fd, int name",
         send_ret_on_success=True)

# Intercept sysconf
# FIXME Sometimes a retval == -1 does not denote error. Same for pathconf.
generate("long", ["__sysconf"], "int name",
         platforms=["linux"],
         send_ret_on_success=True)
generate("long", ["sysconf"], "int name",
         send_ret_on_success=True)

# Intercept syscall
# FIXME What does syscall(SYS_syscall, ...) do? :)
generate("long", "syscall", "long number, ...",
         platforms=["linux"],
         tpl="syscall",
         send_ret_on_success=True,
         ack_condition="true")
generate("int", ["syscall", "__syscall"], "int number, ...",
         platforms=["darwin"],
         tpl="syscall",
         send_ret_on_success=True,
         ack_condition="true")

generate("int", ["__mac_syscall", "SYS___mac_syscall"], "char* policy, int call, user_addr_t arg",
         platforms=["darwin"],
         msg="mac_syscall",
         msg_skip_fields=["arg"],
         send_ret_on_success=True)

# FIXME This is temporary only, until intercepting confstr is implemented:
#generate("size_t", "confstr", "int name, char *buf, size_t len")
#generate("size_t", "__confstr_chk", "int name, char *buf, size_t len, size_t fortify_size")
skip("confstr", "__confstr_chk")

# Network
# FIXME use the "once" template instead: we need to know it can't be shortcut
skip("getsockname", "SYS_getsockname", "getpeername", "SYS_getpeername")
skip("getsockopt", "SYS_getsockopt", "setsockopt", "SYS_setsockopt")
skip("accept", "SYS_accept", "accept4", "SYS_accept4", "shutdown", "SYS_shutdown", "sockatmark")

# Mounts
skip("setmntent", "getmntent", "getmntent_r", "addmntent", "endmntent", "hasmntopt")

# FIXME Implement intercepting these:
# mkdir(at)
# freopen(64) - looks complicated

# locale related ones:
# (bind)textdomain, *gettext, {set,use,new}locale, nl_langinfo, iconv*
# iswalpha, towlower and friends (the locale dependant ones)
# strcasecmp
# strerror*, strsignal, strftime
# *_l


# Finally rename the output files.
for gen in set(libc_outputs + syscall_outputs):
  os.rename(outdir + "/gen_" + gen + tmpsuffix, outdir + "/gen_" + gen)
