/* GIO - GLib Input, Output and Streaming Library
*
* Copyright (C) 2008 Red Hat, Inc.
* Copyright (C) 2009 Jonathon Jongsma
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifndef G_OS_WIN32
#include
#endif
#include
static Glib::RefPtr resolver;
static Glib::RefPtr cancellable;
static Glib::RefPtr loop;
static int nlookups = 0;
static void G_GNUC_NORETURN
usage(void)
{
std::cerr
<< "Usage: resolver [-t] [-s] [hostname | IP | service/protocol/domain ] ...\n"
<< " resolver [-t] [-s] -c [hostname | IP | service/protocol/domain ]\n"
<< " Use -s to do synchronous lookups.\n"
<< " Use -c (and only a single resolvable argument) to test GSocketConnectable.\n";
exit(1);
}
static std::mutex response_mutex;
static bool
idle_quit()
{
loop->quit();
return false;
}
static void
done_lookup(void)
{
nlookups--;
if (nlookups == 0)
{
/* In the sync case we need to make sure we don't call
* g_main_loop_quit before the loop is actually running...
*/
Glib::signal_idle().connect(sigc::ptr_fun(idle_quit));
}
}
static void
print_resolved_name(const Glib::ustring& phys, const Glib::ustring& name)
{
std::lock_guard lock_guard(response_mutex);
std::cout << Glib::ustring::compose("Address: %1\n", phys)
<< Glib::ustring::compose("Name: %1\n", name) << std::endl;
done_lookup();
}
static void
print_resolved_addresses(
const Glib::ustring& name, const std::list>& addresses)
{
std::lock_guard lock_guard(response_mutex);
std::cout << Glib::ustring::compose("Name: %1\n", name);
for (const auto& i : addresses)
{
std::cout << Glib::ustring::compose("Address: %1\n", i->to_string());
}
std::cout << std::endl;
done_lookup();
}
static void
print_resolved_service(const Glib::ustring& service, const std::list& targets)
{
std::lock_guard lock_guard(response_mutex);
std::cout << Glib::ustring::compose("Service: %1\n", service);
for (const auto& i : targets)
{
std::cout << Glib::ustring::compose("%1:%2 (pri %3, weight %4)\n", i.get_hostname(),
i.get_port(), i.get_priority(), i.get_weight());
}
std::cout << std::endl;
done_lookup();
}
static std::vector
split_service_parts(const Glib::ustring& arg)
{
std::vector parts;
std::size_t delim1 = 0;
std::size_t delim2 = 0;
delim1 = arg.find('/', 0);
if (delim1 == std::string::npos)
return parts;
delim2 = arg.find('/', delim1 + 1);
if (delim2 == std::string::npos)
return parts;
parts.emplace_back(arg.substr(0, delim1));
parts.emplace_back(arg.substr(delim1 + 1, delim2 - delim1 - 1));
parts.emplace_back(arg.substr(delim2 + 1));
return parts;
}
static void
lookup_one_sync(const Glib::ustring& arg)
{
if (arg.find('/') != std::string::npos)
{
/* service/protocol/domain */
const auto parts = split_service_parts(arg);
if (parts.size() != 3)
{
usage();
return;
}
try
{
const auto targets = resolver->lookup_service(parts[0], parts[1], parts[2], cancellable);
print_resolved_service(arg, targets);
}
catch (const Gio::ResolverError& err)
{
std::cerr << err.what() << std::endl;
}
}
else if (Gio::hostname_is_ip_address(arg))
{
auto addr = Gio::InetAddress::create(arg);
try
{
Glib::ustring name = resolver->lookup_by_address(addr, cancellable);
print_resolved_name(arg, name);
}
catch (const Gio::ResolverError& err)
{
std::cerr << err.what() << std::endl;
}
}
else
{
std::list> addresses;
try
{
addresses = resolver->lookup_by_name(arg, cancellable);
print_resolved_addresses(arg, addresses);
}
catch (const Gio::ResolverError& err)
{
std::cerr << err.what() << std::endl;
}
}
}
static void
lookup_thread(const Glib::ustring& arg)
{
lookup_one_sync(arg);
}
static std::vector
start_threaded_lookups(char** argv, int argc)
{
std::vector result;
for (auto i = 0; i < argc; i++)
{
const Glib::ustring arg = argv[i];
const auto thread = new std::thread(&lookup_thread, arg);
result.emplace_back(thread);
}
return result;
}
static void
lookup_by_addr_callback(Glib::RefPtr result, const Glib::ustring& phys)
{
try
{
print_resolved_name(phys, resolver->lookup_by_address_finish(result));
}
catch (const Gio::ResolverError& err)
{
std::cerr << err.what() << std::endl;
done_lookup();
}
}
static void
lookup_by_name_callback(Glib::RefPtr result, const Glib::ustring& name)
{
try
{
print_resolved_addresses(name, resolver->lookup_by_name_finish(result));
}
catch (const Gio::ResolverError& err)
{
std::cerr << err.what() << std::endl;
}
}
static void
lookup_service_callback(Glib::RefPtr result, const Glib::ustring& service)
{
try
{
print_resolved_service(service, resolver->lookup_service_finish(result));
}
catch (const Gio::ResolverError& err)
{
std::cerr << err.what() << std::endl;
}
}
static void
start_async_lookups(char** argv, int argc)
{
for (auto i = 0; i < argc; i++)
{
Glib::ustring arg(argv[i]);
if (arg.find('/') != std::string::npos)
{
/* service/protocol/domain */
auto parts = split_service_parts(arg);
if (parts.size() != 3)
{
usage();
return;
}
resolver->lookup_service_async(parts[0], parts[1], parts[2],
sigc::bind(sigc::ptr_fun(lookup_service_callback), Glib::ustring(argv[i])), cancellable);
}
else if (Gio::hostname_is_ip_address(argv[i]))
{
auto addr = Gio::InetAddress::create(argv[i]);
resolver->lookup_by_address_async(
addr, sigc::bind(sigc::ptr_fun(lookup_by_addr_callback), argv[i]), cancellable);
}
else
{
resolver->lookup_by_name_async(
argv[i], sigc::bind(sigc::ptr_fun(lookup_by_name_callback), argv[i]), cancellable);
}
/* Stress-test the reloading code */
// g_signal_emit_by_name (resolver, "reload");
}
}
static void
print_connectable_sockaddr(Glib::RefPtr sockaddr)
{
Glib::ustring phys;
auto isa = Glib::RefPtr::cast_dynamic(sockaddr);
if (!isa)
{
std::cerr << Glib::ustring::compose("Error: Unexpected sockaddr type '%1'\n",
g_type_name_from_instance((GTypeInstance*)sockaddr->gobj()));
}
else
{
phys = isa->get_address()->to_string();
std::cout << Glib::ustring::compose("Address: %1%2%3:%4\n",
phys.find(':') != std::string::npos ? "[" : "", phys,
phys.find(':') != std::string::npos ? "]" : "", isa->get_port());
}
}
static void
do_sync_connectable(Glib::RefPtr enumerator)
{
Glib::RefPtr sockaddr;
while ((sockaddr = enumerator->next(cancellable)))
print_connectable_sockaddr(sockaddr);
done_lookup();
}
static void do_async_connectable(Glib::RefPtr enumerator);
static void
got_next_async(
Glib::RefPtr result, Glib::RefPtr enumerator)
{
try
{
const auto sockaddr = enumerator->next_finish(result);
if (sockaddr)
{
print_connectable_sockaddr(sockaddr);
do_async_connectable(enumerator);
}
else
{
done_lookup();
}
}
catch (const Gio::ResolverError& err)
{
std::cerr << err.what() << std::endl;
}
}
Glib::RefPtr global_enumerator;
static void
do_async_connectable(Glib::RefPtr enumerator)
{
enumerator->next_async(cancellable, sigc::bind(sigc::ptr_fun(got_next_async), enumerator));
}
Glib::RefPtr global_connectable;
static void
do_connectable(const Glib::ustring& arg, gboolean synchronous)
{
std::vector parts;
Glib::RefPtr connectable;
if (arg.find('/') != Glib::ustring::npos)
{
/* service/protocol/domain */
parts = split_service_parts(arg);
if (parts.size() != 3)
{
usage();
return;
}
connectable = Gio::NetworkService::create(parts[0], parts[1], parts[2]);
}
else
{
Glib::ustring host;
guint16 port = 0;
const auto pos = arg.find(':');
if (pos != Glib::ustring::npos)
{
host = arg.substr(0, pos);
auto port_str = arg.substr(pos);
port = std::stoul(port_str.raw());
}
if (Gio::hostname_is_ip_address(host))
{
const auto addr = Gio::InetAddress::create(host);
connectable = Gio::InetSocketAddress::create(addr, port);
}
else
connectable = Gio::NetworkAddress::create(arg.raw(), port);
}
const auto enumerator = connectable->enumerate();
if (synchronous)
do_sync_connectable(enumerator);
else
do_async_connectable(enumerator);
}
#ifdef G_OS_UNIX
static volatile int cancel_fd;
static void
interrupted(int /*sig*/)
{
const int save_errno = errno;
while (write(cancel_fd, "", 1) < 0 && errno == EINTR)
{
}
errno = save_errno;
}
static bool
async_cancel(Glib::IOCondition /*cond*/, Glib::RefPtr the_cancellable)
{
the_cancellable->cancel();
return false;
}
#endif
int
main(int argc, char** argv)
{
auto synchronous = false;
auto use_connectable = false;
#ifdef G_OS_UNIX
Glib::RefPtr chan;
sigc::connection watch_conn;
#endif
// TODO: Use Glib::OptionContext.
while (argc >= 2 && argv[1][0] == '-')
{
if (!strcmp(argv[1], "-s"))
synchronous = true;
else if (!strcmp(argv[1], "-c"))
use_connectable = true;
else
usage();
argv++;
argc--;
}
Gio::init();
if (argc < 2 || (argc > 2 && use_connectable))
usage();
resolver = Gio::Resolver::get_default();
cancellable = Gio::Cancellable::create();
#ifdef G_OS_UNIX
/* Set up cancellation; we want to cancel if the user ^C's the
* program, but we can't cancel directly from an interrupt.
*/
int cancel_fds[2];
if (pipe(cancel_fds) < 0)
{
perror("pipe");
exit(1);
}
cancel_fd = cancel_fds[1];
signal(SIGINT, interrupted);
chan = Glib::IOChannel::create_from_fd(cancel_fds[0]);
const auto source = chan->create_watch(Glib::IO_IN);
watch_conn = source->connect(sigc::bind(sigc::ptr_fun(async_cancel), cancellable));
#endif
nlookups = argc - 1;
loop = Glib::MainLoop::create(true);
std::vector threads;
if (use_connectable)
do_connectable(argv[1], synchronous);
else
{
if (synchronous)
threads = start_threaded_lookups(argv + 1, argc - 1);
else
start_async_lookups(argv + 1, argc - 1);
}
loop->run();
// Join and delete each thread:
std::for_each(threads.begin(), threads.end(), [](std::thread* thread) {
thread->join();
delete thread;
});
#ifdef G_OS_UNIX
watch_conn.disconnect();
#endif
return 0;
}