Companion code · Chapter 9

Files, Bits, and Function Pointers: ADTs Behind a Header

24 files. About this chapter.

Download ch09.zip

ch09/csvcount.c40 lines
#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "usage: %s file.csv\n", argv[0]);
        return 2;
    }
    FILE *f = fopen(argv[1], "r");
    if (f == NULL) {
        perror(argv[1]);
        return 1;
    }
    char line[128];
    size_t lineno = 0, good = 0;
    while (fgets(line, sizeof line, f) != NULL) {
        lineno++;
        if (strchr(line, '\n') == NULL && !feof(f)) {
            fprintf(stderr, "line %zu: too long\n", lineno);
            fclose(f);
            return 1;
        }
        unsigned id, flags; long long t; double v;
        int n = sscanf(line, "%u,%lld,%lf,%u",
                       &id, &t, &v, &flags);
        if (n != 4) {
            fprintf(stderr, "line %zu: bad: %s", lineno, line);
            continue;
        }
        good++;
    }
    int failed = ferror(f);     /* end of file, or a failure? */
    fclose(f);
    if (failed) {
        fprintf(stderr, "%s: read error\n", argv[1]);
        return 1;
    }
    printf("%zu lines, %zu good records\n", lineno, good);
    return 0;
}
ch09/day.csv5 lines
1001,60,21.75,3
1002,60,64.5,3
1003,60,1013.25,1
1004,60,3.5
1001,120,21.5,3
ch09/db_demo.c40 lines
#include <stdio.h>
#include "sensor_db.h"
#include "stn_log.h"

struct frost_ctx { long minutes; double coldest; };

static void count_frost(const struct reading *r, void *ctx) {
    struct frost_ctx *fc = ctx;         /* we know what we passed */
    if (r->sensor_id % 1000 != 1) return;      /* temperature */
    if (r->value < 2.0) fc->minutes++;
    if (r->value < fc->coldest) fc->coldest = r->value;
}

int main(int argc, char *argv[]) {
    if (argc != 2) return 2;
    FILE *f = fopen(argv[1], "rb");
    if (f == NULL) { perror(argv[1]); return 1; }
    struct stn_db *db = stn_db_create(argv[1], 16);
    uint32_t n = 0;
    enum stn_log_status st = db ? stn_log_read_header(f, &n)
                                : STN_LOG_IO_ERROR;
    struct reading r;
    for (uint32_t i = 0; st == STN_LOG_OK && i < n; i++) {
        st = stn_log_read_record(f, &r);
        if (st == STN_LOG_OK && stn_db_add(db, &r) != 0)
            st = STN_LOG_IO_ERROR;
    }
    fclose(f);
    if (st != STN_LOG_OK) {
        fprintf(stderr, "%s: %s\n", argv[1], stn_log_status_name(st));
        stn_db_destroy(db);
        return 1;
    }
    struct frost_ctx fc = { 0, 1e9 };
    stn_db_for_each(db, count_frost, &fc);
    printf("%zu readings; frost minutes %ld; coldest %.2f C\n",
           stn_db_count(db), fc.minutes, fc.coldest);
    stn_db_destroy(db);
    return 0;
}
ch09/endian.c21 lines
#include <stdint.h>
#include <stdio.h>

static void show(const char *label, const void *p, size_t n) {
    const unsigned char *b = p;
    printf("%-10s", label);
    for (size_t i = 0; i < n; i++) {
        printf(" %02x", (unsigned)b[i]);
    }
    putchar('\n');
}

int main(void) {
    uint32_t count = 1440;          /* 0x000005a0 */
    int64_t t = -60;
    double v = 21.75;
    show("count", &count, sizeof count);
    show("time_s", &t, sizeof t);
    show("value", &v, sizeof v);
    return 0;
}
ch09/escape.c18 lines
#include <stdio.h>
#include "sensor_db.h"

const struct reading *stn_db_items(const struct stn_db *db);

