aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMattias Andrée <m@maandree.se>2025-03-24 22:58:14 +0100
committerMattias Andrée <m@maandree.se>2025-03-24 22:58:14 +0100
commit12d7f9b45303fb458cb21a3e0e430af96c781d8b (patch)
tree2b9c674f474d8ab4d6b35c657d4c005b4089192d
downloadlibgeome-12d7f9b45303fb458cb21a3e0e430af96c781d8b.tar.gz
libgeome-12d7f9b45303fb458cb21a3e0e430af96c781d8b.tar.bz2
libgeome-12d7f9b45303fb458cb21a3e0e430af96c781d8b.tar.xz
First commit
Signed-off-by: Mattias Andrée <m@maandree.se>
-rw-r--r--.gitignore14
-rw-r--r--LICENSE15
-rw-r--r--Makefile77
-rw-r--r--README93
-rw-r--r--TODO14
-rw-r--r--common.h51
-rw-r--r--config.mk8
-rw-r--r--libgeome.h273
-rw-r--r--libgeome_basic_context.c36
-rw-r--r--libgeome_get_from_command.c100
-rw-r--r--libgeome_get_from_file.c95
-rw-r--r--libgeome_get_from_time.c38
-rw-r--r--libgeome_get_from_timezone.c221
-rw-r--r--libgeome_netservices.c34
-rw-r--r--mk/linux.mk6
-rw-r--r--mk/macos.mk6
-rw-r--r--mk/windows.mk6
-rw-r--r--util.c356
18 files changed, 1443 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a071ed4
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,14 @@
+*\#*
+*~
+*.o
+*.a
+*.lo
+*.su
+*.so
+*.so.*
+*.dll
+*.dylib
+*.gch
+*.gcov
+*.gcno
+*.gcda
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..0e6be1c
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,15 @@
+ISC License
+
+© 2025 Mattias Andrée <m@maandree.se>
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..11ab463
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,77 @@
+.POSIX:
+
+CONFIGFILE = config.mk
+include $(CONFIGFILE)
+
+OS = linux
+# Linux: linux
+# Mac OS: macos
+# Windows: windows
+include mk/$(OS).mk
+
+
+LIB_MAJOR = 1
+LIB_MINOR = 0
+LIB_VERSION = $(LIB_MAJOR).$(LIB_MINOR)
+LIB_NAME = geome
+
+
+OBJ =\
+ libgeome_netservices.o\
+ libgeome_basic_context.o\
+ libgeome_get_from_time.o\
+ libgeome_get_from_timezone.o\
+ libgeome_get_from_file.o\
+ libgeome_get_from_command.o\
+ util.o
+
+HDR =\
+ libgeome.h\
+ common.h
+
+LOBJ = $(OBJ:.o=.lo)
+
+
+all: libgeome.a libgeome.$(LIBEXT)
+$(OBJ): $(HDR)
+$(LOBJ): $(HDR)
+
+.c.o:
+ $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS)
+
+.c.lo:
+ $(CC) -fPIC -c -o $@ $< $(CFLAGS) $(CPPFLAGS)
+
+libgeome.a: $(OBJ)
+ @rm -f -- $@
+ $(AR) rc $@ $(OBJ)
+ $(AR) ts $@ > /dev/null
+
+libgeome.$(LIBEXT): $(LOBJ)
+ $(CC) $(LIBFLAGS) -o $@ $(LOBJ) $(LDFLAGS)
+
+install: libgeome.a libgeome.$(LIBEXT)
+ mkdir -p -- "$(DESTDIR)$(PREFIX)/lib"
+ mkdir -p -- "$(DESTDIR)$(PREFIX)/include"
+ cp -- libgeome.a "$(DESTDIR)$(PREFIX)/lib/"
+ cp -- libgeome.$(LIBEXT) "$(DESTDIR)$(PREFIX)/lib/libgeome.$(LIBMINOREXT)"
+ $(FIX_INSTALL_NAME) "$(DESTDIR)$(PREFIX)/lib/libgeome.$(LIBMINOREXT)"
+ ln -sf -- libgeome.$(LIBMINOREXT) "$(DESTDIR)$(PREFIX)/lib/libgeome.$(LIBMAJOREXT)"
+ ln -sf -- libgeome.$(LIBMAJOREXT) "$(DESTDIR)$(PREFIX)/lib/libgeome.$(LIBEXT)"
+ cp -- libgeome.h "$(DESTDIR)$(PREFIX)/include/"
+
+uninstall:
+ -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libgeome.a"
+ -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libgeome.$(LIBMAJOREXT)"
+ -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libgeome.$(LIBMINOREXT)"
+ -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libgeome.$(LIBEXT)"
+ -rm -f -- "$(DESTDIR)$(PREFIX)/include/libgeome.h"
+
+clean:
+ -rm -f -- *.o *.a *.lo *.su *.so *.so.* *.dll *.dylib
+ -rm -f -- *.gch *.gcov *.gcno *.gcda *.$(LIBEXT)
+
+.SUFFIXES:
+.SUFFIXES: .lo .o .c
+
+.PHONY: all install uninstall clean
diff --git a/README b/README
new file mode 100644
index 0000000..c94d7fd
--- /dev/null
+++ b/README
@@ -0,0 +1,93 @@
+NAME
+ libgeome - Locate local user's geographical location
+
+SYNOPSIS
+ #include <libgeome.h>
+
+ #define LIBGEOME_DATUM_LATITUDE /* omitted */
+ #define LIBGEOME_DATUM_LONGITUDE /* omitted */
+ #define LIBGEOME_DATUM_ALTITUDE /* omitted */
+
+ struct libgeome_data {
+ uint64_t requested_data;
+ double latitude;
+ double longitude;
+ double altitude;
+ };
+
+ struct libgeome_context {
+ union {
+ const void *cptr;
+ void *ptr;
+ int i;
+ unsigned int u;
+ size_t n;
+ } user_data;
+ void (*print_error)(struct libgeome_context *, const char *, ...);
+ void (*print_abort)(struct libgeome_context *, const char *, ...);
+ void (*print_debug)(struct libgeome_context *, const char *, ...);
+ };
+
+ struct libgeome_netservice {
+ uint64_t can_fetch;
+ size_t service_id;
+ size_t limit;
+ unsigned int alarm_seconds;
+ const char *path;
+ const char *const *args;
+ };
+
+ extern struct libgeome_netservice libgeome_netservices[];
+ extern const size_t libgeome_netservices_count;
+
+ void libgeome_basic_context(struct libgeome_context *, const char *);
+
+ int libgeome_get_from_time(struct libgeome_context *, struct libgeome_data *);
+ int libgeome_get_from_timezone(struct libgeome_context *, struct libgeome_data *);
+ int libgeome_get_from_file(struct libgeome_context *, struct libgeome_data *, const char *);
+ int libgeome_get_from_command(struct libgeome_context *, struct libgeome_data *, size_t,
+ unsigned int, const char *, const char *const *);
+ int libgeome_get_from_netservice(struct libgeome_context *, struct libgeome_data *,
+ const struct libgeome_netservice *);
+
+ Link with -lgeome -lm.
+
+DESCRIPTION
+ libgeome is a library which provides functionally for looking up
+ the local user's location on the Globe.
+
+ To use libgeome, first create and initialise a struct libgeome_context
+ which will be used for all other library calls. This can easily be done
+ by creating a statically allocated struct libgeome_context and call the
+ libgeome_basic_context(3) function with it as the first argument and
+ the name of the program as the second argument, optionally with
+ "libgeome" added make it clear where error messages are printed from.
+
+ libgeome_get_from_file(3) can be used to check if the user has specified
+ his location in /etc/geolocation (use NULL as the third argument) or any
+ other file (specified by the third argument).
+
+ The next best thing is to use libgeome_get_from_timezone(3) to get a
+ rough location based on the user's timezone. But if this doesn't work,
+ libgeome_get_from_time(3) must be used, which provides only gives an
+ approximate longitude based one the current timezone, which is affected
+ by daylight saving, which may be desirable for some applications but not
+ for other applications.
+
+ Unless the location was determined using the libgeome_get_from_file(3)
+ function, it may be desirable to attempt to get a more accurate location
+ using network location services. This should of course not be done
+ without the user's consent as this connects to a third party service.
+ The function libgeome_get_from_netservice(3) is used for this, and a
+ list of services is provided in libgeome_netservices(3), with the number
+ of services listed provided in libgeome_netservices_count(3). You can
+ also add custom services, or run local program, using the
+ libgeome_get_from_command(3) function. If it cannot parse the output,
+ can reformat it as "%s %s\n", <latitude>, <longitude>.
+
+ When alling a libgeome function, any struct libgeome_data must have
+ its .requested_data set. This lets the called function 1) know what data
+ to request, and 2) what fields are available in the application's
+ version of the structure so that it doesn't write outside it if it is
+ out of date. There for it is critical that the application doesn't
+ simply set all but, but instead only recognised ones.
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..f583938
--- /dev/null
+++ b/TODO
@@ -0,0 +1,14 @@
+Write man pages
+Implement GeoClue support
+Implement CoreLocation support
+Add support for more types of location data
+Add location cache
+Add daemon
+Add support for detecting changes to network
+Add support for detecting changes to TZFILE (/etc/localtime)
+Add libgeome_get_from_command_async
+Add libgeome_get_from_netservice_async
+Add support for GPS
+Add support for averaging non-outlier netservices
+Add stream support (run a command and continuouly read lines to receive updates)
+Add support for more automatic location determination
diff --git a/common.h b/common.h
new file mode 100644
index 0000000..db3bae2
--- /dev/null
+++ b/common.h
@@ -0,0 +1,51 @@
+/* See LICENSE file for copyright and license details. */
+#include "libgeome.h"
+
+#include <sys/wait.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <math.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+
+#ifndef TZDIR
+# define TZDIR "/usr/share/zoneinfo"
+#endif
+
+#ifndef TZFILE
+# define TZFILE "/etc/localtime"
+#endif
+
+#ifndef GEOFILE
+# define GEOFILE "/etc/geolocation"
+#endif
+
+
+struct location {
+ double latitude;
+ double longitude;
+};
+
+struct spawn_join_info {
+ pid_t pid;
+ int ignore_sigchld;
+};
+
+
+char *libgeome_util_areadlink(struct libgeome_context *ctx, const char *path);
+int libgeome_util_safe_pipe(struct libgeome_context *ctx, int fds[2]);
+int libgeome_util_spawn_async_read(struct libgeome_context *ctx, const char *path, const char *const *argv,
+ unsigned int alarm_seconds, struct spawn_join_info *info_out, int *fd_out);
+void libgeome_util_spawn_async_kill(struct libgeome_context *ctx, const struct spawn_join_info *info, int signum);
+int libgeome_util_spawn_async_join(struct libgeome_context *ctx, const struct spawn_join_info *info, int *status_out);
+uint64_t libgeome_util_parse_xml(char *s, struct location *location_out);
+uint64_t libgeome_util_parse_json(char *s, struct location *location_out);
+uint64_t libgeome_util_parse_plain(char *s, struct location *location_out);
+uint64_t libgeome_util_parse_output(char *s, struct location *location_out);
diff --git a/config.mk b/config.mk
new file mode 100644
index 0000000..f4adf12
--- /dev/null
+++ b/config.mk
@@ -0,0 +1,8 @@
+PREFIX = /usr
+MANPREFIX = $(PREFIX)/share/man
+
+CC = c99
+
+CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE
+CFLAGS =
+LDFLAGS =
diff --git a/libgeome.h b/libgeome.h
new file mode 100644
index 0000000..48e40e3
--- /dev/null
+++ b/libgeome.h
@@ -0,0 +1,273 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef LIBGEOME_H
+#define LIBGEOME_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+
+#define LIBGEOME_DATUM_LATITUDE UINT64_C(0x0000000000000001)
+#define LIBGEOME_DATUM_LONGITUDE UINT64_C(0x0000000000000002)
+#define LIBGEOME_DATUM_ALTITUDE UINT64_C(0x0000000000000004)
+
+
+/**
+ * Geolocation data
+ */
+struct libgeome_data {
+ /**
+ * Requested data, data that was not retrieved will be cleared
+ */
+ uint64_t requested_data;
+
+ /**
+ * GPS latitude (geodetic) in degrees north
+ *
+ * Bit in `.requested_data`: LIBGEOME_DATUM_LATITUDE
+ */
+ double latitude;
+
+ /**
+ * GPS longitude (geodetic) in degrees east
+ *
+ * Bit in `.requested_data`: LIBGEOME_DATUM_LONGITUDE
+ */
+ double longitude;
+
+ /**
+ * Ellipsoidal altitude in meters
+ *
+ * Bit in `.requested_data`: LIBGEOME_DATUM_ALTITUDE
+ */
+ double altitude;
+};
+
+
+/**
+ * Library context
+ */
+struct libgeome_context {
+ /**
+ * The application is free to used this field as sees fit
+ */
+ union {
+ const void *cptr;
+ void *ptr;
+ int i;
+ unsigned int u;
+ size_t n;
+ } user_data;
+
+ /**
+ * Print error message
+ *
+ * @param ctx Function pointer container
+ * @param fmt Format string
+ * @param ... Format argument
+ */
+ void (*print_error)(struct libgeome_context *ctx, const char *fmt, ...);
+
+ /**
+ * Print error message for an error that the library
+ * cannot recover from. The library will abort the
+ * process after calling this function.
+ *
+ * @param ctx Function pointer container
+ * @param fmt Format string
+ * @param ... Format argument
+ */
+ void (*print_abort)(struct libgeome_context *ctx, const char *fmt, ...);
+
+ /**
+ * Print debug message
+ *
+ * @param ctx Function pointer container
+ * @param fmt Format string
+ * @param ... Format argument
+ */
+ void (*print_debug)(struct libgeome_context *ctx, const char *fmt, ...);
+};
+
+
+/**
+ * Geolocation network service description
+ */
+struct libgeome_netservice {
+ /**
+ * Geolocation data that may be provided by the service
+ *
+ * 0 if unknown (always known (non-zero) when retreived
+ * from libgeome_netservices)
+ *
+ * See `struct libgeome_data.requested_data`
+ */
+ uint64_t can_fetch;
+
+ /**
+ * A unique number assigned to each unique service,
+ * so that alternatives that user the same service
+ * can be recognised
+ *
+ * Only set when stated so in the source
+ *
+ * This is used because a service may have multiple
+ * ways to fetch the data, as all supported ones
+ * are added in case one is removed in the future;
+ * this could be multiple APIs or even multiple
+ * URLs
+ *
+ * These number are sorted and start from 0,
+ * and increment by one for each new service that's
+ * not just another way to use the service in the
+ * previous entry
+ */
+ size_t service_id;
+
+ /**
+ * Expected maximum output size
+ */
+ size_t limit;
+
+ /**
+ * Expected maximum invokation duration
+ */
+ unsigned int alarm_seconds;
+
+ /**
+ * Path command to run to get the location
+ * (this would be "curl")
+ */
+ const char *path;
+
+ /**
+ * Command to run to get the location
+ * (this would be curl with some parameters
+ * and an URL)
+ */
+ const char *const *args;
+};
+
+
+/**
+ * Built in list of geolocation network service
+ *
+ * `.service_id` is set
+ *
+ * Be aware these services may have terms of use that
+ * restrict how you are allowed to use them.
+ */
+extern struct libgeome_netservice libgeome_netservices[];
+
+/**
+ * The number of elements in `libgeome_netservices`
+ */
+extern const size_t libgeome_netservices_count;
+
+
+/**
+ * Create a basic initialisation for `struct libgeome_context`
+ *
+ * Note that the implementation will use `ctx_out->user_data` to store `procname`
+ * there. It will not make a copy of `procname`, so `procname` must be valid while
+ * the context is in use
+ *
+ * `*ctx_out->print_error` and `*ctx_out->print_abort` will print plain
+ * error messages, but `*ctx_out->print_debug` will not do anything.
+ * If you want debug outputs, you can assign `ctx_out->print_error` or
+ * `ctx_out->print_abort` to `ctx_out->print_debug`.
+ *
+ * @param ctx_out Structure to initialise
+ * @param procname The name of the process, will be prefixed to all outputs
+ * using the format `"%s: ", procname`. It may be a good idea
+ * to add prepend ": libgeome: " to make it clear that the
+ * output comes from libgeome
+ */
+void libgeome_basic_context(struct libgeome_context *ctx_out, const char *procname);
+
+/**
+ * Get a very rough location guess based on
+ * the current timezone's offset from UTC
+ *
+ * This can always be used to get the longitude,
+ * but only the longitude
+ *
+ * @param ctx Library context
+ * @param out Output parameter, `out->requested_data` must be set
+ * @return 0 if data could be retrieved, even if none of the requested data
+ * could be retrieved, -1 otherwise
+ */
+int libgeome_get_from_time(struct libgeome_context *ctx, struct libgeome_data *out);
+
+/**
+ * Get a rather rough location guess based on
+ * timezone data: the nominally the timezone's
+ * most populous city
+ *
+ * It is not guaranteed that the needed information
+ * is installed on the machine, and even if it is,
+ * does not necessarily contain needed data for the
+ * user's timezone
+ *
+ * This function will get both the latitude and
+ * the longitude but nothing more, provided it found
+ * data for the timezone
+ *
+ * @param ctx Library context
+ * @param out Output parameter, `out->requested_data` must be set
+ * @return 0 if data could be retrieved, even if none of the requested data
+ * could be retrieved, -1 otherwise
+ */
+int libgeome_get_from_timezone(struct libgeome_context *ctx, struct libgeome_data *out);
+
+/**
+ * Get the user's location from a file where it has
+ * been manually stored
+ *
+ * This function will get both the latitude and
+ * the longitude but nothing more
+ *
+ * The default file is /etc/geolocation unless changed at compile time
+ *
+ * @param ctx Library context
+ * @param out Output parameter, `out->requested_data` must be set
+ * @param path The file to read, `NULL` to use the default
+ * @return 0 if data could be retrieved, even if none of the requested data
+ * could be retrieved, -1 otherwise
+ */
+int libgeome_get_from_file(struct libgeome_context *ctx, struct libgeome_data *out, const char *path);
+
+/**
+ * Spawn a program and read it's standard output
+ * to get the user's location data
+ *
+ * @param ctx Library context
+ * @param out Output parameter, `out->requested_data` must be set
+ * @param limit Number of bytes to stop reading output after, zero for default
+ * @param alarm_sec If non-zero, the number of seconds to let the subprocess live
+ * @param path Path to command to spawn
+ * @param argv Command line arguments for the process, including the name of the command
+ * @return 0 if data could be retrieved, even if none of the requested data
+ * could be retrieved, -1 otherwise
+ */
+int libgeome_get_from_command(struct libgeome_context *ctx, struct libgeome_data *out, size_t limit,
+ unsigned int alarm_sec, const char *path, const char *const *argv);
+
+/**
+ * Spawn a program and read it's standard output
+ * to get the user's location data
+ *
+ * @param ctx Library context
+ * @param out Output parameter, `out->requested_data` must be set
+ * @param svc Parameters for the command to run
+ * @return 0 if data could be retrieved, even if none of the requested data
+ * could be retrieved, -1 otherwise
+ */
+inline int
+libgeome_get_from_netservice(struct libgeome_context *ctx, struct libgeome_data *out,
+ const struct libgeome_netservice *svc)
+{
+ return libgeome_get_from_command(ctx, out, svc->limit, svc->alarm_seconds, svc->path, svc->args);
+}
+
+
+#endif
diff --git a/libgeome_basic_context.c b/libgeome_basic_context.c
new file mode 100644
index 0000000..878302a
--- /dev/null
+++ b/libgeome_basic_context.c
@@ -0,0 +1,36 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic ignored "-Wsuggest-attribute=format"
+#endif
+
+
+static void
+print(struct libgeome_context *ctx, const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ fprintf(stderr, "%s: ", (const char *)ctx->user_data.cptr);
+ vfprintf(stderr, fmt, args);
+ va_end(args);
+}
+
+
+static void
+dont_print(struct libgeome_context *ctx, const char *fmt, ...)
+{
+ (void) ctx;
+ (void) fmt;
+}
+
+
+void
+libgeome_basic_context(struct libgeome_context *ctx_out, const char *procname)
+{
+ ctx_out->user_data.cptr = procname;
+ ctx_out->print_error = &print;
+ ctx_out->print_abort = &print;
+ ctx_out->print_debug = &dont_print;
+}
diff --git a/libgeome_get_from_command.c b/libgeome_get_from_command.c
new file mode 100644
index 0000000..4d1c5af
--- /dev/null
+++ b/libgeome_get_from_command.c
@@ -0,0 +1,100 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+static char *
+spawn_read(struct libgeome_context *ctx, const char *path, const char *const *argv, size_t limit, unsigned int alarm_seconds)
+{
+ struct spawn_join_info info;
+ char *ret = NULL, *new;
+ size_t size = 0;
+ size_t len = 0;
+ ssize_t r;
+ int fd, status;
+
+ if (libgeome_util_spawn_async_read(ctx, path, argv, alarm_seconds, &info, &fd))
+ return NULL;
+
+ for (;;) {
+ if (len == size) {
+ size += 512U;
+ new = realloc(ret, size);
+ if (!new) {
+ ctx->print_error(ctx, "realloc: %s\n", strerror(errno));
+ goto read_fail;
+ }
+ ret = new;
+ }
+ r = read(fd, &ret[len], size - len);
+ if (r <= 0) {
+ if (!r)
+ break;
+ if (errno == EINTR)
+ continue;
+ ctx->print_error(ctx, "read <pipe>: %s\n", strerror(errno));
+ read_fail:
+ close(fd);
+ free(ret);
+ ret = NULL;
+ libgeome_util_spawn_async_kill(ctx, &info, SIGKILL);
+ break;
+ }
+ len += (size_t)r;
+ if (len >= limit)
+ break;
+ }
+
+ if (len) {
+ if (len == size)
+ len--;
+ ret[len] = '\0';
+ } else {
+ free(ret);
+ ret = NULL;
+ }
+
+ if (libgeome_util_spawn_async_join(ctx, &info, &status) || status != 0) {
+ free(ret);
+ ret = NULL;
+ }
+ return ret;
+}
+
+
+int
+libgeome_get_from_command(struct libgeome_context *ctx, struct libgeome_data *out, size_t limit,
+ unsigned int alarm_secs, const char *path, const char *const *argv)
+{
+ struct location location;
+ char *text;
+
+ text = spawn_read(ctx, path, argv, limit ? limit : 2048U, alarm_secs);
+ if (!text)
+ return -1;
+ out->requested_data &= libgeome_util_parse_output(text, &location);
+ free(text);
+
+ if (out->requested_data & LIBGEOME_DATUM_LATITUDE) {
+ if (!isfinite(location.latitude) || location.latitude < -90 || location.latitude > -90) {
+ ctx->print_debug(ctx, "invalide latitude retrieved: %g\n", location.latitude);
+ out->requested_data ^= LIBGEOME_DATUM_LATITUDE;
+ } else {
+ out->latitude = location.latitude;
+ }
+ }
+
+ if (out->requested_data & LIBGEOME_DATUM_LONGITUDE) {
+ if (!isfinite(location.longitude) || location.longitude < -90 || location.longitude > -90) {
+ ctx->print_debug(ctx, "invalide longitude retrieved: %g\n", location.longitude);
+ out->requested_data ^= LIBGEOME_DATUM_LONGITUDE;
+ } else {
+ out->longitude = location.longitude;
+ }
+ }
+
+ return 0;
+}
+
+
+extern inline int libgeome_get_from_netservice(struct libgeome_context *ctx, struct libgeome_data *out,
+ const struct libgeome_netservice *svc);
diff --git a/libgeome_get_from_file.c b/libgeome_get_from_file.c
new file mode 100644
index 0000000..bbed448
--- /dev/null
+++ b/libgeome_get_from_file.c
@@ -0,0 +1,95 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+static int
+parse(char *s, struct location *location)
+{
+ errno = 0;
+
+ location->latitude = strtod(s, &s);
+ if (errno)
+ return -1;
+
+ while (isspace(*s))
+ s++;
+ if (*s == ',' || *s == ':')
+ s++;
+ while (isspace(*s))
+ s++;
+
+ location->longitude = strtod(s, &s);
+ if (errno)
+ return -1;
+
+ while (isspace(*s) && *s != '\n')
+ s++;
+
+ return (*s && *s != '\n') ? -1 : 0;
+}
+
+
+int
+libgeome_get_from_file(struct libgeome_context *ctx, struct libgeome_data *out, const char *path)
+{
+ struct location location;
+ FILE *f;
+ char *line = NULL;
+ size_t size = 0;
+ int r;
+
+ if (!path)
+ path = GEOFILE;
+
+ f = fopen(path, "r");
+ if (!f) {
+ if (errno == ENOENT)
+ ctx->print_debug(ctx, "fopen %s r: %s\n", path, strerror(errno));
+ else
+ ctx->print_error(ctx, "fopen %s r: %s\n", path, strerror(errno));
+ return 1;
+ }
+
+again:
+ if (getline(&line, &size, f) < 0) {
+ if (feof(f)) {
+ ctx->print_debug(ctx, "%s is empty\n", path, strerror(errno));
+ } else {
+ if (errno == EINTR) {
+ clearerr(f);
+ goto again;
+ }
+ ctx->print_error(ctx, "getline %s: %s\n", path, strerror(errno));
+ }
+ fclose(f);
+ free(line);
+ return 1;
+ }
+
+ fclose(f);
+
+ r = parse(line, &location);
+ free(line);
+ if (!r) {
+ if (!isfinite(location.latitude) || !isfinite(location.longitude))
+ r = -1;
+ else if (location.latitude < -90 || location.latitude > 90)
+ r = -1;
+ else if (location.longitude < -180 || location.longitude > 180)
+ r = -1;
+ }
+ if (r) {
+ ctx->print_debug(ctx, "the first line of %s was not a coordinate pair\n", path, strerror(errno));
+ return 1;
+ }
+
+ out->requested_data &= LIBGEOME_DATUM_LATITUDE | LIBGEOME_DATUM_LONGITUDE;
+ if (out->requested_data) {
+ if (out->requested_data & LIBGEOME_DATUM_LATITUDE)
+ out->latitude = location.latitude;
+ if (out->requested_data & LIBGEOME_DATUM_LONGITUDE)
+ out->longitude = location.longitude;
+ }
+
+ return 0;
+}
diff --git a/libgeome_get_from_time.c b/libgeome_get_from_time.c
new file mode 100644
index 0000000..b512a34
--- /dev/null
+++ b/libgeome_get_from_time.c
@@ -0,0 +1,38 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+int
+libgeome_get_from_time(struct libgeome_context *ctx, struct libgeome_data *out)
+{
+ time_t utc, local, uoff;
+ struct tm tm;
+
+ utc = time(NULL);
+ if (utc < 0) {
+ ctx->print_error(ctx, "time: %s\n", strerror(errno));
+ return -1;
+ }
+ if (!localtime_r(&utc, &tm)) {
+ ctx->print_error(ctx, "localtime_r: %s\n", strerror(errno));
+ return -1;
+ }
+
+ local = (time_t)(tm.tm_hour * 60 * 60);
+ local += (time_t)(tm.tm_min * 60);
+ local += (time_t)tm.tm_sec;
+ utc %= (time_t)(24 * 60 * 60);
+ uoff = local - utc;
+
+ out->requested_data &= LIBGEOME_DATUM_LONGITUDE;
+ if (out->requested_data) {
+ out->longitude = (double)uoff / (24 * 60 * 60 / 360);
+ out->longitude = fmod(out->longitude, 360);
+ if (out->longitude < -180)
+ out->longitude += 360;
+ else if (out->longitude > 180)
+ out->longitude -= 360;
+ }
+
+ return 0;
+}
diff --git a/libgeome_get_from_timezone.c b/libgeome_get_from_timezone.c
new file mode 100644
index 0000000..a9d3b69
--- /dev/null
+++ b/libgeome_get_from_timezone.c
@@ -0,0 +1,221 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+static char *
+get_timezone(struct libgeome_context *ctx, int *env_determined_out)
+{
+ const char *tzdir;
+ const char *tz;
+ char *tzbuf = NULL;
+ char *ret;
+ size_t tzdirlen;
+
+ tz = getenv("TZ");
+ *env_determined_out = !!tz;
+ if (!tz) {
+ tzdir = getenv("TZDIR");
+ if (!tzdir)
+ tzdir = TZDIR;
+ tz = tzbuf = libgeome_util_areadlink(ctx, TZFILE);
+ if (!tz)
+ return NULL;
+ tzdirlen = strlen(tzdir);
+ if (!strncmp(tz, tzdir, tzdirlen) && tz[tzdirlen] == '/') {
+ tz = &tz[tzdirlen];
+ while (*tz == '/')
+ tz++;
+ }
+ }
+
+ ret = strdup(tz);
+ if (!ret)
+ ctx->print_error(ctx, "strdup: %s\n", strerror(errno));
+ free(tzbuf);
+ return ret;
+}
+
+
+static int
+get_timezone_location3(struct libgeome_context *ctx, const char *tz, const char *tabfile, struct location *location_out)
+{
+ const char *tzdir, *coords;
+ char *path, *line = NULL, *p;
+ size_t size = 0, tzlen = strlen(tz);
+ int lat_sign, lat_degrees, lat_minutes, lat_seconds = 0;
+ int lon_sign, lon_degrees, lon_minutes, lon_seconds = 0;
+ ssize_t len;
+ FILE *f;
+
+ tzdir = getenv("TZDIR");
+ if (!tzdir)
+ tzdir = TZDIR;
+
+ path = malloc(strlen(tzdir) + strlen(tabfile) + sizeof("/"));
+ if (!path) {
+ ctx->print_error(ctx, "malloc: %s\n", strerror(errno));
+ return -1;
+ }
+ stpcpy(stpcpy(stpcpy(path, tzdir), "/"), tabfile);
+
+ f = fopen(path, "r");
+ if (!f) {
+ if (errno == ENOENT)
+ ctx->print_debug(ctx, "fopen %s r: %s\n", path, strerror(errno));
+ else
+ ctx->print_error(ctx, "fopen %s r: %s\n", path, strerror(errno));
+ free(path);
+ return -1;
+ }
+
+again:
+ while (len = getline(&line, &size, f), len >= 0) {
+ p = line;
+ for (;; p++) {
+ if (!*p || *p == '\r' || *p == '\n' || *p == '#')
+ goto next_line;
+ if (*p == '\t')
+ break;
+ }
+
+ coords = ++p;
+ for (;; p++) {
+ if (!*p || *p == '\r' || *p == '\n' || *p == '#')
+ goto next_line;
+ if (*p == '\t')
+ break;
+ }
+ *p++ = '\0';
+
+ if (strncmp(p, tz, tzlen))
+ goto next_line;
+ p = &p[tzlen];
+ if (*p && *p != '\r' && *p != '\n' && *p != '#' && *p != '\t')
+ goto next_line;
+
+ goto found_coords;
+
+ next_line:;
+ }
+ if (!feof(f)) {
+ if (errno == EINTR) {
+ clearerr(f);
+ goto again;
+ }
+ ctx->print_error(ctx, "getline %s: %s\n", path, strerror(errno));
+ }
+ ctx->print_debug(ctx, "entry for timezone %s not found in %s\n", tz, path);
+
+ free(line);
+ free(path);
+ fclose(f);
+ return -1;
+
+bad_coords:
+ ctx->print_debug(ctx, "malformatted coordinates for %s in %s\n", tz, path);
+ free(line);
+ free(path);
+ fclose(f);
+ return -1;
+
+found_coords:
+ if (coords[0] != '+' && coords[0] != '-')
+ goto bad_coords;
+ if (!isdigit(coords[1]) || !isdigit(coords[2]))
+ goto bad_coords;
+ if (!isdigit(coords[3]) || !isdigit(coords[4]))
+ goto bad_coords;
+ lat_sign = coords[0] == '-' ? -1 : +1;
+ lat_degrees = (coords[1] & 15) * 10 + (coords[2] & 15);
+ lat_minutes = (coords[3] & 15) * 10 + (coords[4] & 15);
+ coords = &coords[5];
+ if (isdigit(coords[0]) && isdigit(coords[1])) {
+ lat_seconds = (coords[0] & 15) * 10 + (coords[1] & 15);
+ coords = &coords[2];
+ }
+
+ if (coords[0] != '+' && coords[0] != '-')
+ goto bad_coords;
+ if (!isdigit(coords[1]) || !isdigit(coords[2]) || !isdigit(coords[3]))
+ goto bad_coords;
+ if (!isdigit(coords[4]) || !isdigit(coords[5]))
+ goto bad_coords;
+ lon_sign = coords[0] == '-' ? -1 : +1;
+ lon_degrees = (coords[1] & 15) * 100 + (coords[2] & 15) * 10 + (coords[3] & 15);
+ lon_minutes = (coords[4] & 15) * 10 + (coords[5] & 15);
+ coords = &coords[6];
+ if (isdigit(coords[0]) && isdigit(coords[1])) {
+ lon_seconds = (coords[0] & 15) * 10 + (coords[1] & 15);
+ coords = &coords[2];
+ }
+
+ if (*coords)
+ goto bad_coords;
+
+ if (lat_degrees > 90 || lon_degrees > 180)
+ goto bad_coords;
+ if (lat_minutes >= 60 || lon_minutes >= 60)
+ goto bad_coords;
+ if (lat_seconds >= 60 || lon_seconds >= 60)
+ goto bad_coords;
+ if (lat_degrees == 90 && lat_minutes + lat_seconds)
+ goto bad_coords;
+ if (lon_degrees == 180 && lon_minutes + lon_seconds)
+ goto bad_coords;
+
+ location_out->latitude = (double)lat_degrees;
+ location_out->latitude += (double)lat_minutes / 60;
+ location_out->latitude += (double)lat_seconds / 3600;
+ location_out->latitude *= (double)lat_sign;
+
+ location_out->longitude = (double)lon_degrees;
+ location_out->longitude += (double)lon_minutes / 60;
+ location_out->longitude += (double)lon_seconds / 3600;
+ location_out->longitude *= (double)lon_sign;
+
+ free(line);
+ free(path);
+ fclose(f);
+ return 0;
+}
+
+
+static int
+get_timezone_location(struct libgeome_context *ctx, const char *tz, struct location *location_out)
+{
+ if (!get_timezone_location3(ctx, tz, "zone1970.tab", location_out))
+ return 0;
+ if (!get_timezone_location3(ctx, tz, "zone.tab", location_out))
+ return 0;
+ if (!get_timezone_location3(ctx, tz, "zonenow.tab", location_out))
+ return 0;
+ return -1;
+}
+
+
+int
+libgeome_get_from_timezone(struct libgeome_context *ctx, struct libgeome_data *out)
+{
+ struct location location;
+ char *tz;
+
+ tz = get_timezone(ctx, &(int){0});
+ if (!tz)
+ return -1;
+ if (get_timezone_location(ctx, tz, &location)) {
+ free(tz);
+ return -1;
+ }
+ free(tz);
+
+ out->requested_data &= LIBGEOME_DATUM_LATITUDE | LIBGEOME_DATUM_LONGITUDE;
+ if (out->requested_data) {
+ if (out->requested_data & LIBGEOME_DATUM_LATITUDE)
+ out->latitude = location.latitude;
+ if (out->requested_data & LIBGEOME_DATUM_LONGITUDE)
+ out->longitude = location.longitude;
+ }
+
+ return 0;
+
+}
diff --git a/libgeome_netservices.c b/libgeome_netservices.c
new file mode 100644
index 0000000..7701e2f
--- /dev/null
+++ b/libgeome_netservices.c
@@ -0,0 +1,34 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+#define GET(URL) {"curl", "-sL", URL, NULL}
+
+static const char *args0a[] = GET("http://ip-api.com/line?fields=lat,lon");
+static const char *args0b[] = GET("http://ip-api.com/csv?fields=lat,lon");
+static const char *args0c[] = GET("http://ip-api.com/json?fields=lat,lon");
+static const char *args0d[] = GET("http://ip-api.com/xml?fields=lat,lon");
+static const char *args1[] = GET("https://ipinfo.io/loc");
+static const char *args2[] = GET("http://ipwho.is");
+static const char *args3[] = GET("https://geolocation-db.com/json/");
+
+
+#define COMMON(SERVICE_ID, ARGS)\
+ {\
+ .can_fetch = (LIBGEOME_DATUM_LATITUDE | LIBGEOME_DATUM_LONGITUDE),\
+ .service_id = (SERVICE_ID), .limit = 2048U, .alarm_seconds = 5U,\
+ .path = "curl", .args = (ARGS)\
+ }
+
+struct libgeome_netservice libgeome_netservices[] = {
+ COMMON(0, args0a),
+ COMMON(0, args0b),
+ COMMON(0, args0c),
+ COMMON(0, args0d),
+ COMMON(1, args1),
+ COMMON(2, args2),
+ COMMON(3, args3)
+};
+
+
+const size_t libgeome_netservices_count = sizeof(libgeome_netservices) / sizeof(*libgeome_netservices);
diff --git a/mk/linux.mk b/mk/linux.mk
new file mode 100644
index 0000000..ad58f69
--- /dev/null
+++ b/mk/linux.mk
@@ -0,0 +1,6 @@
+LIBEXT = so
+LIBFLAGS = -shared -Wl,-soname,lib$(LIB_NAME).$(LIBEXT).$(LIB_MAJOR)
+LIBMAJOREXT = $(LIBEXT).$(LIB_MAJOR)
+LIBMINOREXT = $(LIBEXT).$(LIB_VERSION)
+
+FIX_INSTALL_NAME = :
diff --git a/mk/macos.mk b/mk/macos.mk
new file mode 100644
index 0000000..f058c70
--- /dev/null
+++ b/mk/macos.mk
@@ -0,0 +1,6 @@
+LIBEXT = dylib
+LIBFLAGS = -dynamiclib -Wl,-compatibility_version,$(LIB_MAJOR) -Wl,-current_version,$(LIB_VERSION)
+LIBMAJOREXT = $(LIB_MAJOR).$(LIBEXT)
+LIBMINOREXT = $(LIB_VERSION).$(LIBEXT)
+
+FIX_INSTALL_NAME = install_name_tool -id "$(PREFIX)/lib/libgeome.$(LIBMAJOREXT)"
diff --git a/mk/windows.mk b/mk/windows.mk
new file mode 100644
index 0000000..ed5ec8d
--- /dev/null
+++ b/mk/windows.mk
@@ -0,0 +1,6 @@
+LIBEXT = dll
+LIBFLAGS = -shared
+LIBMAJOREXT = $(LIB_MAJOR).$(LIBEXT)
+LIBMINOREXT = $(LIB_VERSION).$(LIBEXT)
+
+FIX_INSTALL_NAME = :
diff --git a/util.c b/util.c
new file mode 100644
index 0000000..3894c24
--- /dev/null
+++ b/util.c
@@ -0,0 +1,356 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+char *
+libgeome_util_areadlink(struct libgeome_context *ctx, const char *path)
+{
+ char *ret = NULL, *new;
+ size_t size = 0;
+ ssize_t r;
+
+ do {
+ new = realloc(ret, size += 128U);
+ if (!new) {
+ ctx->print_error(ctx, "realloc: %s\n", strerror(errno));
+ free(ret);
+ return NULL;
+ }
+ ret = new;
+ r = readlink(path, ret, size - 1U);
+ if (r < 0) {
+ ctx->print_error(ctx, "readlink %s: %s\n", path, strerror(errno));
+ free(ret);
+ return NULL;
+ }
+ } while ((size_t)r >= size - 2U);
+
+ ret[r] = '\0';
+ return ret;
+}
+
+
+int
+libgeome_util_safe_pipe(struct libgeome_context *ctx, int fds[2])
+{
+ int i, old_fd, new_fd, nul_fd = -1;
+
+ if (pipe(fds)) {
+ ctx->print_error(ctx, "pipe: %s\n", strerror(errno));
+ return -1;
+ }
+
+ if (fds[0] > 2 && fds[1] > 2)
+ return 0;
+
+ for (i = 0; i < 2; i++) {
+ if (fds[i] > 2)
+ continue;
+ new_fd = fcntl(fds[i], F_DUPFD, 3);
+ if (new_fd < 0) {
+ ctx->print_error(ctx, "fcntl F_DUPFD 3: %s\n", strerror(errno));
+ goto fail;
+ }
+ old_fd = fds[i];
+ close(fds[i]);
+ fds[i] = new_fd;
+ nul_fd = open("/dev/null", O_RDWR);
+ if (nul_fd != old_fd) {
+ ctx->print_error(ctx, "open /dev/null O_RDWR: %s\n", strerror(errno));
+ goto fail;
+ }
+ }
+
+ if (old_fd < 2) {
+ new_fd = dup(nul_fd);
+ if (new_fd < 0) {
+ ctx->print_error(ctx, "duo: %s\n", strerror(errno));
+ goto fail;
+ }
+ if (new_fd > 2)
+ close(new_fd);
+ }
+
+ return 0;
+
+fail:
+ close(fds[0]);
+ close(fds[1]);
+ return -1;
+}
+
+
+void
+libgeome_util_spawn_async_kill(struct libgeome_context *ctx, const struct spawn_join_info *info, int signum)
+{
+ (void) ctx;
+ kill(info->pid, signum);
+}
+
+
+int
+libgeome_util_spawn_async_read(struct libgeome_context *ctx, const char *path, const char *const *argv,
+ unsigned int alarm_seconds, struct spawn_join_info *info_out, int *fd_out)
+{
+ struct sigaction sa, old_sa;
+ sigset_t mask, old_mask;
+ int fds[2], ignore_sigchld;
+ pid_t pid;
+
+ if (sigemptyset(&mask)) {
+ ctx->print_error(ctx, "sigemptyset: %s\n", strerror(errno));
+ return -1;
+ }
+ if (sigaddset(&mask, SIGCHLD)) {
+ ctx->print_error(ctx, "sigaddset SIGCHLD: %s\n", strerror(errno));
+ return -1;
+ }
+ if (sigprocmask(SIG_BLOCK, &mask, &old_mask)) {
+ ctx->print_error(ctx, "sigprocmask SIG_BLOCK {SIGCHLD}: %s\n", strerror(errno));
+ return -1;
+ }
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = SIG_DFL;
+ if (sigaction(SIGCHLD, &sa, &old_sa)) {
+ ctx->print_error(ctx, "sigaction SIGCHLD: %s\n", strerror(errno));
+ if (sigprocmask(SIG_SETMASK, &old_mask, NULL)) {
+ ctx->print_abort(ctx, "sigprocmask SIG_SETMASK <old mask>: %s\n", strerror(errno));
+ abort();
+ }
+ return -1;
+ }
+ ignore_sigchld = !(old_sa.sa_flags & SA_SIGINFO) && old_sa.sa_handler == SIG_IGN;
+ if (!ignore_sigchld && sigaction(SIGCHLD, &old_sa, NULL)) {
+ ctx->print_abort(ctx, "sigaction SIGCHLD: %s\n", strerror(errno));
+ abort();
+ }
+
+ if (sigprocmask(SIG_SETMASK, &old_mask, NULL)) {
+ ctx->print_abort(ctx, "sigaction SIG_SETMASK <old mask>: %s\n", strerror(errno));
+ abort();
+ }
+
+ if (libgeome_util_safe_pipe(ctx, fds))
+ goto out;
+
+ pid = fork();
+ switch (pid) {
+ case -1:
+ ctx->print_error(ctx, "fork: %s\n", strerror(errno));
+ close(fds[0]);
+ close(fds[1]);
+ goto out;
+
+ case 0:
+ close(fds[0]);
+ if (dup2(fds[1], STDOUT_FILENO) != STDOUT_FILENO) {
+ ctx->print_error(ctx, "dup2 <pipe> <stdout>: %s\n", strerror(errno));
+ _exit(127);
+ }
+ close(fds[1]);
+ alarm(alarm_seconds);
+ execvp(path, (const void *)argv);
+ ctx->print_error(ctx, "execvp %s: %s\n", path, strerror(errno));
+ _exit(127);
+
+ default:
+ close(fds[1]);
+ break;
+ }
+
+ info_out->pid = pid;
+ info_out->ignore_sigchld = ignore_sigchld;
+ *fd_out = fds[0];
+ return 0;
+
+out:
+ if (ignore_sigchld) {
+ sa.sa_handler = SIG_IGN;
+ if (sigaction(SIGCHLD, &sa, NULL)) {
+ ctx->print_abort(ctx, "sigaction SIGCHLD: %s\n", strerror(errno));
+ abort();
+ }
+ while (waitpid(-1, NULL, WNOHANG) != -1);
+ }
+ return -1;
+}
+
+
+int
+libgeome_util_spawn_async_join(struct libgeome_context *ctx, const struct spawn_join_info *info, int *status_out)
+{
+ struct sigaction sa;
+ int ret = 0;
+
+ if (waitpid(info->pid, status_out, 0) != info->pid) {
+ ctx->print_error(ctx, "waitpid <subprocess> 0: %s\n", strerror(errno));
+ ret = -1;
+ }
+
+ if (info->ignore_sigchld) {
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = SIG_IGN;
+ if (sigaction(SIGCHLD, &sa, NULL)) {
+ ctx->print_abort(ctx, "sigaction SIGCHLD: %s\n", strerror(errno));
+ abort();
+ }
+ while (waitpid(-1, NULL, WNOHANG) != -1);
+ }
+
+ return ret;
+}
+
+
+uint64_t
+libgeome_util_parse_xml(char *s, struct location *location_out)
+{
+ uint64_t ret = 0;
+ char *p = s;
+
+ errno = 0;
+
+ p = strstr(s, "<lat>");
+ if (!p)
+ p = strstr(s, "<latitude>");
+ if (!p)
+ goto skip_latitude;
+ p = &strchr(p, '>')[1];
+ while (isspace(*p))
+ p++;
+ location_out->latitude = strtod(p, &p);
+ if (errno)
+ goto skip_latitude;
+ while (isspace(*p))
+ p++;
+ if (*p != '<')
+ goto skip_latitude;
+ ret |= LIBGEOME_DATUM_LATITUDE;
+skip_latitude:
+
+ p = strstr(s, "<lon>");
+ if (!p)
+ p = strstr(s, "<long>");
+ if (!p)
+ p = strstr(s, "<lng>");
+ if (!p)
+ p = strstr(s, "<longitude>");
+ if (!p)
+ goto skip_longitude;
+ p = &strchr(p, '>')[1];
+ while (isspace(*p))
+ p++;
+ location_out->longitude = strtod(p, &p);
+ if (errno)
+ goto skip_longitude;
+ while (isspace(*p))
+ p++;
+ if (*p != '<')
+ goto skip_longitude;
+ ret |= LIBGEOME_DATUM_LONGITUDE;
+skip_longitude:
+
+ return ret;
+}
+
+
+uint64_t
+libgeome_util_parse_json(char *s, struct location *location_out)
+{
+ uint64_t ret = 0;
+ char *p = s;
+
+ errno = 0;
+
+ p = strstr(s, "\"lat\"");
+ if (!p)
+ p = strstr(s, "\"latitude\"");
+ if (!p)
+ goto skip_latitude;
+ p = &strchr(&p[1], '"')[1];
+ while (isspace(*p))
+ p++;
+ if (*p++ != ':')
+ goto skip_latitude;
+ while (isspace(*p))
+ p++;
+ location_out->latitude = strtod(p, &p);
+ if (errno)
+ goto skip_latitude;
+ while (isspace(*p))
+ p++;
+ if (*p != ',' && *p != '}')
+ goto skip_latitude;
+ ret |= LIBGEOME_DATUM_LATITUDE;
+skip_latitude:
+
+ p = strstr(s, "\"lon\"");
+ if (!p)
+ p = strstr(s, "\"long\"");
+ if (!p)
+ p = strstr(s, "\"lng\"");
+ if (!p)
+ p = strstr(s, "\"longitude\"");
+ if (!p)
+ goto skip_longitude;
+ p = &strchr(&p[1], '"')[1];
+ while (isspace(*p))
+ p++;
+ if (*p++ != ':')
+ goto skip_longitude;
+ while (isspace(*p))
+ p++;
+ location_out->longitude = strtod(p, &p);
+ if (errno)
+ goto skip_longitude;
+ while (isspace(*p))
+ p++;
+ if (*p != ',' && *p != '}')
+ goto skip_longitude;
+ ret |= LIBGEOME_DATUM_LONGITUDE;
+skip_longitude:
+
+ return ret;
+}
+
+
+uint64_t
+libgeome_util_parse_plain(char *s, struct location *location_out)
+{
+ errno = 0;
+
+ location_out->latitude = strtod(s, &s);
+ if (errno)
+ return 0;
+
+ while (isspace(*s))
+ s++;
+ if (*s == ',')
+ s++;
+ while (isspace(*s))
+ s++;
+
+ location_out->longitude = strtod(s, &s);
+ if (errno)
+ return 0;
+
+ while (isspace(*s))
+ s++;
+
+ if (*s)
+ return 0;
+
+ return LIBGEOME_DATUM_LATITUDE | LIBGEOME_DATUM_LONGITUDE;
+}
+
+
+uint64_t
+libgeome_util_parse_output(char *s, struct location *location_out)
+{
+ if (s[0] == '<')
+ return libgeome_util_parse_xml(s, location_out);
+ else if (s[0] == '{')
+ return libgeome_util_parse_json(s, location_out);
+ else
+ return libgeome_util_parse_plain(s, location_out);
+}