#define _GNU_SOURCE #include "util.h" #include #include #include #include #include #include #include #include #include void die(const char *fmt, ...) { va_list ap; va_start(ap, fmt); fputs("austen: ", stderr); vfprintf(stderr, fmt, ap); fputc('\n', stderr); va_end(ap); exit(1); } void *xmalloc(size_t n, size_t size) { if (size && n > SIZE_MAX / size) die("allocation trop grande"); size_t total = n * size; void *p = malloc(total ? total : 1); if (!p) die("mémoire épuisée"); return p; } void *xcalloc(size_t n, size_t size) { void *p = calloc(n ? n : 1, size ? size : 1); if (!p) die("mémoire épuisée"); return p; } void *xrealloc(void *p, size_t n, size_t size) { if (size && n > SIZE_MAX / size) die("allocation trop grande"); size_t total = n * size; p = realloc(p, total ? total : 1); if (!p) die("mémoire épuisée"); return p; } char *xstrdup(const char *s) { size_t n = strlen(s) + 1; char *d = xmalloc(n, 1); memcpy(d, s, n); return d; } uint8_t *read_file(const char *path, size_t max, size_t *len) { int fd = open(path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); if (fd < 0) die("ouverture impossible : %s (%s)", path, strerror(errno)); struct stat st; if (fstat(fd, &st) != 0 || !S_ISREG(st.st_mode)) die("pas un fichier ordinaire : %s", path); if ((uintmax_t)st.st_size > max) die("fichier trop gros : %s", path); size_t n = (size_t)st.st_size; uint8_t *buf = xmalloc(n + 1, 1); size_t got = 0; while (got < n) { ssize_t r = read(fd, buf + got, n - got); if (r < 0 && errno == EINTR) continue; if (r <= 0) die("lecture incomplète : %s", path); got += (size_t)r; } buf[n] = 0; close(fd); *len = n; return buf; } void write_file_atomic(const char *path, const void *data, size_t len) { size_t pl = strlen(path); char *tmp = xmalloc(pl + 5, 1); memcpy(tmp, path, pl); memcpy(tmp + pl, ".new", 5); int fd = open(tmp, O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW | O_CLOEXEC, 0644); if (fd < 0) die("écriture impossible : %s (%s)", tmp, strerror(errno)); const uint8_t *p = data; size_t done = 0; while (done < len) { ssize_t w = write(fd, p + done, len - done); if (w < 0 && errno == EINTR) continue; if (w <= 0) die("écriture incomplète : %s", tmp); done += (size_t)w; } if (fchmod(fd, 0644) != 0 || close(fd) != 0) die("fermeture impossible : %s", tmp); if (rename(tmp, path) != 0) die("renommage impossible : %s", path); free(tmp); } size_t grid_points(const Grid *g) { if (g->nx <= 0 || g->ny <= 0 || (size_t)g->nx * (size_t)g->ny > MAX_POINTS) die("grille invalide %dx%d", g->nx, g->ny); return (size_t)g->nx * (size_t)g->ny; } /* ------------------------------------------------------------------ .grd */ typedef struct { char magic[4]; uint32_t version, nx, ny, nt_msl, nt_gust; double lat0, dlat, lon0, dlon; } GrdHeader; Grd *grd_read(const char *path) { size_t len; uint8_t *buf = read_file(path, (size_t)8 << 30, &len); if (len < sizeof(GrdHeader)) die("%s : en-tête tronqué", path); GrdHeader h; memcpy(&h, buf, sizeof h); if (memcmp(h.magic, "AUGR", 4) != 0 || h.version != 1) die("%s : format inconnu", path); if (h.nt_msl > MAX_TIMES || h.nt_gust > MAX_TIMES) die("%s : trop d'échéances", path); Grd *d = xcalloc(1, sizeof *d); d->g = (Grid){(int)h.nx, (int)h.ny, h.lat0, h.dlat, h.lon0, h.dlon}; size_t np = grid_points(&d->g); d->nt_msl = (int)h.nt_msl; d->nt_gust = (int)h.nt_gust; size_t need = sizeof h + (h.nt_msl + h.nt_gust) * sizeof(int64_t) + (h.nt_msl + h.nt_gust) * np * sizeof(float); if (len != need) die("%s : taille incohérente", path); const uint8_t *p = buf + sizeof h; d->t_msl = xmalloc(h.nt_msl, sizeof(int64_t)); d->t_gust = xmalloc(h.nt_gust, sizeof(int64_t)); memcpy(d->t_msl, p, h.nt_msl * sizeof(int64_t)); p += h.nt_msl * sizeof(int64_t); memcpy(d->t_gust, p, h.nt_gust * sizeof(int64_t)); p += h.nt_gust * sizeof(int64_t); d->msl = xmalloc((size_t)h.nt_msl * np, sizeof(float)); d->gust = xmalloc((size_t)h.nt_gust * np, sizeof(float)); memcpy(d->msl, p, (size_t)h.nt_msl * np * sizeof(float)); p += (size_t)h.nt_msl * np * sizeof(float); memcpy(d->gust, p, (size_t)h.nt_gust * np * sizeof(float)); free(buf); return d; } void grd_write(const char *path, const Grd *d) { size_t np = grid_points(&d->g); GrdHeader h = {{'A', 'U', 'G', 'R'}, 1, (uint32_t)d->g.nx, (uint32_t)d->g.ny, (uint32_t)d->nt_msl, (uint32_t)d->nt_gust, d->g.lat0, d->g.dlat, d->g.lon0, d->g.dlon}; size_t len = sizeof h + (size_t)(d->nt_msl + d->nt_gust) * (sizeof(int64_t) + np * sizeof(float)); uint8_t *buf = xmalloc(len, 1), *p = buf; memcpy(p, &h, sizeof h); p += sizeof h; memcpy(p, d->t_msl, (size_t)d->nt_msl * sizeof(int64_t)); p += (size_t)d->nt_msl * sizeof(int64_t); memcpy(p, d->t_gust, (size_t)d->nt_gust * sizeof(int64_t)); p += (size_t)d->nt_gust * sizeof(int64_t); memcpy(p, d->msl, (size_t)d->nt_msl * np * sizeof(float)); p += (size_t)d->nt_msl * np * sizeof(float); memcpy(p, d->gust, (size_t)d->nt_gust * np * sizeof(float)); write_file_atomic(path, buf, len); free(buf); } void grd_free(Grd *d) { if (!d) return; free(d->t_msl); free(d->t_gust); free(d->msl); free(d->gust); free(d); } /* ------------------------------------------------------------------ dates */ int64_t epoch_utc(int y, int mo, int d, int h, int mi, int s) { /* jours depuis 1970 (algorithme de Howard Hinnant), sans dépendre du fuseau du système */ y -= mo <= 2; int64_t era = (y >= 0 ? y : y - 399) / 400; int64_t yoe = y - era * 400; int64_t doy = (153 * (mo + (mo > 2 ? -3 : 9)) + 2) / 5 + d - 1; int64_t doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; int64_t days = era * 146097 + doe - 719468; return days * 86400 + h * 3600 + mi * 60 + s; } int64_t parse_iso(const char *s) { int y, mo, d, h, mi; char z; if (sscanf(s, "%4d-%2d-%2dT%2d:%2d%c", &y, &mo, &d, &h, &mi, &z) != 6 || z != 'Z') return -1; if (mo < 1 || mo > 12 || d < 1 || d > 31 || h > 23 || mi > 59) return -1; return epoch_utc(y, mo, d, h, mi, 0); } void fmt_iso(int64_t t, char out[20]) { time_t tt = (time_t)t; struct tm tm; gmtime_r(&tt, &tm); snprintf(out, 20, "%04d-%02d-%02dT%02d:%02dZ", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min); } char *next_line(char **cur) { char *s = *cur; while (*s == '\n') s++; if (!*s) { *cur = s; return NULL; } char *e = strchr(s, '\n'); if (e) { *e = 0; *cur = e + 1; } else { *cur = s + strlen(s); } return s; } double now_epoch(void) { struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); return (double)ts.tv_sec + (double)ts.tv_nsec * 1e-9; } int cmd_catsafe(int argc, char **argv) { /* « austen catsafe » : copie le fichier sur la sortie standard sans suivre * de lien (O_NOFOLLOW), sans se bloquer sur une FIFO (O_NONBLOCK) et seulement s'il est ordinaire. * Sert à la partie PHP, qui ne sait pas ouvrir un fichier avec ces drapeaux. */ if (argc != 2) die("usage : austen catsafe "); char *end; unsigned long long max = strtoull(argv[1], &end, 10); if (*end || max == 0 || max > (1ull << 30)) die("taille maxi invalide"); int fd = open(argv[0], O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_CLOEXEC); if (fd < 0) die("ouverture refusée : %s", argv[0]); struct stat st; if (fstat(fd, &st) != 0 || !S_ISREG(st.st_mode) || (unsigned long long)st.st_size > max) die("fichier refusé : %s", argv[0]); char buf[65536]; unsigned long long total = 0; for (;;) { ssize_t r = read(fd, buf, sizeof buf); if (r < 0 && errno == EINTR) continue; if (r < 0) die("lecture impossible : %s", argv[0]); if (r == 0) break; total += (unsigned long long)r; if (total > max) die("fichier trop gros : %s", argv[0]); if (fwrite(buf, 1, (size_t)r, stdout) != (size_t)r) die("écriture impossible"); } close(fd); return fflush(stdout) ? 1 : 0; }