SELKIELogger  1.0.0
ExtractSource.c
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2023 Swansea University
3  *
4  * This file is part of the SELKIELogger suite of tools.
5  *
6  * SELKIELogger is free software: you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the Free
8  * Software Foundation, either version 3 of the License, or (at your option)
9  * any later version.
10  *
11  * SELKIELogger is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14  * more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this SELKIELogger product.
18  * If not, see <http://www.gnu.org/licenses/>.
19 */
20 
21 #include <errno.h>
22 #include <libgen.h>
23 #include <stdbool.h>
24 #include <stdio.h>
25 #include <sys/stat.h>
26 #include <unistd.h>
27 #include <zlib.h>
28 
29 #include "SELKIELoggerBase.h"
30 #include "SELKIELoggerMP.h"
31 
32 #include "version.h"
33 
47 int main(int argc, char *argv[]) {
48  program_state state = {0};
49  state.verbose = 1;
50 
51  char *outFileName = NULL;
52  bool clobberOutput = false;
53  uint8_t source = 0;
54  bool type[255] = {0};
55  bool raw = false;
56 
57  char *usage =
58  "Usage: %1$s [-v] [-q] [-f] [-r] [-o outfile] -S source [-C channel [-C channel ...]] DATFILE\n"
59  "\t-v\tIncrease verbosity\n"
60  "\t-q\tDecrease verbosity\n"
61  "\t-f\tOverwrite existing output files\n"
62  "\t-r\tWrite raw data (No message formatting)\n"
63  "\t-S\tSource number to extract\n"
64  "\t-T\tMessage type(s) to extract\n"
65  "\t-o\tWrite output to named file\n"
66  "\nVersion: " GIT_VERSION_STRING "\n"
67  "\nOutput file name will be generated based on input file name, unless set by -o option\n";
68 
69  opterr = 0; // Handle errors ourselves
70  int go = 0;
71  bool doUsage = false;
72  uint8_t tmp = 0;
73  uint8_t typeCount = 0;
74  while ((go = getopt(argc, argv, "vqfro:S:C:")) != -1) {
75  switch (go) {
76  case 'v':
77  state.verbose++;
78  break;
79  case 'q':
80  state.verbose--;
81  break;
82  case 'f':
83  clobberOutput = true;
84  break;
85  case 'r':
86  raw = true;
87  break;
88  case 'S':
89  source = strtol(optarg, NULL, 0);
90  if (source < 2 || source > 128) {
91  log_error(&state, "Invalid source requested (%s)", optarg);
92  doUsage = true;
93  }
94  break;
95  case 'C':
96  tmp = strtol(optarg, NULL, 0);
97  if (tmp < 1 || tmp >= 128) {
98  log_error(&state, "Invalid message type requested (%s)",
99  optarg);
100  doUsage = true;
101  }
102  type[tmp] = true;
103  typeCount++;
104  break;
105 
106  case 'o':
107  if (outFileName) {
108  log_error(
109  &state,
110  "Only a single output file name can be provided");
111  doUsage = true;
112  } else {
113  outFileName = strdup(optarg);
114  }
115  break;
116  case '?':
117  log_error(&state, "Unknown option `-%c'", optopt);
118  doUsage = true;
119  }
120  }
121 
122  // Should be 1 spare arguments: The file to convert
123  if (argc - optind != 1) {
124  log_error(&state, "Invalid arguments");
125  doUsage = true;
126  }
127 
128  if (doUsage) {
129  fprintf(stderr, usage, argv[0]);
130  destroy_program_state(&state);
131  free(outFileName);
132  return -1;
133  }
134 
135  char *inFileName = strdup(argv[optind]);
136  FILE *inFile = fopen(inFileName, "rb");
137  if (inFile == NULL) {
138  log_error(&state, "Unable to open input file");
139  free(inFileName);
140  free(outFileName);
141  destroy_program_state(&state);
142  return -1;
143  }
144 
145  if (raw && typeCount != 1) {
146  log_warning(&state, "Raw mode requested without a message type filter");
147  log_warning(&state,
148  "Different message types will not be distinguished in output file");
149  }
150 
151  if (outFileName == NULL) {
152  // Work out output file name
153  // Split into base and dirnames so that we're don't accidentally split the
154  // path on a .
155  char *inF1 = strdup(inFileName);
156  char *inF2 = strdup(inFileName);
157  char *dn = dirname(inF1);
158  char *bn = basename(inF2);
159 
160  // Find last . in file name, if any
161  char *ep = strrchr(bn, '.');
162 
163  // If no ., use full basename length, else use length to .
164  int bnl = 0;
165  if (ep == NULL) {
166  bnl = strlen(bn);
167  } else {
168  bnl = ep - bn;
169  }
170  // New basename is old basename up to . (or end, if absent)
171  char *nbn = calloc(bnl + 1, sizeof(char));
172  strncpy(nbn, bn, bnl);
173  if (asprintf(&outFileName, "%s/%s.s%02x.dat", dn, nbn, source) <= 0) {
174  outFileName = NULL;
175  }
176  free(nbn);
177  free(inF1);
178  free(inF2);
179  if (outFileName == NULL) {
180  log_error(&state, "Unable to generate output file name");
181  free(inFileName);
182  return -1;
183  }
184  }
185 
186  errno = 0;
187  char fmode[4] = {'w', 'b', 0};
188 
189  if (!clobberOutput) {
190  // wbx7: Create, binary, comp. level 7
191  fmode[2] = 'x';
192  }
193 
194  FILE *outFile = fopen(outFileName, fmode);
195  if (outFile == NULL) {
196  log_error(&state, "Unable to open output file");
197  log_error(&state, "%s", strerror(errno));
198  fclose(inFile);
199  free(inFileName);
200  free(outFileName);
201  destroy_program_state(&state);
202  return -1;
203  }
204  log_info(&state, 1, "Writing messages from source 0x%02x to %s", source, outFileName);
205  log_info(&state, 1, "Raw mode %s", raw ? "enabled" : "disabled");
206  if (typeCount > 0) {
207  log_info(&state, 2, "Filtering for %d message types", typeCount);
208  for (int i = 0; i < 128; i++) {
209  if (type[i]) { log_info(&state, 3, "Message type 0x%02x enabled", i); }
210  }
211  }
212  free(outFileName);
213  outFileName = NULL;
214 
215  state.started = 1;
216  int msgCount = 0;
217  struct stat inStat = {0};
218  if (fstat(fileno(inFile), &inStat) != 0) {
219  log_error(&state, "Unable to get input file status: %s", strerror(errno));
220  free(inFileName);
221  fclose(inFile);
222  fclose(outFile);
223  destroy_program_state(&state);
224  return -1;
225  }
226  long inSize = inStat.st_size;
227  long inPos = 0;
228  int progress = 0;
229  if (inSize >= 1E9) {
230  log_info(&state, 1, "Reading %.2fGB of data from %s", inSize / 1.0E9, inFileName);
231  } else if (inSize >= 1E6) {
232  log_info(&state, 1, "Reading %.2fMB of data from %s", inSize / 1.0E6, inFileName);
233  } else if (inSize >= 1E3) {
234  log_info(&state, 1, "Reading %.2fkB of data from %s", inSize / 1.0E3, inFileName);
235  } else {
236  log_info(&state, 1, "Reading %ld bytes of data from %s", inSize, inFileName);
237  }
238 
239  free(inFileName);
240 
241  while (!(feof(inFile))) {
242  // Read message from data file
243  msg_t mtmp = {0};
244  if (!mp_readMessage(fileno(inFile), &mtmp)) {
245  if (mtmp.data.value == 0xAA || mtmp.data.value == 0xEE) {
246  log_error(&state,
247  "Error reading messages from file (Code: 0x%52x)\n",
248  (uint8_t)mtmp.data.value);
249  }
250  if (mtmp.data.value == 0xFD) {
251  // No more data, exit cleanly
252  log_info(&state, 1, "End of file reached");
253  }
254  break;
255  }
256  if (mtmp.source == source) {
257  bool writeMsg = true;
258  if (typeCount > 0) { writeMsg = type[mtmp.type]; }
259 
260  if (writeMsg) {
261  if (raw) {
262  if (!mp_writeData(fileno(outFile), &mtmp)) {
263  log_error(&state, "Unable to write output: %s",
264  strerror(errno));
265  fclose(inFile);
266  fclose(outFile);
267  destroy_program_state(&state);
268  return -1;
269  }
270  } else {
271  if (!mp_writeMessage(fileno(outFile), &mtmp)) {
272  log_error(&state, "Unable to write output: %s",
273  strerror(errno));
274  fclose(inFile);
275  fclose(outFile);
276  destroy_program_state(&state);
277  return -1;
278  }
279  }
280  msgCount++;
281  }
282  }
283  msg_destroy(&mtmp);
284  inPos = ftell(inFile);
285  if (((((1.0 * inPos) / inSize) * 100) - progress) >= 5) {
286  progress = (((1.0 * inPos) / inSize) * 100);
287  log_info(&state, 2, "Progress: %d%% (%ld / %ld)", progress, inPos, inSize);
288  }
289  }
290  fclose(inFile);
291  fclose(outFile);
292 
293  log_info(&state, 1, "%d messages processed", msgCount);
294  destroy_program_state(&state);
295  return 0;
296 }
int main(int argc, char *argv[])
Definition: ExtractSource.c:47
int msgCount[PGN_MAX]
Definition: N2KClassify.c:51
void msg_destroy(msg_t *msg)
Destroy a message.
Definition: messages.c:349
bool mp_readMessage(int handle, msg_t *out)
Static wrapper around mp_readMessage_buf.
Definition: MPSerial.c:66
bool mp_writeData(int handle, const msg_t *out)
Send message data (only!) to attached device.
Definition: MPSerial.c:397
bool mp_writeMessage(int handle, const msg_t *out)
Send message to attached device.
Definition: MPSerial.c:382
void destroy_program_state(program_state *s)
Cleanly destroy program state.
Definition: logging.c:207
void log_info(const program_state *s, const int level, const char *format,...)
Output formatted information message at a given level.
Definition: logging.c:125
void log_warning(const program_state *s, const char *format,...)
Output formatted warning message.
Definition: logging.c:86
void log_error(const program_state *s, const char *format,...)
Output formatted error message.
Definition: logging.c:47
Queuable message.
Definition: messages.h:71
msg_data_t data
Embedded data.
Definition: messages.h:76
uint8_t type
Message type. Common types to be documented.
Definition: messages.h:73
uint8_t source
Maps to a specific sensor unit or data source.
Definition: messages.h:72
Program state and logging information.
Definition: logging.h:40
int verbose
Current log verbosity (console output)
Definition: logging.h:43
bool started
Indicates startup completed.
Definition: logging.h:41
float value
Generic numerical data.
Definition: messages.h:46
#define GIT_VERSION_STRING
Git version description.
Definition: version.h.in:13