// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
// Copyright (c) 2020 Anton Protopopov
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>

#include "map_helpers.h"

#define warn(...) fprintf(stderr, __VA_ARGS__)

static bool batch_map_ops = true; /* hope for the best */

static int
dump_hash_iter(int map_fd, void *keys, __u32 key_size,
	       void *values, __u32 value_size, __u32 *count,
	       void *invalid_key)
{
	__u8 key[key_size], next_key[key_size];
	__u32 n = 0;
	int err;

	/* First get keys */
	__builtin_memcpy(key, invalid_key, key_size);
	while (n < *count) {
		err = bpf_map_get_next_key(map_fd, key, next_key);
		if (err && errno != ENOENT) {
			return -1;
		} else if (err) {
			break;
		}
		__builtin_memcpy(key, next_key, key_size);
		__builtin_memcpy(keys + key_size * n, next_key, key_size);
		n++;
	}

	/* Now read values */
	for (int i = 0; i < n; i++) {
		err = bpf_map_lookup_elem(map_fd, keys + key_size * i,
					  values + value_size * i);
		if (err)
			return -1;
	}

	*count = n;
	return 0;
}

static int
dump_hash_batch(int map_fd, void *keys, __u32 key_size,
		void *values, __u32 value_size, __u32 *count)
{
	void *in = NULL, *out;
	__u32 n, n_read = 0;
	int err = 0;

	while (n_read < *count && !err) {
		n = *count - n_read;
		err = bpf_map_lookup_batch(map_fd, &in, &out,
					   keys + n_read * key_size,
					   values + n_read * value_size,
					   &n, NULL);
		if (err && errno != ENOENT) {
			return -1;
		}
		n_read += n;
		in = out;
	}

	*count = n_read;
	return 0;
}

int dump_hash(int map_fd,
	      void *keys, __u32 key_size,
	      void *values, __u32 value_size,
	      __u32 *count, void *invalid_key)
{
	int err;

	if (!keys || !values || !count || !key_size || !value_size) {
		errno = EINVAL;
		return -1;
	}

	if (batch_map_ops) {
		err = dump_hash_batch(map_fd, keys, key_size,
				      values, value_size, count);
		if (err) {
			if (errno != EINVAL) {
				return -1;

				/* assume that batch operations are not
				 * supported and try non-batch mode */
				batch_map_ops = false;
			}
		}
	}

	if (!invalid_key) {
		errno = EINVAL;
		return -1;
	}

	return dump_hash_iter(map_fd, keys, key_size,
			      values, value_size, count, invalid_key);
}
