/*
 *  helper.h
 *
 *  Copyright 2005-2010 Coriolis Systems Limited. All rights reserved.
 *
 */

#ifndef HELPER_H_
#define HELPER_H_

#include <Security/Authorization.h>
#include <stdbool.h>
#include <limits.h>
#include <sys/queue.h>

/* Status codes that are used by the helper code.  Commands are free to
   use any other code that they like. */
enum {
  RET_OK			= 0,
  
  /* This is returned by authorise to indicate that the message has been
     forwarded to a privileged child. */
  RET_FORWARDED			= 10000,
  
  // This means that a command forgot to send a reply
  RET_DEFAULT			= 10001,
  
  /* This indicates some kind of problem with the helper: e.g. it terminated
     or there was a problem communicating via the pipe. */
  RET_FAILED			= 10002,

  /* Used when a request for a file descriptor finds it's no long available
     (due to the reply being sent). */
  RET_NO_FD                     = 10003,

  /* Used to indicate a problem with the invocation of the command, e.g.
     wrong number of arguments or incorrect format of arguments. */
  RET_INVALID_ARGS		= 10004,

  /* This code is used internally to mean no reponse has yet been received
     and is therefore reserved. */
  RET_NO_RESPONSE		= INT_MIN,
};

typedef enum {
  InteractionNotAllowed       = 0,
  InteractionAllowed          = 1,
  InteractionAllowedOnce      = 2,
} Interaction;

#if HELPER

typedef struct invocation invocation_t;

typedef void (* command_handler_t)(invocation_t *);

typedef struct command {
  struct command    *next;
  const char	    *command;
  command_handler_t handler;
  unsigned	    min_args, max_args;
} command_t;

enum {
  MAX_ARGS = 256
};

extern command_t *commands;

/* To register a command, use this macro.  The convention is to use
   lower case for the names.  Upper case commands are used internally. */
#define DECLARE_COMMAND(name, fn, min, max)			  \
  typedef char fn##_max_args_check__[max > MAX_ARGS ? -1 : 0];	  \
  static void fn (invocation_t *);				  \
  static command_t fn##_command__ = { NULL, name, fn, min, max }; \
  static __attribute__ ((__constructor__)) void                   \
  register_##fn##_command__ (void) {				  \
    fn##_command__.next = commands;				  \
    commands = &fn##_command__;					  \
  }

/* This structure records information about a command that is in progress. */
struct invocation {
  LIST_ENTRY (invocation) chain;

  // Used to correlate replies
  unsigned	      command_id;
  
  // The original command string
  char		     *cmd;

  // The parsed arguemnts
  unsigned	      argc;
  char		     *argv[MAX_ARGS];
  
  // The matched command
  command_t	     *command;

  bool		      sent_reply;
  volatile bool	      should_abort;

  /* File descriptors are not sent back immediately so they get stored here.
     See send_reply_with_fd for more information. */
  int		      fd;

  /* Always called when the invocation is freed (including if terminated
     early). */
  void		     (* cleanup_fn)(invocation_t *inv);

  /* Here you store whatever you want.  If set, will be free'd using free. */
  void		     *user;

  unsigned	      command_data_len;
  void		     *command_data;
};

const char *tool_path (void);
const char *tool_name (void);

// Use the one of the following to send replies
void send_reply (invocation_t *inv, int status);
void send_reply_with_data (invocation_t *inv, int status,
			   const void *data, size_t len);
void send_reply_with_fd (invocation_t *inv, int fd);

void send_progress (const char *progress_fmt, ...)
  __attribute__ ((format (printf, 1, 2)));

/* Use this to send back a file descriptor *before* the end of the command.
   NOTE: Use send_reply_with_fd if you want to return a file descriptor at the
   end of processing the command. */
void send_fd (invocation_t *inv, int fd);

// Call this periodically to find out whether to abort
bool should_abort (void);

/* Call this to authorise an action.  If the tool is unprivileged, this will
   forward the command to a privileged child. */
int authorise (const char *right, Interaction interaction);

/* Invocations run on their own threads, so use this to figure out what the
   invocation is for this thread. */
invocation_t * current_invocation (void);

void set_cleanup (invocation_t *inv, 
		  void (*cleanup_fn)(invocation_t *));

AuthorizationRef tool_auth (void);

#endif // if HELPER

#endif /* HELPER_H_ */
