static void log_message(const char * const message) { FILE * fp; char filename[128]; time_t now = time(NULL); struct tm * broken = gmtime(&now); sprintf(filename, "/tmp/trustdb-messages-%04d-%02d-%02d.log", broken->tm_year + 1900, broken->tm_mon + 1, broken->tm_mday); if((fp = fopen(filename, "a"))) { fprintf(fp, "%s\n%s\n", time_text(now, false), message); fclose(fp); } }
static void process_timetable(const char * string, const jsmntok_t * tokens) { char timestamp[32], zs[256]; strcpy(zs, "Timetable information: "); jsmn_find_extract_token(string, tokens, 0, "timestamp", timestamp, sizeof(timestamp)); if(timestamp[0]) { time_t stamp = atoi(timestamp); strcat(zs, "Timestamp "); strcat(zs, time_text(stamp, true)); _log(GENERAL, "%s. Sequence number %s.", zs, commas_q(stats[Sequence])); } else { strcat(zs, "Timestamp not found. Processing aborted."); _log(CRITICAL, zs); run = 0; } }
void dump_mysql_result(MYSQL_RES * result) { MYSQL_ROW row; MYSQL_FIELD * fields = mysql_fetch_fields(result); word num_fields = mysql_num_fields(result); word num_rows = mysql_num_rows(result); word field, row_i; char zs[1024]; sprintf(zs, "Result dump: %d rows of %d fields", num_rows, num_fields); _log(MINOR, zs); for(row_i = 0; row_i < num_rows; row_i++) { sprintf(zs, "Row %d", row_i); _log(MINOR, zs); row = mysql_fetch_row(result); for(field = 0; field < num_fields; field++) { sprintf(zs, " %s = \"%s\"", fields[field].name, row[field]); // See if we can spot a timestamp if(row[field][0] == '1' && strlen(row[field]) == 10) { time_t stamp = atol(row[field]); // Accept Jan 2008 to May 2033 // 1,200,000,000 2,000,000,000 if(stamp > 1200000000L && stamp < 2000000000L) { strcat(zs, " "); strcat(zs, time_text(stamp, false )); } } _log(MINOR, zs); } } }
static word fetch_file(void) { // Returns 0=Success with relevant file open on fp_result // Or !0 = failure and file is closed. // DANGER: In failure case, fp_result is INVALID and may not be null. char zs[256], filepathz[256], filepath[256], url[256]; time_t now, when; struct tm * broken; static char * weekdays[] = { "sun", "mon", "tue", "wed", "thu", "fri", "sat" }; stats[Fetches]++; now = time(NULL); if(!opt_filename) { static CURL * curlh; struct curl_slist * slist; if(!(curlh = curl_easy_init())) { _log(CRITICAL, "fetch_file(): Failed to obtain libcurl easy handle."); return 1; } curl_easy_setopt(curlh, CURLOPT_WRITEFUNCTION, cif_write_data); slist = NULL; slist = curl_slist_append(slist, "Cache-Control: no-cache"); if(!slist) { _log(MAJOR,"fetch_file(): Failed to create slist."); return 1; } // Build URL when = now - 24*60*60; broken = localtime(&when); // Note broken contains "yesterday" if(opt_url) { strcpy(url, opt_url); } else { if(fetch_all) { sprintf(url, "https://datafeeds.networkrail.co.uk/ntrod/CifFileAuthenticate?type=CIF_ALL_FULL_DAILY&day=toc-full"); } else { sprintf(url, "https://datafeeds.networkrail.co.uk/ntrod/CifFileAuthenticate?type=CIF_ALL_UPDATE_DAILY&day=toc-update-%s", weekdays[broken->tm_wday]); } } sprintf(zs, "Fetching \"%s\".", url); _log(GENERAL, zs); if(opt_url || debug) { sprintf(filepathz, "/tmp/tscdb-cif-fetch-%ld.gz", now); sprintf(filepath, "/tmp/tscdb-cif-fetch-%ld", now); } else if(fetch_all) { sprintf(filepathz, "/tmp/tscdb-cif-all-%02d-%02d-%02d-%s.gz", broken->tm_year%100, broken->tm_mon + 1, broken->tm_mday, weekdays[broken->tm_wday]); sprintf(filepath, "/tmp/tscdb-cif-all-%02d-%02d-%02d-%s", broken->tm_year%100, broken->tm_mon + 1, broken->tm_mday, weekdays[broken->tm_wday]); } else { sprintf(filepathz, "/tmp/tscdb-cif-%02d-%02d-%02d-%s.gz", broken->tm_year%100, broken->tm_mon + 1, broken->tm_mday, weekdays[broken->tm_wday]); sprintf(filepath, "/tmp/tscdb-cif-%02d-%02d-%02d-%s", broken->tm_year%100, broken->tm_mon + 1, broken->tm_mday, weekdays[broken->tm_wday]); } if(!(fp_result = fopen(filepathz, "w"))) { sprintf(zs, "Failed to open \"%s\" for writing.", filepathz); _log(MAJOR, zs); return 1; } curl_easy_setopt(curlh, CURLOPT_HTTPHEADER, slist); // Set timeouts curl_easy_setopt(curlh, CURLOPT_NOSIGNAL, 1L); curl_easy_setopt(curlh, CURLOPT_FTP_RESPONSE_TIMEOUT, 128L); curl_easy_setopt(curlh, CURLOPT_TIMEOUT, 128L); curl_easy_setopt(curlh, CURLOPT_CONNECTTIMEOUT, 128L); // Debugging prints. // curl_easy_setopt(curlh, CURLOPT_VERBOSE, 1L); // URL and login curl_easy_setopt(curlh, CURLOPT_URL, url); sprintf(zs, "%s:%s", conf[conf_nr_user], conf[conf_nr_password]); curl_easy_setopt(curlh, CURLOPT_USERPWD, zs); curl_easy_setopt(curlh, CURLOPT_FOLLOWLOCATION, 1L); // On receiving a 3xx response, follow the redirect. total_bytes = 0; CURLcode result; if((result = curl_easy_perform(curlh))) { _log(MAJOR, "fetch_file(): curl_easy_perform() returned error %d: %s.", result, curl_easy_strerror(result)); if(opt_insecure && (result == 51 || result == 60)) { _log(MAJOR, "Retrying download in insecure mode."); // SSH failure, retry without curl_easy_setopt(curlh, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt(curlh, CURLOPT_SSL_VERIFYHOST, 0L); used_insecure = true; if((result = curl_easy_perform(curlh))) { _log(MAJOR, "fetch_file(): In insecure mode curl_easy_perform() returned error %d: %s.", result, curl_easy_strerror(result)); if(fp_result) fclose(fp_result); fp_result = NULL; return 1; } } else { if(fp_result) fclose(fp_result); fp_result = NULL; return 1; } } char * actual_url; if(!curl_easy_getinfo(curlh, CURLINFO_EFFECTIVE_URL, &actual_url) && actual_url) { _log(GENERAL, "Download was redirected to \"%s\".", actual_url); } if(fp_result) fclose(fp_result); fp_result = NULL; if(curlh) curl_easy_cleanup(curlh); curlh = NULL; if(slist) curl_slist_free_all(slist); slist = NULL; sprintf(zs, "Received %s bytes of compressed CIF updates.", commas(total_bytes)); _log(GENERAL, zs); if(total_bytes == 0) return 1; _log(GENERAL, "Decompressing data..."); sprintf(zs, "/bin/gunzip -f %s", filepathz); char * rc; if((rc = system_call(zs))) { _log(MAJOR, "Failed to uncompress file: %s", rc); if((fp_result = fopen(filepathz, "r"))) { char error_message[2048]; size_t length; if((length = fread(error_message, 1, 2047, fp_result)) && error_message[0] == '<') { error_message[length] = '\0'; _log(MAJOR, "Received message:\n%s", error_message); } fclose(fp_result); } return 1; } _log(GENERAL, "Decompressed."); } else { strcpy(filepath, opt_filename); } if(!(fp_result = fopen(filepath, "r"))) { _log(MAJOR, "Failed to open \"%s\" for reading.", filepath); return 1; } // Check if it's really an update { sprintf(zs, "grep -q \\\"Delete\\\" %s", filepath); word not_update = system(zs); if(not_update && (total_bytes < 32000000L)) not_update = false; _log(test_mode?GENERAL:DEBUG, "File is an update assessment: %s.", not_update?"File is not an update":"File is an update"); if(fetch_all && !not_update) { _log(MAJOR, "Requested full timetable looks like an update."); fclose(fp_result); return 1; } if(!fetch_all && not_update) { _log(MAJOR, "Requested update looks like a full timetable."); fclose(fp_result); return 1; } } // Read enough of the file to find the datestamp char front[256]; regmatch_t matches[3]; if(!fread(front, 1, sizeof(front), fp_result)) { fclose(fp_result); return 1; } else { front[255] = '\0'; if(regexec(&match[0], front, 2, matches, 0)) { // Failed _log(MAJOR, "Failed to derive CIF file timestamp."); fclose(fp_result); return 1; } else { char zstamp[256]; extract_match(front, matches, 1, zstamp, sizeof(zstamp)); time_t stamp = atoi(zstamp); _log(test_mode?GENERAL:DEBUG, "Recovered timestamp: %s", time_text(stamp, 0)); _log(test_mode?GENERAL:DEBUG, "Stored in file \"%s\"", filepath); if(regexec(&match[1], front, 2, matches, 0)) { _log(MINOR, "Failed to derive CIF file sequence number."); } else { extract_match(front, matches, 1, zstamp, sizeof(zstamp)); stats[Sequence] = atol(zstamp); _log(test_mode?GENERAL:DEBUG, "Sequence number: %s", commas_q(stats[Sequence])); } if(!test_mode && !opt_url && !opt_filename && (now < stamp || now - stamp > 36*60*60)) { _log(MAJOR, "Timestamp %s is incorrect. Received sequence number %d.", time_text(stamp, true), stats[Sequence]); fclose(fp_result); stats[Sequence] = 0; return 1; } } } fseeko(fp_result, 0, SEEK_SET); return 0; }
static void process_trust_0003(const char * string, const jsmntok_t * tokens, const int index) { char query[1024], zs[32], zs1[32], train_id[128], loc_stanox[128]; time_t planned_timestamp, actual_timestamp, timestamp; time_t now = time(NULL); sprintf(query, "INSERT INTO trust_movement VALUES(%ld, '", now); jsmn_find_extract_token(string, tokens, index, "train_id", train_id, sizeof(train_id)); strcat(query, train_id); strcat(query, "', '"); jsmn_find_extract_token(string, tokens, index, "event_type", zs, sizeof(zs)); strcat(query, zs); strcat(query, "', '"); jsmn_find_extract_token(string, tokens, index, "planned_event_type", zs, sizeof(zs)); strcat(query, zs); strcat(query, "', '"); jsmn_find_extract_token(string, tokens, index, "platform", zs, sizeof(zs)); strcat(query, zs); strcat(query, "', '"); jsmn_find_extract_token(string, tokens, index, "loc_stanox", loc_stanox, sizeof(loc_stanox)); strcat(query, loc_stanox); strcat(query, "', "); jsmn_find_extract_token(string, tokens, index, "actual_timestamp", zs, sizeof(zs)); zs[10] = '\0'; actual_timestamp = correct_trust_timestamp(atol(zs)); sprintf(zs, "%ld", actual_timestamp); strcat(query, zs); strcat(query, ", "); jsmn_find_extract_token(string, tokens, index, "gbtt_timestamp", zs, sizeof(zs)); zs[10] = '\0'; timestamp = correct_trust_timestamp(atol(zs)); sprintf(zs, "%ld", timestamp); strcat(query, zs); strcat(query, ", "); jsmn_find_extract_token(string, tokens, index, "planned_timestamp", zs, sizeof(zs)); zs[10] = '\0'; planned_timestamp = correct_trust_timestamp(atol(zs)); sprintf(zs, "%ld", planned_timestamp); strcat(query, zs); strcat(query, ", "); jsmn_find_extract_token(string, tokens, index, "timetable_variation", zs, sizeof(zs)); strcat(query, zs); strcat(query, ", '"); jsmn_find_extract_token(string, tokens, index, "event_source", zs, sizeof(zs)); strcat(query, zs); strcat(query, "', "); jsmn_find_extract_token(string, tokens, index, "offroute_ind", zs, sizeof(zs)); strcat(query, (zs[0] == 't')?"1":"0"); strcat(query, ", "); jsmn_find_extract_token(string, tokens, index, "train_terminated", zs, sizeof(zs)); strcat(query, (zs[0] == 't')?"1":"0"); strcat(query, ", '"); jsmn_find_extract_token(string, tokens, index, "variation_status", zs, sizeof(zs)); strcat(query, zs); strcat(query, "', '"); jsmn_find_extract_token(string, tokens, index, "next_report_stanox", zs, sizeof(zs)); strcat(query, zs); strcat(query, "', "); jsmn_find_extract_token(string, tokens, index, "next_report_run_time", zs, sizeof(zs)); sprintf(zs1, "%d", atoi(zs)); strcat(query, zs1); strcat(query, ")"); db_query(query); // Old one? if(planned_timestamp && now - actual_timestamp > 6*60*60) { sprintf(query, "Late movement message received, actual timestamp %s.", time_text(actual_timestamp, true)); _log(MINOR, query); } sprintf(query, "SELECT * from trust_activation where trust_id = '%s' and created > %ld and cif_schedule_id > 0", train_id, now - (4*24*60*60)); if(!db_query(query)) { MYSQL_RES * result0 = db_store_result(); word num_rows = mysql_num_rows(result0); mysql_free_result(result0); if(num_rows > 1) { // This is not actually invalid, if there's some cancellations as well // sprintf(query, "Movement message received with %d matching activations, train_id = \"%s\".", num_rows, train_id); // _log(MAJOR, query); } else if(num_rows < 1) { MYSQL_ROW row0; char tiploc[128], reason[128]; word sort_time; time_t now = time(NULL); strcpy(reason, ""); tiploc[0] = '\0'; sprintf(query, "Movement message received with %d matching activations, train_id = \"%s\".", num_rows, train_id); _log(MINOR, query); stats[MovtNoAct]++; if(planned_timestamp == 0) { strcpy(reason, "No planned timestamp"); } if(high_load) strcpy(reason, "Message load too high"); if(!reason[0]) { sprintf(query, "SELECT tiploc FROM corpus WHERE stanox = %s AND tiploc != ''", loc_stanox); if(!db_query(query)) { result0 = db_store_result(); num_rows = mysql_num_rows(result0); if(num_rows) { row0 = mysql_fetch_row(result0); strcpy(tiploc, row0[0]); } else { strcpy(reason, "Unable to determine TIPLOC"); } mysql_free_result(result0); } } if(!reason[0]) { char query1[256]; struct tm * broken = localtime(&planned_timestamp); sort_time = broken->tm_hour * 4 * 60 + broken->tm_min * 4; // Select the day word day = broken->tm_wday; word yest = (day + 6) % 7; // word tom = (day + 1) % 7; broken->tm_hour = 12; broken->tm_min = 0; broken->tm_sec = 0; time_t when = timegm(broken); sprintf(query, "SELECT cif_schedules.id, cif_schedules.CIF_train_uid, signalling_id, CIF_stp_indicator FROM cif_schedules INNER JOIN cif_schedule_locations ON cif_schedules.id = cif_schedule_locations.cif_schedule_id WHERE cif_schedule_locations.tiploc_code = '%s'", tiploc); sprintf(query1, " AND cif_schedule_locations.sort_time > %d AND cif_schedule_locations.sort_time < %d", sort_time - 1, sort_time + 4); strcat(query, query1); strcat(query, " AND (cif_schedules.CIF_stp_indicator = 'N' OR cif_schedules.CIF_stp_indicator = 'P' OR cif_schedules.CIF_stp_indicator = 'O')"); static const char * days_runs[8] = {"runs_su", "runs_mo", "runs_tu", "runs_we", "runs_th", "runs_fr", "runs_sa", "runs_su"}; // sprintf(query1, " AND (((%s) AND (schedule_start_date <= %ld) AND (schedule_end_date >= %ld) AND (NOT next_day))", days_runs[day], when + 12*60*60, when - 12*60*60); strcat(query, query1); sprintf(query1, " OR ((%s) AND (schedule_start_date <= %ld) AND (schedule_end_date >= %ld) AND ( next_day)))", days_runs[yest], when - 12*60*60, when - 36*60*60); strcat(query, query1); sprintf(query1, " AND deleted > %ld ORDER BY LOCATE(CIF_stp_indicator, 'NPO')", planned_timestamp); strcat(query, query1); if(!db_query(query)) { char save_uid[16], save_stp; save_uid[0] = save_stp = '\0'; dword cif_schedule_id = 0; result0 = db_store_result(); num_rows = mysql_num_rows(result0); if(!num_rows) { strcpy(reason, "No schedules found"); } while((row0 = mysql_fetch_row(result0))) { sprintf(query, " Found potential match:%8s (%s) %s STP=%s", row0[0], row0[1], row0[2], row0[3]); _log(MINOR, query); if (!reason[0]) { if(save_uid[0] && strcmp(save_uid, row0[1])) strcpy(reason, "Multiple matching schedule UIDs"); else if (save_stp == 'O' && row0[3][0] =='O') strcpy(reason, "Multiple matching overlay schedules"); else { cif_schedule_id = atol(row0[0]); strcpy(save_uid, row0[1]); save_stp = row0[3][0]; } } } mysql_free_result(result0); if(!reason[0]) { sprintf(query, "INSERT INTO trust_activation VALUES(%ld, '%s', %ld, 1)", now, train_id, cif_schedule_id); db_query(query); sprintf(query, " Successfully deduced activation %ld", cif_schedule_id); _log(MINOR, query); } } } if(!reason[0]) { stats[DeducedAct]++; } else { sprintf(query, " Failed to deduce an activation - Reason: %s.", reason ); _log(MINOR, query); sprintf(query, " stanox = %s, tiploc = \"%s\", planned_timestamp %s, derived sort time = %d", loc_stanox, tiploc, time_text(planned_timestamp, true), sort_time); _log(MINOR, query); // jsmn_dump_tokens(string, tokens, index); } } } return; }
static void process_message(const word describer, const char * const body, const size_t index) { char message_type[8]; char times[16]; time_t timestamp; char from[16], to[16], descr[16], wasf[16], wast[16]; jsmn_find_extract_token(body, tokens, index, "msg_type", message_type, sizeof(message_type)); _log(DEBUG, "Message name = \"%s\".", message_type); jsmn_find_extract_token(body, tokens, index, "time", times, sizeof(times)); times[10] = '\0'; timestamp = atol(times); time_t now = time(NULL); last_td_processed[describer] = now; if(((status_last_td_actual[describer] + 8) < timestamp) || ((status_last_td_processed + 8) < now)) { status_last_td_actual[describer] = timestamp; status_last_td_processed = now; char query[256]; sprintf(query, "update status SET last_td_processed = %ld", now); db_query(query); sprintf(query, "update td_status set last_timestamp = %ld WHERE d = '%s'", timestamp, describers[describer]); db_query(query); } if(!strcasecmp(message_type, "CA")) { jsmn_find_extract_token(body, tokens, index, "from", from, sizeof(from)); jsmn_find_extract_token(body, tokens, index, "to", to, sizeof(to)); jsmn_find_extract_token(body, tokens, index, "descr", descr, sizeof(descr)); strcpy(wasf, query_berth(describer, from)); strcpy(wast, query_berth(describer, to)); _log(DEBUG, "%s CA: Berth step (%s) Description \"%s\" from berth \"%s\" to berth \"%s\"", describers[describer], time_text(timestamp, true), descr, from, to); log_detail(timestamp, "%s CA: %s from %s to %s", describers[describer], descr, from, to); if(strcmp(wasf, descr) || strcmp(wast, "")) { _log(DEBUG, "Message %s CA: Step \"%s\" from \"%s\" to \"%s\" found \"%s\" in \"%s\" and \"%s\" in \"%s\".", describers[describer], descr, from, to, wasf, from, wast, to); } update_database(Berth, describer, from, ""); update_database(Berth, describer, to, descr); stats[CA]++; } else if(!strcasecmp(message_type, "CB")) { jsmn_find_extract_token(body, tokens, index, "from", from, sizeof(from)); jsmn_find_extract_token(body, tokens, index, "descr", descr, sizeof(descr)); strcpy(wasf, query_berth(describer, from)); _log(DEBUG, "%s CB: Berth cancel (%s) Description \"%s\" from berth \"%s\"", describers[describer], time_text(timestamp, true), descr, from); log_detail(timestamp, "%s CB: %s from %s", describers[describer], descr, from); if(strcmp(wasf, descr)) { _log(DEBUG, "Message %s CB: Cancel \"%s\" from \"%s\" found \"%s\" in \"%s\".", describers[describer], descr, from, wasf, from); } update_database(Berth, describer, from, ""); stats[CB]++; } else if(!strcasecmp(message_type, "CC")) { jsmn_find_extract_token(body, tokens, index, "to", to, sizeof(to)); jsmn_find_extract_token(body, tokens, index, "descr", descr, sizeof(descr)); _log(DEBUG, "%s CC: Berth interpose (%s) Description \"%s\" to berth \"%s\"", describers[describer], time_text(timestamp, true), descr, to); log_detail(timestamp, "%s CC: %s to %s", describers[describer], descr, to); update_database(Berth, describer, to, descr); stats[CC]++; } else if(!strcasecmp(message_type, "CT")) { // Heartbeat char report_time[16]; jsmn_find_extract_token(body, tokens, index, "report_time", report_time, sizeof(report_time)); _log(DEBUG, "%s CT: Heartbeat (%s) Report time = %s", describers[describer], time_text(timestamp, true), report_time); stats[CT]++; } else if(!strcasecmp(message_type, "SF")) { char address[16], data[32]; jsmn_find_extract_token(body, tokens, index, "address", address, sizeof(address)); jsmn_find_extract_token(body, tokens, index, "data", data, sizeof(data)); _log(DEBUG, "%s SF: Signalling update (%s) Address \"%s\", data \"%s\"", describers[describer], time_text(timestamp, true), address, data); word a = strtoul(address, NULL, 16); dword d = strtoul(data, NULL, 16); signalling_update("SF", describer, timestamp, a, d); stats[SF]++; } else if(!strcasecmp(message_type, "SG")) { char address[16], data[32]; jsmn_find_extract_token(body, tokens, index, "address", address, sizeof(address)); jsmn_find_extract_token(body, tokens, index, "data", data, sizeof(data)); _log(DEBUG, "%s SG: Signalling refresh (%s) Address \"%s\", data \"%s\"", describers[describer], time_text(timestamp, true), address, data); word a = strtoul(address, NULL, 16); dword d = strtoul(data, NULL, 16); _log(DEBUG, "a = %04x, d = %08x", a, d); signalling_update("SG", describer, timestamp, a , 0xff & (d >> 24)); signalling_update("SG", describer, timestamp, a + 1 , 0xff & (d >> 16)); signalling_update("SG", describer, timestamp, a + 2 , 0xff & (d >> 8 )); signalling_update("SG", describer, timestamp, a + 3 , 0xff & (d )); stats[SG]++; }