/*
  Copyright (c) 2018 Martin Wilck, SUSE Linux GmbH

  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License
  as published by the Free Software Foundation; either version 2
  of the License, or (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef _FOREIGN_H
#define _FOREIGN_H
#include <stdbool.h>
#include <libudev.h>
#define LIBMP_FOREIGN_API ((1 << 8) | 2)

struct strbuf;
struct context;

/* return codes of functions below returning "int" */
enum foreign_retcode {
	FOREIGN_OK,
	FOREIGN_CLAIMED,
	FOREIGN_IGNORED,
	FOREIGN_UNCLAIMED,
	FOREIGN_NODEV,
	FOREIGN_ERR,
	__LAST_FOREIGN_RETCODE,
};

/**
 * Foreign multipath library API
 * Foreign libraries must implement the following methods.
 */
struct foreign {
	/**
	 * method: init(api, name)
	 * Initialize foreign library, and check API compatibility
	 * return pointer to opaque internal data structure if successful,
	 * NULL otherwise.
	 *
	 * @param[in] api: API version
	 * @param[in] name: name to use for references to self in log messages,
	 *     doesn't need to be strdup'd
	 * @returns context pointer to use in future method calls.
	 */
	struct context* (*init)(unsigned int api, const char *name);

	/**
	 * method: cleanup(context)
	 * Free data structures used by foreign library, including
	 * context itself.
	 *
	 * @param[in] context foreign library context. This shouldn't be
	 * referenced any more after calling cleanup().
	 */
	void (*cleanup)(struct context *);

	/**
	 * method: add(context, udev)
	 * This is called during path detection, and for udev ADD events.
	 *
	 * @param[in] context foreign library context
	 * @param[in] udev udev device to add
	 * @returns status code
	 * @retval FOREIGN_CLAIMED: device newly claimed
	 * @retval FOREIGN_OK: device already registered, no action taken
	 * @retval FOREIGN_IGNORED: device is ignored, no action taken
	 * @retval FOREIGN_ERR: an error occurred (e.g. out-of-memory)
	 */
	int (*add)(struct context *, struct udev_device *);

	/**
	 * method: change
	 * This is called on udev CHANGE events.
	 *
	 * @param[in] context foreign library context
	 * @param[in] udev udev device that has generated the event
	 * @returns status code
	 * @retval FOREIGN_OK: event processed
	 * @retval FOREIGN_IGNORED: the device is ignored
	 * @retval FOREIGN_ERR: an error occurred (e.g. out-of-memory)
	 *
	 * Note: theoretically it can happen that the status of a foreign device
	 * (claimed vs. not claimed) changes in a change event.
	 * Supporting this correctly would require big efforts. For now, we
	 * don't support it. "multipathd reconfigure" starts foreign device
	 * detection from scratch and should be able to handle this situation.
	 */
	int (*change)(struct context *, struct udev_device *);

	/**
	 * method: delete
	 * This is called on udev DELETE events.
	 *
	 * @param[in] context foreign library context
	 * @param[in] udev udev device that has generated the event and
	 *	should be deleted
	 * @returns status code
	 * @retval FOREIGN_OK: processed correctly (device deleted)
	 * @retval FOREIGN_IGNORED: device wasn't registered internally
	 * @retval FOREIGN_ERR: error occurred.
	 */
	int (*delete)(struct context *, struct udev_device *);

	/**
	 * method: delete_all
	 * This is called if multipathd reconfigures itself.
	 * Deletes all registered devices (maps and paths)
	 *
	 * @param[in] context foreign library context
	 * @returns status code
	 * @retval FOREIGN_OK: processed correctly
	 * @retval FOREIGN_IGNORED: nothing to delete
	 * @retval FOREIGN_ERR: error occurred
	 */
	int (*delete_all)(struct context*);

	/**
	 * method: check
	 * This is called from multipathd's checker loop.
	 *
	 * Check status of managed devices, update internal status, and print
	 * log messages if appropriate.
	 * @param[in] context foreign library context
	 */
	void (*check)(struct context *);

	/**
	 * lock internal data structures.
	 * @param[in] ctx: foreign context
	 */
	void (*lock)(struct context *ctx);

	/**
	 * unlock internal data structures.
	 * @param[in] ctx: foreign context (void* in order to use the function
	 *	as argument to pthread_cleanup_push())
	 */
	void (*unlock)(void *ctx);

	/**
	 * method: get_multipaths(context)
	 * Returned vector must be freed by calling release_multipaths().
	 * Lock must be held until release_multipaths() is called.
	 *
	 * @param[in] context foreign library context
	 * @returns a vector of "struct gen_multipath*" with the map devices
	 * belonging to this library (see generic.h).
	 */
	const struct _vector* (*get_multipaths)(const struct context *);

	/**
	 * method: release_multipaths(context, mpvec)
	 * release data structures obtained with get_multipaths (if any)
	 *
	 * @param[in] ctx the foreign context
	 * @param[in] mpvec the vector allocated with get_multipaths()
	 */
	void (*release_multipaths)(const struct context *ctx,
				   const struct _vector* mpvec);