int main(void) {
    struct stn_db *db = stn_db_create("escape", 16);
    if (db == NULL) return 1;
    struct reading r = { 1001, 60, 21.75, STN_FLAG_VALID };
    for (int i = 0; i < 16; i++) {
        if (stn_db_add(db, &r) != 0) return 1;
    }
    const struct reading *items = stn_db_items(db);
    if (stn_db_add(db, &r) != 0) return 1;   /* 17th: grows */
    printf("first value %.2f\n", items[0].value);
    stn_db_destroy(db);
    return 0;
}
ch09/flags.c26 lines
#include <stdint.h>
#include <stdio.h>
#include "station.h"

static void print_bits(const char *label, uint8_t x) {
    printf("%-14s 0x%02x  ", label, (unsigned)x);
    for (int bit = 7; bit >= 0; bit--) {
        putchar((x >> bit) & 1u ? '1' : '0');
    }
    putchar('\n');
}

int main(void) {
    uint8_t flags = 0;
    flags |= STN_FLAG_VALID | STN_FLAG_CALIBRATED;    /* set */
    print_bits("set V,C", flags);
    flags |= STN_FLAG_LOW_BATTERY;                    /* set */
    print_bits("set LOW_BAT", flags);
    flags &= (uint8_t)~STN_FLAG_CALIBRATED;          /* clear */
    print_bits("clear CAL", flags);
    flags ^= STN_FLAG_VALID;                        /* toggle */
    print_bits("toggle VALID", flags);
    int low = (flags & STN_FLAG_LOW_BATTERY) != 0;    /* test */
    printf("low battery? %d\n", low);
    return 0;
}
ch09/fp.c24 lines
#include <stdio.h>

static double c_to_f(double c) { return c * 9.0 / 5.0 + 32.0; }
static double c_to_k(double c) { return c + 273.15; }

/* apply calls whatever conversion it is handed: a callback. */
static void apply(double (*conv)(double),
                  const double *xs, size_t n) {
    for (size_t i = 0; i < n; i++) {
        printf(" %.2f", conv(xs[i]));
    }
    putchar('\n');
}

int main(void) {
    double (*fp)(double) = c_to_f;       /* no & needed */
    printf("fp(21.75) = %.2f\n", fp(21.75));
    printf("fp == c_to_f? %d\n", fp == c_to_f);
    typedef double (*converter)(double);
    converter table[2] = { c_to_f, c_to_k };
    const double temps[3] = { -5.0, 0.0, 21.75 };
    for (size_t i = 0; i < 2; i++) apply(table[i], temps, 3);
    return 0;
}
ch09/mklog.c34 lines
#include <stdio.h>
#include <stdlib.h>
#include "stn_log.h"

/* mklog FILE MINUTES: one reading per minute from each of
   the four sensors 1001..1004. */
int main(int argc, char *argv[]) {
    if (argc != 3) {
        fprintf(stderr, "usage: %s file minutes\n", argv[0]);
        return 2;
    }
    long minutes = strtol(argv[2], NULL, 10);
    if (minutes < 1 || minutes > 100000) {
        fprintf(stderr, "minutes must be 1..100000\n");
        return 2;
    }
    FILE *f = fopen(argv[1], "wb");
    if (f == NULL) { perror(argv[1]); return 1; }
    static const double base[4] = {3.0, 91.0, 1013.25, 3.5};
    uint32_t n = (uint32_t)(4 * minutes);
    int ok = stn_log_write_header(f, n) == STN_LOG_OK;
    for (long m = 1; ok && m <= minutes; m++) {
        for (uint32_t k = 0; ok && k < 4; k++) {
            double v = base[k] - 0.25 * (double)(m % 8);
            struct reading r = { 1001 + k, 60 * m, v,
                STN_FLAG_VALID | STN_FLAG_CALIBRATED };
            ok = stn_log_write_record(f, &r) == STN_LOG_OK;
        }
    }
    if (!ok) { perror("write"); fclose(f); return 1; }
    if (fclose(f) != 0) { perror("close"); return 1; }
    printf("wrote %u records to %s\n", (unsigned)n, argv[1]);
    return 0;
}
ch09/pack.c32 lines
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include "station.h"

/* Pack four booleans into the station's flag byte. */
uint8_t stn_pack_flags(bool valid, bool calibrated,
                       bool low_battery, bool out_of_range) {
    uint8_t f = 0;
    if (valid)        f |= STN_FLAG_VALID;
    if (calibrated)   f |= STN_FLAG_CALIBRATED;
    if (low_battery)  f |= STN_FLAG_LOW_BATTERY;
    if (out_of_range) f |= STN_FLAG_OUT_OF_RANGE;
    return f;
}

