diff options
author | Mattias Andrée <m@maandree.se> | 2025-03-24 22:58:14 +0100 |
---|---|---|
committer | Mattias Andrée <m@maandree.se> | 2025-03-24 22:58:14 +0100 |
commit | 12d7f9b45303fb458cb21a3e0e430af96c781d8b (patch) | |
tree | 2b9c674f474d8ab4d6b35c657d4c005b4089192d | |
download | libgeome-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-- | .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); +} |