	/**
	 * method: get_paths
	 * Returned vector must be freed by calling release_paths().
	 * Lock must be held until release_paths() is called.
	 *
	 * @param[in] context foreign library context
	 * @returns a vector of "struct gen_path*" with the path devices
	 * belonging to this library (see generic.h)
	 */
	const struct _vector* (*get_paths)(const struct context *);

	/**
	 * release data structures obtained with get_multipaths (if any)
	 *
	 * @param[in] ctx the foreign context
	 * @param[in] ppvec the vector allocated with get_paths()
	 */
	void (*release_paths)(const struct context *ctx,
			      const struct _vector* ppvec);

	void *handle;
	struct context *context;
	const char name[0];
};

/**
 * init_foreign(dir)
 * load and initialize foreign multipath libraries in dir (libforeign-*.so).
 * @param dir: directory to search
 * @param enable: regex to match foreign library name ("*" above) against
 * @returns: 0 on success, negative value on failure.
 */
int init_foreign(const char *enable);

/**
 * cleanup_foreign(dir)
 * cleanup and free all data structures owned by foreign libraries
 */
void cleanup_foreign(void);

/**
 * add_foreign(udev)
 * check if a device belongs to any foreign library.
 * calls add() for all known foreign libs, in the order registered,
 * until the first one returns FOREIGN_CLAIMED or FOREIGN_OK.
 * @param udev: udev device to check
 * @returns: status code
 * @retval FOREIGN_CLAIMED: newly claimed by a foreign lib
 * @retval FOREIGN_OK: already claimed by a foreign lib
 * @retval FOREIGN_IGNORED: ignored by all foreign libs
 * @retval FOREIGN_ERR: an error occurred
 */
int add_foreign(struct udev_device *);

/**
 * change_foreign(udev)
 * Notify foreign libraries of an udev CHANGE event
 * @param udev: udev device to check
 * @returns: status code (see change() method above).
 */
int change_foreign(struct udev_device *);

/**
 * delete_foreign(udev)
 * @param udev: udev device being removed
 * @returns: status code (see remove() above)
 */
int delete_foreign(struct udev_device *);

/**
 * delete_all_foreign()
 * call delete_all() for all foreign libraries
 * @returns: status code (see delete_all() above)
 */
int delete_all_foreign(void);

/**
 * check_foreign()
 * call check() (see above) for all foreign libraries
 */
void check_foreign(void);

/**
 * foreign_path_layout()
 * call this before printing paths, after get_path_layout(), to determine
 * output field width.
 * @param width: an array allocated by alloc_path_layout()
 */
void foreign_path_layout(fieldwidth_t *width);

/**
 * foreign_multipath_layout()
 * call this before printing maps, after get_multipath_layout(), to determine
 * output field width.
 * @param width: an array allocated by alloc_multipath_layout()
 */
void foreign_multipath_layout(fieldwidth_t *width);

/**
 * snprint_foreign_topology(buf, len, verbosity);
 * prints topology information from foreign libraries into buffer,
 * '\0' - terminated.
 * @param buf: output buffer
 * @param verbosity: verbosity level
 * @param width: an array of field widths, initialized by _get_path_layout()
 * @returns: number of printed characters excluding trailing '\0'.
 */
int snprint_foreign_topology(struct strbuf *buf, int verbosity,
			     const fieldwidth_t *width);

/**
 * snprint_foreign_paths(buf, len, style, pad);
 * prints formatted path information from foreign libraries into buffer,
 * '\0' - terminated.
 * @param buf: output buffer
 * @param style: format string
 * @param width: array initialized with get_path_layout(), or NULL for no padding
 * @returns: number of printed characters excluding trailing '\0'.
 */
int snprint_foreign_paths(struct strbuf *buf, const char *style,
			  const fieldwidth_t *width);

/**
 * snprint_foreign_multipaths(buf, len, style, pad);
 * prints formatted map information from foreign libraries into buffer,
 * '\0' - terminated.
 * @param buf: output buffer
 * @param style: format string
 * @param width: array initialized with get_path_layout(), or NULL for no padding
 * @returns: number of printed characters excluding trailing '\0'.
 */
int snprint_foreign_multipaths(struct strbuf *buf, const char *style,
			       const fieldwidth_t *width);

/**
 * print_foreign_topology(v)
 * print foreign topology to stdout
 * @param verbosity: verbosity level
 */
void print_foreign_topology(int verbosity);

/**
 * is_claimed_by_foreign(ud)
 * @param udev: udev device
 * @returns: true if device is (newly or already) claimed by a foreign lib
 */
static inline bool
is_claimed_by_foreign(struct udev_device *ud)
{
	int rc = add_foreign(ud);

	return (rc == FOREIGN_CLAIMED || rc == FOREIGN_OK);
}

#endif /*  _FOREIGN_H */