/* Raw ADC word: bits 15..12 channel, bits 11..0 sample. */
uint16_t adc_pack(unsigned channel, unsigned sample) {
    unsigned w = ((channel & 0xFu) << 12) | (sample & 0xFFFu);
    return (uint16_t)w;
}
unsigned adc_channel(uint16_t w) { return (w >> 12) & 0xFu; }
unsigned adc_sample(uint16_t w)  { return w & 0xFFFu; }

int main(void) {
    printf("flags = 0x%02x\n",
           (unsigned)stn_pack_flags(true, false, true, false));
    uint16_t w = adc_pack(3, 2748);
    printf("word = 0x%04x channel = %u sample = %u\n",
           (unsigned)w, adc_channel(w), adc_sample(w));
    return 0;
}
ch09/poke.c10 lines
#include <stdio.h>
#include "sensor_db.h"

int main(void) {
    struct stn_db *db = stn_db_create("north-field", 8);
    printf("%zu\n", db->count);      /* reach behind the wall */
    struct stn_db copy;              /* make one on the stack */
    stn_db_destroy(db);
    return 0;
}
ch09/predict.c16 lines
#include <stdint.h>
#include <stdio.h>
#include "station.h"

int main(void) {
    uint8_t f = 0x0B;   /* VALID | CALIBRATED | OUT_OF_RANGE */
    printf("a) %#04x\n", (unsigned)(f & STN_FLAG_CALIBRATED));
    printf("b) %#04x\n", (unsigned)(f | STN_FLAG_LOW_BATTERY));
    printf("c) %#04x\n", (unsigned)(f ^ 0x0Fu));
    printf("d) %#04x\n", (unsigned)(f >> 1));
    printf("e) %d\n", f << 5);
    printf("f) %d\n", (uint8_t)(f << 5));
    printf("g) %d\n", ~f);
    printf("h) %d\n", (f & STN_FLAG_LOW_BATTERY) != 0);
    return 0;
}
ch09/reader_bad.c42 lines
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "station.h"

static void show(const struct reading *r) {
    printf("%u t=%lld v=%.2f\n", (unsigned)r->sensor_id,
           (long long)r->time_s, r->value);
}
static void (*const handlers[4])(const struct reading *) = {
    show, show, show, show };

static uint32_t u32(const unsigned char *b) {
    return (uint32_t)b[0] | (uint32_t)b[1] << 8 |
           (uint32_t)b[2] << 16 | (uint32_t)b[3] << 24;
}
static uint64_t u64(const unsigned char *b) {
    return u32(b) | (uint64_t)u32(b + 4) << 32;
}

int main(int argc, char *argv[]) {
    if (argc < 2) return 1;
    FILE *f = fopen(argv[1], "rb");
    unsigned char hdr[8];
    fread(hdr, 1, 8, f);
    uint32_t count = u32(hdr + 4);
    unsigned char *buf = malloc((size_t)count * 21);
    fread(buf, 21, count, f);
    for (uint32_t i = 0; i < count; i++) {
        const unsigned char *b = buf + (size_t)i * 21;
        uint64_t vb = u64(b + 12);
        double v;
        memcpy(&v, &vb, sizeof v);
        struct reading r = { u32(b), (int64_t)u64(b + 4),
                             v, b[20] };
        handlers[r.sensor_id % 1000 - 1](&r);
    }
    free(buf);
    fclose(f);
    return 0;
}
ch09/sensor_db.c87 lines
/* sensor_db.c (Chapter 9): Chapter 7's file with growth by doubling
 * and stn_db_for_each. Still the only file that knows the fields. */
#include <stdlib.h>
#include <string.h>
#include "sensor_db.h"
struct stn_db {
    char *name;            /* owned: heap copy of the name */
    struct reading *rows;  /* owned: capacity slots */
    size_t count;          /* slots in use */
    size_t capacity;
};

