src/geolocation.c
Rama master · réplica de lectura del repositorio original.
/**
* ______ ___
* / ____/___ / | _____________ __________
* / / __/ __ \/ /| |/ ___/ ___/ _ \/ ___/ ___/
* / /_/ / /_/ / ___ / /__/ /__/ __(__ |__ )
* \____/\____/_/ |_\___/\___/\___/____/____/
*
* The MIT License (MIT)
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/* Geolocation storage identities and restore compatibility. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "geolocation.h"
#include "error.h"
#include "gkhash.h"
#include "util.h"
/* Country and city labels use the same canonical continent names in both
* GeoIP backends and in legacy persistence detection. */
static const struct {
const char *code;
const char *label;
} geo_continents[] = {
{"AF", "AF Africa"},
{"AN", "AN Antarctica"},
{"AS", "AS Asia"},
{"EU", "EU Europe"},
{"NA", "NA North America"},
{"OC", "OC Oceania"},
{"SA", "SA South America"},
};
/* Restore filtering runs before the raw-data cache borrows these strings. */
typedef struct GGeoRestore_ {
khash_t (is32) * data, *roots;
khash_t (ii32) * keys;
khash_t (imtv) * metrics;
} GGeoRestore;
/* Resolve a continent code to its canonical display label.
*
* On success, the continent label is returned.
* On failure, the unknown-continent label is returned. */
const char *
geo_continent_name (const char *code) {
size_t i = 0;
for (i = 0; i < ARRAY_SIZE (geo_continents); ++i) {
if (strcmp (code, geo_continents[i].code) == 0)
return geo_continents[i].label;
}
return GEO_UNKNOWN_CONTINENT;
}
/* Write a country-qualified city identity into caller-owned scratch space. */
void
geo_city_key (char key[GEO_CITY_KEY_LEN], const char *country, const char *city) {
int len =
snprintf (key, GEO_CITY_KEY_LEN, GEO_CITY_KEY_PREFIX "%zu:%s%s", strlen (country), country,
city);
if (len < 0 || (size_t) len >= GEO_CITY_KEY_LEN)
FATAL ("Geolocation identity exceeds its storage buffer.");
}
/* Find the display name in a city identity belonging to the given country.
*
* On success, a borrowed pointer to the city name is returned.
* On failure, NULL is returned for a country row or an invalid identity. */
const char *
geo_city_name (const char *key, const char *country) {
char prefix[GEO_CITY_KEY_LEN];
size_t len = 0;
if (strncmp (key, GEO_CITY_KEY_PREFIX, sizeof (GEO_CITY_KEY_PREFIX) - 1) != 0)
return NULL;
geo_city_key (prefix, country, "");
len = strlen (prefix);
if (strncmp (key, prefix, len) != 0 || key[len] == '\0')
return NULL;
return key + len;
}
/* Recognize a legacy continent-to-country row when no format marker exists.
*
* On success, non-zero is returned for a country row.
* On failure, 0 is returned for a city row or an unknown hierarchy. */
static int
is_country_row (const char *root, const char *data) {
size_t i = 0;
if (strcmp (root, GEO_UNKNOWN) == 0 || strcmp (root, GEO_UNKNOWN_CONTINENT) == 0)
return strcmp (data, GEO_UNKNOWN) == 0 || get_continent_for_country (data) != NULL;
for (i = 0; i < ARRAY_SIZE (geo_continents); ++i) {
if (strcmp (root, geo_continents[i].label) == 0)
return 1;
}
return 0;
}
/* Remove identities belonging only to discarded legacy leaves. */
static void
prune_geo_keys (GGeoRestore *store) {
khint_t k = 0;
uint32_t id = 0;
for (k = kh_begin (store->keys); k != kh_end (store->keys); ++k) {
if (!kh_exist (store->keys, k))
continue;
id = kh_val (store->keys, k);
/* A leaf ID can also name a parent; keep those dictionary entries. */
if (kh_get (is32, store->data, id) == kh_end (store->data) &&
kh_get (is32, store->roots, id) == kh_end (store->roots))
kh_del (ii32, store->keys, k);
}
}
/* Recompute geolocation metadata after discarding legacy city aggregates. */
static void
rebuild_geo_metadata (uint32_t date, GGeoRestore *store) {
khash_t (su64) * metadata = get_hash (GEO_LOCATION, date, MTRC_METADATA);
GKMetricVals *metrics = NULL;
khint_t k = 0, km = 0;
del_su64_free (metadata, 1);
for (k = kh_begin (store->data); k != kh_end (store->data); ++k) {
if (!kh_exist (store->data, k))
continue;
km = kh_get (imtv, store->metrics, kh_key (store->data, k));
if (km == kh_end (store->metrics))
continue;
metrics = &kh_val (store->metrics, km);
ht_insert_meta_data (GEO_LOCATION, date, "hits", metrics->hits);
ht_insert_meta_data (GEO_LOCATION, date, "visitors", metrics->visitors);
ht_insert_meta_data (GEO_LOCATION, date, "bytes", metrics->bw);
ht_insert_meta_data (GEO_LOCATION, date, "cumts", metrics->cumts);
/* Like ingestion, maxts metadata totals request times; the cache computes
* the actual maximum from the surviving per-row maxima. */
ht_insert_meta_data (GEO_LOCATION, date, "maxts", metrics->cumts);
}
}
/* Discard legacy city rows while retaining country rows and other panels.
*
* On success, non-zero is returned if any rows were discarded.
* On failure or when there are no unsafe rows, 0 is returned. */
static int
discard_geo_date (uint32_t date) {
GGeoRestore store = {
.data = get_hash (GEO_LOCATION, date, MTRC_DATAMAP),
.roots = get_hash (GEO_LOCATION, date, MTRC_ROOTMAP),
.keys = get_hash (GEO_LOCATION, date, MTRC_KEYMAP),
.metrics = get_hash (GEO_LOCATION, date, MTRC_METRICS),
};
khint_t k = 0, kr = 0, km = 0;
const char *label = NULL, *root = NULL;
int discarded = 0;
if (!store.data || !store.roots || !store.keys || !store.metrics)
return 0;
for (k = kh_begin (store.data); k != kh_end (store.data); ++k) {
if (!kh_exist (store.data, k))
continue;
km = kh_get (imtv, store.metrics, kh_key (store.data, k));
if (km == kh_end (store.metrics))
continue;
kr = kh_get (is32, store.roots, kh_val (store.metrics, km).root);
label = kh_val (store.data, k);
root = kr != kh_end (store.roots) ? kh_val (store.roots, kr) : NULL;
if (root && (geo_city_name (label, root) || is_country_row (root, label)))
continue;
free (kh_val (store.data, k));
kh_del (is32, store.data, k);
kh_del (imtv, store.metrics, km);
discarded = 1;
}
if (discarded) {
prune_geo_keys (&store);
rebuild_geo_metadata (date, &store);
}
return discarded;
}
/* Drop ambiguous legacy city aggregates and report the missing GEO history. */
void
discard_legacy_geo_cities (void) {
GKDB *db = get_db_instance (DB_INSTANCE);
khash_t (igkh) * dates = get_hdb (db, MTRC_DATES);
khint_t k = 0;
int discarded = 0;
if (!dates)
return;
for (k = kh_begin (dates); k != kh_end (dates); ++k) {
if (kh_exist (dates, k))
discarded |= discard_geo_date (kh_key (dates, k));
}
if (discarded)
fprintf (stderr,
"Warning: Skipped legacy GEO city rows that may contain traffic merged across countries. "
"Other report history is preserved. Reprocess the original logs in an empty --db-path "
"for complete geolocation history.\n");
}
/* Check retained storage for rows before deciding whether to show a panel.
*
* On success, non-zero is returned when the module contains data rows.
* On failure or when the module has no rows, 0 is returned. */
static int
has_geo_rows (GModule module) {
GKDB *db = get_db_instance (DB_INSTANCE);
khash_t (igkh) * dates = get_hdb (db, MTRC_DATES);
khash_t (is32) * data = NULL;
khint_t k = 0;
if (!dates)
return 0;
for (k = kh_begin (dates); k != kh_end (dates); ++k) {
if (!kh_exist (dates, k))
continue;
data = get_hash (module, kh_key (dates, k), MTRC_DATAMAP);
if (data && kh_size (data))
return 1;
}
return 0;
}
/* Hide empty restored GEO/ASN panels that cannot receive new lookup data. */
void
verify_restored_geo_panels (void) {
if (!conf.has_geocountry && !conf.has_geocity && !has_geo_rows (GEO_LOCATION))
remove_module (GEO_LOCATION);
if (!conf.has_geoasn && !has_geo_rows (ASN))
remove_module (ASN);
}