summaryrefslogtreecommitdiff
path: root/src/cam/options.cpp
blob: 358507eabf89f30fc4f0349d1ca37c30f53ab717 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
</* SPDX-License-Identifier: GPL-2.0-or-later */
/*
 * Copyright (C) 2019, Google Inc.
 *
 * object-invoke.cpp - Cross-thread Object method invocation test
 */

#include <chrono>
#include <iostream>
#include <thread>

#include <libcamera/event_dispatcher.h>
#include <libcamera/object.h>

#include "test.h"
#include "thread.h"

using namespace std;
using namespace libcamera;

class InvokedObject : public Object
{
public:
	enum Status {
		NoCall,
		InvalidThread,
		CallReceived,
	};

	InvokedObject()
		: status_(NoCall)
	{
	}

	Status status() const { return status_; }
	int value() const { return value_; }
	void reset()
	{
		status_ = NoCall;
		value_ = 0;
	}

	void method(int value)
	{
		if (Thread::current() != thread())
			status_ = InvalidThread;
		else
			status_ = CallReceived;

		value_ = value;
	}

private:
	Status status_;
	int value_;
};

class ObjectInvokeTest : public Test
{
protected:
	int run()
	{
		EventDispatcher *dispatcher = Thread::current()->eventDispatcher();
		InvokedObject object;

		/*
		 * Test that method invocation in the same thread goes through
		 * the event dispatcher.
		 */
		object.invokeMethod(&InvokedObject::method, 42);

		if (object.status() != InvokedObject::NoCall) {
			cerr << "Method not invoked asynchronously" << endl;
			return TestFail;
		}

		dispatcher->processEvents();

		switch (object.status()) {
		case InvokedObject::NoCall:
			cout << "Method not invoked for main thread" << endl;
			return TestFail;
		case InvokedObject::InvalidThread:
			cout << "Method invoked in incorrect thread for main thread" << endl;
			return TestFail;
		default:
			break;
		}

		if (object.value() != 42) {
			cout << "Method invoked with incorrect value for main thread" << endl;
			return TestFail;
		}

		/*
		 * Move the object to a thread and verify that the method is
		 * delivered in the correct thread.
		 */
		object.reset();
		object.moveToThread(&thread_);

		thread_.start();

		object.invokeMethod(&InvokedObject::method, 42);
		this_thread::sleep_for(chrono::milliseconds(100));

		switch (object.status()) {
		case InvokedObject::NoCall:
			cout << "Method not invoked for custom thread" << endl;
			return TestFail;
		case InvokedObject::InvalidThread:
			cout << "Method invoked in incorrect thread for custom thread" << endl;
			return TestFail;
		default:
			break;
		}

		if (object.value() != 42) {
			cout << "Method invoked with incorrect value for custom thread" << endl;
			return TestFail;
		}

		return TestPass;
	}

	void cleanup()
	{
		thread_.exit(0);
		thread_.wait();
	}

private:
	Thread thread_;
};

TEST_REGISTER(ObjectInvokeTest)
#n474'>474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
 * Copyright (C) 2019, Google Inc.
 *
 * options.cpp - cam - Options parsing
 */

#include <assert.h>
#include <getopt.h>
#include <iomanip>
#include <iostream>
#include <string.h>

#include "options.h"

/* -----------------------------------------------------------------------------
 * Option
 */

const char *Option::typeName() const
{
	switch (type) {
	case OptionNone:
		return "none";

	case OptionInteger:
		return "integer";

	case OptionString:
		return "string";

	case OptionKeyValue:
		return "key=value";
	}

	return "unknown";
}

/* -----------------------------------------------------------------------------
 * OptionBase<T>
 */

template<typename T>
bool OptionsBase<T>::empty() const
{
	return values_.empty();
}

template<typename T>
bool OptionsBase<T>::valid() const
{
	return valid_;
}

template<typename T>
bool OptionsBase<T>::isSet(const T &opt) const
{
	return values_.find(opt) != values_.end();
}

template<typename T>
const OptionValue &OptionsBase<T>::operator[](const T &opt) const
{
	static const OptionValue empty;

	auto it = values_.find(opt);
	if (it != values_.end())
		return it->second;
	return empty;
}

template<typename T>
void OptionsBase<T>::invalidate()
{
	valid_ = false;
}

template<typename T>
bool OptionsBase<T>::parseValue(const T &opt, const Option &option,
				const char *optarg)
{
	OptionValue value;

	switch (option.type) {
	case OptionNone:
		break;

	case OptionInteger:
		unsigned int integer;

		if (optarg) {
			char *endptr;
			integer = strtoul(optarg, &endptr, 0);
			if (*endptr != '\0')
				return false;
		} else {
			integer = 0;
		}

		value = OptionValue(integer);
		break;

	case OptionString:
		value = OptionValue(optarg ? optarg : "");
		break;

	case OptionKeyValue:
		KeyValueParser *kvParser = option.keyValueParser;
		KeyValueParser::Options keyValues = kvParser->parse(optarg);
		if (!keyValues.valid())
			return false;

		value = OptionValue(keyValues);
		break;
	}

	if (option.isArray)
		values_[opt].addValue(value);
	else
		values_[opt] = value;

	return true;
}

