diff options
Diffstat (limited to '')
| -rw-r--r-- | .gitignore | 14 | ||||
| -rw-r--r-- | LICENSE | 15 | ||||
| -rw-r--r-- | Makefile | 77 | ||||
| -rw-r--r-- | README | 93 | ||||
| -rw-r--r-- | TODO | 14 | ||||
| -rw-r--r-- | common.h | 51 | ||||
| -rw-r--r-- | config.mk | 8 | ||||
| -rw-r--r-- | libgeome.h | 273 | ||||
| -rw-r--r-- | libgeome_basic_context.c | 36 | ||||
| -rw-r--r-- | libgeome_get_from_command.c | 100 | ||||
| -rw-r--r-- | libgeome_get_from_file.c | 95 | ||||
| -rw-r--r-- | libgeome_get_from_time.c | 38 | ||||
| -rw-r--r-- | libgeome_get_from_timezone.c | 221 | ||||
| -rw-r--r-- | libgeome_netservices.c | 34 | ||||
| -rw-r--r-- | mk/linux.mk | 6 | ||||
| -rw-r--r-- | mk/macos.mk | 6 | ||||
| -rw-r--r-- | mk/windows.mk | 6 | ||||
| -rw-r--r-- | util.c | 356 | 
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 @@ -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 @@ -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. @@ -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 = : @@ -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); +} | 
