From 12d7f9b45303fb458cb21a3e0e430af96c781d8b Mon Sep 17 00:00:00 2001 From: Mattias Andrée Date: Mon, 24 Mar 2025 22:58:14 +0100 Subject: First commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Andrée --- .gitignore | 14 ++ LICENSE | 15 ++ Makefile | 77 ++++++++++ README | 93 +++++++++++ TODO | 14 ++ common.h | 51 +++++++ config.mk | 8 + libgeome.h | 273 +++++++++++++++++++++++++++++++++ libgeome_basic_context.c | 36 +++++ libgeome_get_from_command.c | 100 ++++++++++++ libgeome_get_from_file.c | 95 ++++++++++++ libgeome_get_from_time.c | 38 +++++ libgeome_get_from_timezone.c | 221 +++++++++++++++++++++++++++ libgeome_netservices.c | 34 +++++ mk/linux.mk | 6 + mk/macos.mk | 6 + mk/windows.mk | 6 + util.c | 356 +++++++++++++++++++++++++++++++++++++++++++ 18 files changed, 1443 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README create mode 100644 TODO create mode 100644 common.h create mode 100644 config.mk create mode 100644 libgeome.h create mode 100644 libgeome_basic_context.c create mode 100644 libgeome_get_from_command.c create mode 100644 libgeome_get_from_file.c create mode 100644 libgeome_get_from_time.c create mode 100644 libgeome_get_from_timezone.c create mode 100644 libgeome_netservices.c create mode 100644 mk/linux.mk create mode 100644 mk/macos.mk create mode 100644 mk/windows.mk create mode 100644 util.c 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 + +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 + + #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", , . + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#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 +#include + + +#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 : %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 : %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 : %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 : %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 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, ""); + if (!p) + p = strstr(s, ""); + 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, ""); + if (!p) + p = strstr(s, ""); + if (!p) + p = strstr(s, ""); + if (!p) + p = strstr(s, ""); + 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); +} -- cgit v1.2.3-70-g09d2