template class OptionsBase<int>;
template class OptionsBase<std::string>;

/* -----------------------------------------------------------------------------
 * KeyValueParser
 */

bool KeyValueParser::addOption(const char *name, OptionType type,
			       const char *help, OptionArgument argument)
{
	if (!name)
		return false;
	if (!help || help[0] == '\0')
		return false;
	if (argument != ArgumentNone && type == OptionNone)
		return false;

	/* Reject duplicate options. */
	if (optionsMap_.find(name) != optionsMap_.end())
		return false;

	optionsMap_[name] = Option({ 0, type, name, argument, nullptr,
				     help, nullptr, false });
	return true;
}

KeyValueParser::Options KeyValueParser::parse(const char *arguments)
{
	Options options;

	for (const char *pair = arguments; *arguments != '\0'; pair = arguments) {
		const char *comma = strchrnul(arguments, ',');
		size_t len = comma - pair;

		/* Skip over the comma. */
		arguments = *comma == ',' ? comma + 1 : comma;

		/* Skip to the next pair if the pair is empty. */
		if (!len)
			continue;

		std::string key;
		std::string value;

		const char *separator = static_cast<const char *>(memchr(pair, '=', len));
		if (!separator) {
			key = std::string(pair, len);
			value = "";
		} else {
			key = std::string(pair, separator - pair);
			value = std::string(separator + 1, comma - separator - 1);
		}

		/* The key is mandatory, the value might be optional. */
		if (key.empty())
			continue;

		if (optionsMap_.find(key) == optionsMap_.end()) {
			std::cerr << "Invalid option " << key << std::endl;
			return options;
		}

		OptionArgument arg = optionsMap_[key].argument;
		if (value.empty() && arg == ArgumentRequired) {
			std::cerr << "Option " << key << " requires an argument"
				  << std::endl;
			return options;
		} else if (!value.empty() && arg == ArgumentNone) {
			std::cerr << "Option " << key << " takes no argument"
				  << std::endl;
			return options;
		}

		const Option &option = optionsMap_[key];
		if (!options.parseValue(key, option, value.c_str())) {
			std::cerr << "Failed to parse '" << value << "' as "
				  << option.typeName() << " for option " << key
				  << std::endl;
			return options;
		}
	}

	options.valid_ = true;
	return options;
}

void KeyValueParser::usage(int indent)
{
	unsigned int space = 0;

	for (auto const &iter : optionsMap_) {
		const Option &option = iter.second;
		unsigned int length = 14;
		if (option.argument != ArgumentNone)
			length += 1 + strlen(option.typeName());
		if (option.argument == ArgumentOptional)
			length += 2;

		if (length > space)
			space = length;
	}

	space = (space + 7) / 8 * 8;

	for (auto const &iter : optionsMap_) {
		const Option &option = iter.second;
		std::string argument = option.name;

		if (option.argument != ArgumentNone) {
			if (option.argument == ArgumentOptional)
				argument += "[=";
			else
				argument += "=";
			argument += option.typeName();
			if (option.argument == ArgumentOptional)
				argument += "]";
		}

		std::cerr << std::setw(indent) << std::right << " "
			  << std::setw(space) << std::left << argument;

		for (const char *help = option.help, *end = help; end;) {
			end = strchr(help, '\n');
			if (end) {
				std::cerr << std::string(help, end - help + 1);
				std::cerr << std::setw(indent + space) << " ";
				help = end + 1;
			} else {
				std::cerr << help << std::endl;
			}
		}
	}
}

/* -----------------------------------------------------------------------------
 * OptionValue
 */

OptionValue::OptionValue()
	: type_(ValueNone), integer_(0)
{
}

OptionValue::OptionValue(int value)
	: type_(ValueInteger), integer_(value)
{
}

OptionValue::OptionValue(const char *value)
	: type_(ValueString), integer_(0), string_(value)
{
}

OptionValue::OptionValue(const std::string &value)
	: type_(ValueString), integer_(0), string_(value)
{
}

OptionValue::OptionValue(const KeyValueParser::Options &value)
	: type_(ValueKeyValue), integer_(0), keyValues_(value)
{
}

void OptionValue::addValue(const OptionValue &value)
{
	assert(type_ == ValueNone || type_ == ValueArray);

	type_ = ValueArray;
	array_.push_back(value);
}

OptionValue::operator int() const
{
	return toInteger();
}

OptionValue::operator std::string() const
{
	return toString();
}

OptionValue::operator KeyValueParser::Options() const
{
	return toKeyValues();
}

OptionValue::operator std::vector<OptionValue>() const
{
	return toArray();
}

int OptionValue::toInteger() const
{
	if (type_ != ValueInteger)
		return 0;

	return integer_;
}

std::string OptionValue::toString() const
{
	if (type_ != ValueString)
		return std::string();

	return string_;
}

KeyValueParser::Options OptionValue::toKeyValues() const
{
	if (type_ != ValueKeyValue)
		return KeyValueParser::Options();

	return keyValues_;
}