struct stn_db *stn_db_create(const char *name, size_t capacity) {
    if (capacity == 0) {
        return NULL;
    }
    struct stn_db *db = malloc(sizeof *db);
    if (db == NULL) {
        return NULL;
    }
    db->name = malloc(strlen(name) + 1);
    db->rows = calloc(capacity, sizeof *db->rows);
    if (db->name == NULL || db->rows == NULL) {
        free(db->name);      /* free(NULL) is allowed and does nothing */
        free(db->rows);
        free(db);
        return NULL;
    }
    strcpy(db->name, name);
    db->count = 0;
    db->capacity = capacity;
    return db;
}

void stn_db_destroy(struct stn_db *db) {
    if (db != NULL) {
        free(db->rows);
        free(db->name);
        free(db);
    }
}

int stn_db_add(struct stn_db *db, const struct reading *r) {
    if (db->count == db->capacity) {        /* full: double */
        size_t cap = 2 * db->capacity;
        struct reading *p = realloc(db->rows, cap * sizeof *p);
        if (p == NULL) return -1;       /* db->rows untouched */
        db->rows = p;
        db->capacity = cap;
    }
    db->rows[db->count++] = *r;       /* struct assignment */
    return 0;
}

size_t stn_db_count(const struct stn_db *db) {
    return db->count;
}

const struct reading *stn_db_get(const struct stn_db *db, size_t i) {
    return i < db->count ? &db->rows[i] : NULL;
}

const char *stn_db_name(const struct stn_db *db) {
    return db->name;
}

double stn_db_mean(const struct stn_db *db, uint32_t sensor_id,
                   size_t *n_out) {
    double sum = 0.0;
    size_t n = 0;
    for (size_t i = 0; i < db->count; i++) {
        const struct reading *r = &db->rows[i];
        if (r->sensor_id == sensor_id && (r->flags & STN_FLAG_VALID)) {
            sum += r->value;
            n++;
        }
    }
    *n_out = n;
    return n > 0 ? sum / (double)n : 0.0;
}

void stn_db_for_each(const struct stn_db *db,
                     stn_db_visit visit, void *ctx) {
    for (size_t i = 0; i < db->count; i++) {
        visit(&db->rows[i], ctx);
    }
}
ch09/sensor_db.h51 lines
/* sensor_db.h (Chapter 9): Chapter 7's interface, now growable.
 * Same opaque type and functions; one new operation. */
#ifndef SENSOR_DB_H
#define SENSOR_DB_H

#include <stddef.h>
#include "station.h"

struct stn_db;   /* declared here; defined in sensor_db.c */

/* OWNERSHIP RULES (the type system cannot say these; we must)
 * 1. stn_db_create returns a new db, or NULL if out of
 *    memory or capacity is 0. The caller owns it and calls
 *    stn_db_destroy exactly once. capacity is only a
 *    starting size: the db grows.
 * 2. The db stores its own copy of name. The caller still
 *    owns the string it passed and may change or free it
 *    afterward.
 * 3. stn_db_add copies *r into the db and keeps no pointer
 *    to r.
 * 4. stn_db_get and stn_db_name return BORROWED pointers
 *    into the db. Never free them. They dangle after
 *    stn_db_destroy. A pointer from stn_db_get also dangles
 *    after the next stn_db_add, because growth may move the
 *    rows.
 * 5. stn_db_for_each passes visit a BORROWED pointer, valid
 *    only during that call.
 */
struct stn_db *stn_db_create(const char *name, size_t capacity);
void stn_db_destroy(struct stn_db *db);      /* NULL: no-op */

/* 0 on success, -1 if out of memory (db unchanged). */
int stn_db_add(struct stn_db *db, const struct reading *r);
size_t stn_db_count(const struct stn_db *db);
const struct reading *stn_db_get(const struct stn_db *db,
                                 size_t i);
                                  /* NULL if i >= count */
const char *stn_db_name(const struct stn_db *db);

/* Mean of VALID readings from sensor_id; stores how many
 * in *n_out. Returns 0.0 when *n_out is 0. */
double stn_db_mean(const struct stn_db *db, uint32_t sensor_id,
                   size_t *n_out);

/* Calls visit(r, ctx) once per reading, in insertion order. */
typedef void (*stn_db_visit)(const struct reading *r,
                             void *ctx);
void stn_db_for_each(const struct stn_db *db,
                     stn_db_visit visit, void *ctx);

#endif /* SENSOR_DB_H */
ch09/sensor_db_leaky.c92 lines
/* sensor_db.c (Chapter 9): Chapter 7's file with growth by doubling
 * and stn_db_for_each. Still the only file that knows the fields. */
#include <stdlib.h>
#include <string.h>
#include "sensor_db.h"
struct stn_db {
    char *name;            /* owned: heap copy of the name */
    struct reading *rows;  /* owned: capacity slots */
    size_t count;          /* slots in use */
    size_t capacity;
};

struct stn_db *stn_db_create(const char *name, size_t capacity) {
    if (capacity == 0) {
        return NULL;
    }
    struct stn_db *db = malloc(sizeof *db);
    if (db == NULL) {
        return NULL;
    }
    db->name = malloc(strlen(name) + 1);
    db->rows = calloc(capacity, sizeof *db->rows);
    if (db->name == NULL || db->rows == NULL) {
        free(db->name);      /* free(NULL) is allowed and does nothing */
        free(db->rows);
        free(db);
        return NULL;
    }
    strcpy(db->name, name);
    db->count = 0;
    db->capacity = capacity;
    return db;
}

void stn_db_destroy(struct stn_db *db) {
    if (db != NULL) {
        free(db->rows);
        free(db->name);
        free(db);
    }
}

int stn_db_add(struct stn_db *db, const struct reading *r) {
    if (db->count == db->capacity) {        /* full: double */
        size_t cap = 2 * db->capacity;
        struct reading *p = realloc(db->rows, cap * sizeof *p);
        if (p == NULL) return -1;       /* db->rows untouched */
        db->rows = p;
        db->capacity = cap;
    }
    db->rows[db->count++] = *r;       /* struct assignment */
    return 0;
}

size_t stn_db_count(const struct stn_db *db) {
    return db->count;
}

const struct reading *stn_db_get(const struct stn_db *db, size_t i) {
    return i < db->count ? &db->rows[i] : NULL;
}

const char *stn_db_name(const struct stn_db *db) {
    return db->name;
}

double stn_db_mean(const struct stn_db *db, uint32_t sensor_id,
                   size_t *n_out) {
    double sum = 0.0;
    size_t n = 0;
    for (size_t i = 0; i < db->count; i++) {
        const struct reading *r = &db->rows[i];
        if (r->sensor_id == sensor_id && (r->flags & STN_FLAG_VALID)) {
            sum += r->value;
            n++;
        }
    }
    *n_out = n;
    return n > 0 ? sum / (double)n : 0.0;
}

void stn_db_for_each(const struct stn_db *db,
                     stn_db_visit visit, void *ctx) {
    for (size_t i = 0; i < db->count; i++) {
        visit(&db->rows[i], ctx);
    }
}

/* Agent-added accessor (illustrative): hands out the array. */
const struct reading *stn_db_items(const struct stn_db *db) {
    return db->rows;
}
ch09/shift2.c7 lines
#include <stdio.h>
static unsigned bit(unsigned n) { return 1u << n; }
int main(void) {
    volatile unsigned k = 32;
    printf("%u %u\n", bit(32), bit(k));
    return 0;
}
ch09/shiftbug.c22 lines
#include <inttypes.h>
#include <stdio.h>

/* Agent-written decoder (illustrative). */
static uint64_t get_u64le_agent(const unsigned char *b) {
    uint64_t x = 0;
    for (int i = 0; i < 8; i++) {
        x |= b[i] << (8 * i);
    }
    return x;
}

int main(void) {
    /* 86400 s (one day) and 5000000000 s (about 158 years) */
    const unsigned char day[8] = {0x80, 0x51, 0x01, 0,
                                  0, 0, 0, 0};
    const unsigned char big[8] = {0x00, 0xf2, 0x05, 0x2a,
                                  0x01, 0, 0, 0};
    printf("day: %" PRIu64 "\n", get_u64le_agent(day));
    printf("big: %" PRIu64 "\n", get_u64le_agent(big));
    return 0;
}
ch09/shortread.c24 lines
#include <stdint.h>
#include <stdio.h>