std::vector<OptionValue> OptionValue::toArray() const
{
	if (type_ != ValueArray)
		return std::vector<OptionValue>{};

	return array_;
}

/* -----------------------------------------------------------------------------
 * OptionsParser
 */

bool OptionsParser::addOption(int opt, OptionType type, const char *help,
			      const char *name, OptionArgument argument,
			      const char *argumentName, bool array)
{
	/*
	 * Options must have at least a short or long name, and a text message.
	 * If an argument is accepted, it must be described by argumentName.
	 */
	if (!isalnum(opt) && !name)
		return false;
	if (!help || help[0] == '\0')
		return false;
	if (argument != ArgumentNone && !argumentName)
		return false;

	/* Reject duplicate options. */
	if (optionsMap_.find(opt) != optionsMap_.end())
		return false;

	options_.push_back(Option({ opt, type, name, argument, argumentName,
				    help, nullptr, array }));
	optionsMap_[opt] = &options_.back();
	return true;
}

bool OptionsParser::addOption(int opt, KeyValueParser *parser, const char *help,
			      const char *name, bool array)
{
	if (!addOption(opt, OptionKeyValue, help, name, ArgumentRequired,
		       "key=value[,key=value,...]", array))
		return false;

	options_.back().keyValueParser = parser;
	return true;
}

OptionsParser::Options OptionsParser::parse(int argc, char **argv)
{
	OptionsParser::Options options;

	/*
	 * Allocate short and long options arrays large enough to contain all
	 * options.
	 */
	char shortOptions[options_.size() * 3 + 2];
	struct option longOptions[options_.size() + 1];
	unsigned int ids = 0;
	unsigned int idl = 0;

	shortOptions[ids++] = ':';

	for (const Option &option : options_) {
		if (option.hasShortOption()) {
			shortOptions[ids++] = option.opt;
			if (option.argument != ArgumentNone)
				shortOptions[ids++] = ':';
			if (option.argument == ArgumentOptional)
				shortOptions[ids++] = ':';
		}

		if (option.hasLongOption()) {
			longOptions[idl].name = option.name;

			switch (option.argument) {
			case ArgumentNone:
				longOptions[idl].has_arg = no_argument;
				break;
			case ArgumentRequired:
				longOptions[idl].has_arg = required_argument;
				break;
			case ArgumentOptional:
				longOptions[idl].has_arg = optional_argument;
				break;
			}

			longOptions[idl].flag = 0;
			longOptions[idl].val = option.opt;
			idl++;
		}
	}

	shortOptions[ids] = '\0';
	memset(&longOptions[idl], 0, sizeof(longOptions[idl]));

	opterr = 0;

	while (true) {
		int c = getopt_long(argc, argv, shortOptions, longOptions, nullptr);

		if (c == -1)
			break;

		if (c == '?' || c == ':') {
			if (c == '?')
				std::cerr << "Invalid option ";
			else
				std::cerr << "Missing argument for option ";
			std::cerr << argv[optind - 1] << std::endl;

			usage();
			return options;
		}

		const Option &option = *optionsMap_[c];
		if (!options.parseValue(c, option, optarg)) {
			parseValueError(option);
			usage();
			return options;
		}
	}

	options.valid_ = true;
	return options;
}

void OptionsParser::usage()
{
	std::cerr << "Options:" << std::endl;

	unsigned int indent = 0;

	for (const Option &option : options_) {
		unsigned int length = 14;
		if (option.hasLongOption())
			length += 2 + strlen(option.name);
		if (option.argument != ArgumentNone)
			length += 1 + strlen(option.argumentName);
		if (option.argument == ArgumentOptional)
			length += 2;
		if (option.isArray)
			length += 4;

		if (length > indent)
			indent = length;
	}

	indent = (indent + 7) / 8 * 8;

	for (const Option &option : options_) {
		std::string argument;
		if (option.hasShortOption())
			argument = std::string("  -")
				 + static_cast<char>(option.opt);
		else
			argument = "    ";

		if (option.hasLongOption()) {
			if (option.hasShortOption())
				argument += ", ";
			else
				argument += "  ";
			argument += std::string("--") + option.name;
		}

		if (option.argument != ArgumentNone) {
			if (option.argument == ArgumentOptional)
				argument += "[=";
			else
				argument += " ";
			argument += option.argumentName;
			if (option.argument == ArgumentOptional)
				argument += "]";
		}

		if (option.isArray)
			argument += " ...";

		std::cerr << std::setw(indent) << std::left << argument;

		for (const char *help = option.help, *end = help; end; ) {
			end = strchr(help, '\n');
			if (end) {
				std::cerr << std::string(help, end - help + 1);
				std::cerr << std::setw(indent) << " ";
				help = end + 1;
			} else {
				std::cerr << help << std::endl;
			}
		}

		if (option.keyValueParser)
			option.keyValueParser->usage(indent);
	}
}

void OptionsParser::parseValueError(const Option &option)
{
	std::string optionName;

	if (option.name)
		optionName = "--" + std::string(option.name);
	else
		optionName = "-" + std::string(1, option.opt);

	std::cerr << "Can't parse " << option.typeName()
		  << " argument for option " << optionName << std::endl;
}