int main(void) {
    FILE *f = fopen("ten.bin", "wb");
    if (f == NULL) { perror("ten.bin"); return 1; }
    const unsigned char bytes[10] = {1, 2, 3, 4, 5,
                                     6, 7, 8, 9, 10};
    if (fwrite(bytes, 1, sizeof bytes, f) != sizeof bytes) {
        perror("write"); fclose(f); return 1;
    }
    if (fclose(f) != 0) { perror("close"); return 1; }

    f = fopen("ten.bin", "rb");          /* holds bytes 1..10 */
    if (f == NULL) { perror("ten.bin"); return 1; }
    uint32_t words[4] = {0};
    size_t got = fread(words, sizeof words[0], 4, f);
    printf("asked for 4 items, got %zu\n", got);
    printf("feof=%d ferror=%d\n", feof(f) != 0, ferror(f) != 0);
    printf("words[2]=0x%08x words[3]=0x%08x\n",
           (unsigned)words[2], (unsigned)words[3]);
    fclose(f);
    return 0;
}
ch09/sort_time.c40 lines
#include <stdio.h>
#include <stdlib.h>
#include "station.h"

/* qsort comparator: negative, zero or positive, like strcmp. */
static int by_time(const void *pa, const void *pb) {
    const struct reading *a = pa;
    const struct reading *b = pb;
    return (a->time_s > b->time_s) - (a->time_s < b->time_s);
}

static int by_id_then_time(const void *pa, const void *pb) {
    const struct reading *a = pa;
    const struct reading *b = pb;
    if (a->sensor_id != b->sensor_id) {
        return (a->sensor_id > b->sensor_id) ? 1 : -1;
    }
    return by_time(pa, pb);
}

static void print_all(const struct reading *r, size_t n) {
    for (size_t i = 0; i < n; i++) {
        printf("  %u %lld %.2f\n", (unsigned)r[i].sensor_id,
               (long long)r[i].time_s, r[i].value);
    }
}

int main(void) {
    struct reading rs[4] = {
        {1002, 120, 64.0, 3}, {1001, 60, 21.75, 3},
        {1001, 120, 21.5, 3}, {1002, 60, 64.5, 3},
    };
    qsort(rs, 4, sizeof rs[0], by_time);
    puts("by time:");
    print_all(rs, 4);
    qsort(rs, 4, sizeof rs[0], by_id_then_time);
    puts("by id, then time:");
    print_all(rs, 4);
    return 0;
}
ch09/station.h29 lines
/* station.h: types shared by every station module.
 * Types and constants only: no variables, no functions. */
#ifndef STATION_H
#define STATION_H

#include <stdint.h>

enum sensor_kind { SENSOR_TEMP, SENSOR_HUMIDITY,
                   SENSOR_PRESSURE, SENSOR_WIND };

#define STN_FLAG_VALID        0x01
#define STN_FLAG_CALIBRATED   0x02
#define STN_FLAG_LOW_BATTERY  0x04
#define STN_FLAG_OUT_OF_RANGE 0x08

/* One sample from one sensor.
 * OWNERSHIP: a plain value with no pointers inside. Copy it
 * with =; there is nothing to free.
 * LAYOUT: chosen by the compiler and the target, padding
 * included. Never fwrite the struct itself; write the
 * fields one at a time. */
struct reading {
    uint32_t sensor_id;   /* e.g. 1001 */
    int64_t  time_s;      /* seconds since station start */
    double   value;       /* in the sensor's unit */
    uint8_t  flags;       /* STN_FLAG_* bits */
};

#endif /* STATION_H */
ch09/station_read.c94 lines
#include <stdio.h>
#include "stn_log.h"

/* station.h has no count of sensor kinds, so name one here. */
#define KIND_COUNT (SENSOR_WIND + 1)

struct day_summary {
    long frost_minutes;     /* temperature below 2.0 C */
    long humid_minutes;     /* humidity above 90 % */
    long pressure_n;        /* pressure readings seen */
    double min_pressure;    /* hPa, valid if pressure_n > 0 */
    double max_wind;        /* m/s */
};

typedef void (*reading_handler)(const struct reading *r,
                                struct day_summary *s);

static void on_temp(const struct reading *r,
                    struct day_summary *s) {
    if (r->value < 2.0) s->frost_minutes++;
}
static void on_humidity(const struct reading *r,
                        struct day_summary *s) {
    if (r->value > 90.0) s->humid_minutes++;
}
static void on_pressure(const struct reading *r,
                        struct day_summary *s) {
    if (s->pressure_n++ == 0 || r->value < s->min_pressure)
        s->min_pressure = r->value;
}
static void on_wind(const struct reading *r,
                    struct day_summary *s) {
    if (r->value > s->max_wind) s->max_wind = r->value;
}

static const reading_handler handlers[KIND_COUNT] = {
    [SENSOR_TEMP]     = on_temp,
    [SENSOR_HUMIDITY] = on_humidity,
    [SENSOR_PRESSURE] = on_pressure,
    [SENSOR_WIND]     = on_wind,
};

/* 1001 -> SENSOR_TEMP ... 1004 -> SENSOR_WIND; -1 if unknown */
static int kind_of(uint32_t sensor_id) {
    uint32_t k = sensor_id % 1000;
    if (k < 1 || k > KIND_COUNT) return -1;
    return (int)(k - 1);
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "usage: %s log\n", argv[0]);
        return 2;
    }
    FILE *f = fopen(argv[1], "rb");
    if (f == NULL) { perror(argv[1]); return 1; }
    uint32_t count = 0;
    enum stn_log_status st = stn_log_read_header(f, &count);
    if (st != STN_LOG_OK) {
        fprintf(stderr, "%s: header: %s\n", argv[1],
                stn_log_status_name(st));
        fclose(f);
        return 1;
    }
    struct day_summary sum = {0, 0, 0, 0.0, 0.0};
    uint32_t n = 0, skipped = 0;
    struct reading r;
    while (n < count) {
        st = stn_log_read_record(f, &r);
        if (st != STN_LOG_OK) break;
        n++;
        int k = kind_of(r.sensor_id);
        if (k < 0 || !(r.flags & STN_FLAG_VALID)) {
            skipped++;
            continue;
        }
        handlers[k](&r, &sum);
    }
    fclose(f);
    printf("records: %u of %u (skipped %u)\n",
           (unsigned)n, (unsigned)count, (unsigned)skipped);
    printf("frost min: %ld  humid min: %ld\n",
           sum.frost_minutes, sum.humid_minutes);
    if (sum.pressure_n > 0)
        printf("min pressure: %.2f hPa  ", sum.min_pressure);
    printf("max wind: %.2f m/s\n", sum.max_wind);
    if (n < count) {
        fprintf(stderr, "%s: stopped after record %u of %u: %s\n",
                argv[1], (unsigned)n, (unsigned)count,
                stn_log_status_name(st));
        return 1;
    }
    return 0;
}
ch09/stn_log.c94 lines
#include "stn_log.h"
#include <string.h>

static void put_u32le(unsigned char *b, uint32_t x) {
    for (int i = 0; i < 4; i++) {
        b[i] = (unsigned char)(x >> (8 * i)); /* low byte 1st */
    }
}
static void put_u64le(unsigned char *b, uint64_t x) {
    for (int i = 0; i < 8; i++) {
        b[i] = (unsigned char)(x >> (8 * i));
    }
}
static uint32_t get_u32le(const unsigned char *b) {
    uint32_t x = 0;
    for (int i = 0; i < 4; i++) {
        x |= (uint32_t)b[i] << (8 * i);  /* widen, then shift */
    }
    return x;
}
static uint64_t get_u64le(const unsigned char *b) {
    uint64_t x = 0;
    for (int i = 0; i < 8; i++) {
        x |= (uint64_t)b[i] << (8 * i);
    }
    return x;
}

enum stn_log_status stn_log_write_header(FILE *f, uint32_t n) {
    unsigned char b[STN_LOG_HEADER_SIZE];
    memcpy(b, "STN1", 4);
    put_u32le(b + 4, n);
    if (fwrite(b, 1, sizeof b, f) != sizeof b) {
        return STN_LOG_IO_ERROR;
    }
    return STN_LOG_OK;
}

enum stn_log_status stn_log_write_record(FILE *f,
                                         const struct reading *r) {
    unsigned char b[STN_LOG_RECORD_SIZE];
    uint64_t tbits, vbits;
    memcpy(&tbits, &r->time_s, sizeof tbits);
    memcpy(&vbits, &r->value, sizeof vbits);
    put_u32le(b + 0, r->sensor_id);
    put_u64le(b + 4, tbits);
    put_u64le(b + 12, vbits);
    b[20] = r->flags;
    if (fwrite(b, 1, sizeof b, f) != sizeof b) {
        return STN_LOG_IO_ERROR;
    }
    return STN_LOG_OK;
}

enum stn_log_status stn_log_read_header(FILE *f, uint32_t *n) {
    unsigned char b[STN_LOG_HEADER_SIZE];
    size_t got = fread(b, 1, sizeof b, f);
    if (got != sizeof b) {
        return ferror(f) ? STN_LOG_IO_ERROR : STN_LOG_TRUNCATED;
    }
    if (memcmp(b, "STN1", 4) != 0) {
        return STN_LOG_BAD_MAGIC;
    }
    *n = get_u32le(b + 4);
    return STN_LOG_OK;
}

enum stn_log_status stn_log_read_record(FILE *f,
                                        struct reading *out) {
    unsigned char b[STN_LOG_RECORD_SIZE];
    size_t got = fread(b, 1, sizeof b, f);
    if (got != sizeof b) {
        if (ferror(f)) return STN_LOG_IO_ERROR;
        return got == 0 ? STN_LOG_END : STN_LOG_TRUNCATED;
    }
    uint64_t tbits = get_u64le(b + 4);
    uint64_t vbits = get_u64le(b + 12);
    out->sensor_id = get_u32le(b + 0);
    memcpy(&out->time_s, &tbits, sizeof tbits);
    memcpy(&out->value, &vbits, sizeof vbits);
    out->flags = b[20];
    return STN_LOG_OK;
}

const char *stn_log_status_name(enum stn_log_status s) {
    switch (s) {
    case STN_LOG_OK:        return "ok";
    case STN_LOG_END:       return "end of file";
    case STN_LOG_TRUNCATED: return "truncated";
    case STN_LOG_BAD_MAGIC: return "bad magic";
    case STN_LOG_IO_ERROR:  return "I/O error";
    }
    return "unknown status";
}
ch09/stn_log.h28 lines
#ifndef STN_LOG_H
#define STN_LOG_H
#include <stdint.h>
#include <stdio.h>
#include "station.h"

#define STN_LOG_HEADER_SIZE 8   /* "STN1" + uint32 count */
#define STN_LOG_RECORD_SIZE 21  /* 4 + 8 + 8 + 1 bytes */

enum stn_log_status {
    STN_LOG_OK,         /* one complete item read or written */
    STN_LOG_END,        /* no bytes left: clean end of file */
    STN_LOG_TRUNCATED,  /* some bytes, but fewer than needed */
    STN_LOG_BAD_MAGIC,  /* not a station log */
    STN_LOG_IO_ERROR    /* the stream reported an error */
};

/* Each returns STN_LOG_OK on success. The caller owns f,
   opened in binary mode, and closes it. */
enum stn_log_status stn_log_write_header(FILE *f, uint32_t n);
enum stn_log_status stn_log_write_record(FILE *f,
                                         const struct reading *r);
enum stn_log_status stn_log_read_header(FILE *f, uint32_t *n);
enum stn_log_status stn_log_read_record(FILE *f,
                                        struct reading *out);
const char *stn_log_status_name(enum stn_log_status s);

#endif
ch09/tocsv.c12 lines
#include <stdio.h>
#include "stn_log.h"
int main(int argc, char *argv[]) {
    if (argc != 2) return 2;
    FILE *f = fopen(argv[1], "rb");
    if (!f) { perror(argv[1]); return 1; }
    uint32_t n; struct reading r;
    if (stn_log_read_header(f, &n) != STN_LOG_OK) return 1;
    for (uint32_t i = 0; i < n && stn_log_read_record(f, &r) == STN_LOG_OK; i++)
        printf("%u,%lld,%.2f,%u\n", (unsigned)r.sensor_id, (long long)r.time_s, r.value, (unsigned)r.flags);
    fclose(f); return 0;
}