diff --git a/CHANGES.md b/CHANGES.md index 122d3a1..fad173f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,10 +9,11 @@ This is a snapshot of ongoing development towards version of 1.5. Some features - Time slots for beaconing. -- V20 configuration item for listing stations known not to understaand AX.25 v2.2. This will speed up connection by going right to SABM and not trying SABME first and failing. - - Documentation updates describing cheap SDR frequency inaccuracy and how to compensate for it. +- Allow single log file with fixed name rather than starting a new one each day. + + ### Bugs Fixed: ### - PACLEN configuration item no longer restricts length of received frames. @@ -31,7 +32,7 @@ This is a snapshot of ongoing development towards version of 1.5. Some features - decode_aprs utility can now accept KISS frames and AX.25 frames as series of two digit hexadecimal numbers. -- New configuration option, V20, for listing stations known to not understand AX.25 v2.2. +- New configuration option, V20, for listing stations known to not understand AX.25 v2.2. This will speed up connection by going right to SABM and not trying SABME first and failing. ### Bugs Fixed: ### diff --git a/config.c b/config.c index a076d62..d6b735d 100644 --- a/config.c +++ b/config.c @@ -877,7 +877,8 @@ void config_init (char *fname, struct audio_s *p_audio_config, strlcpy (p_misc_config->gpsnmea_port, "", sizeof(p_misc_config->gpsnmea_port)); strlcpy (p_misc_config->waypoint_port, "", sizeof(p_misc_config->waypoint_port)); - strlcpy (p_misc_config->logdir, "", sizeof(p_misc_config->logdir)); + p_misc_config->log_daily_names = 0; + strlcpy (p_misc_config->log_path, "", sizeof(p_misc_config->log_path)); /* connected mode. */ @@ -4283,7 +4284,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, } /* - * LOGDIR - Directory name for storing log files. Use "." for current working directory. + * LOGDIR - Directory name for automatically named daily log files. Use "." for current working directory. */ else if (strcasecmp(t, "logdir") == 0) { t = split(NULL,0); @@ -4293,7 +4294,12 @@ void config_init (char *fname, struct audio_s *p_audio_config, continue; } else { - strlcpy (p_misc_config->logdir, t, sizeof(p_misc_config->logdir)); + if (strlen(p_misc_config->log_path) > 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file: LOGDIR on line %d is replacing an earlier LOGDIR or LOGFILE.\n", line); + } + p_misc_config->log_daily_names = 1; + strlcpy (p_misc_config->log_path, t, sizeof(p_misc_config->log_path)); } t = split(NULL,0); if (t != NULL) { @@ -4302,6 +4308,31 @@ void config_init (char *fname, struct audio_s *p_audio_config, } } +/* + * LOGFILE - Log file name, including any directory part. + */ + else if (strcasecmp(t, "logfile") == 0) { + t = split(NULL,0); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file: Missing file name for LOGFILE on line %d.\n", line); + continue; + } + else { + if (strlen(p_misc_config->log_path) > 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file: LOGFILE on line %d is replacing an earlier LOGDIR or LOGFILE.\n", line); + } + p_misc_config->log_daily_names = 0; + strlcpy (p_misc_config->log_path, t, sizeof(p_misc_config->log_path)); + } + t = split(NULL,0); + if (t != NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file: LOGFILE on line %d should have file name and nothing more.\n", line); + } + } + /* * BEACON channel delay every message * diff --git a/config.h b/config.h index 71f8d14..675d292 100644 --- a/config.h +++ b/config.h @@ -77,7 +77,9 @@ struct misc_config_s { #define WPT_FORMAT_KENWOOD 0x08 /* K $PKWDWPL */ - char logdir[80]; /* Directory for saving activity logs. */ + int log_daily_names; /* True to generate new log file each day. */ + + char log_path[80]; /* Either directory or full file name depending on above. */ int sb_configured; /* TRUE if SmartBeaconing is configured. */ int sb_fast_speed; /* MPH */ diff --git a/direwolf.c b/direwolf.c index 62b3db8..4447cb3 100644 --- a/direwolf.c +++ b/direwolf.c @@ -194,7 +194,8 @@ int main (int argc, char *argv[]) struct igate_config_s igate_config; int r_opt = 0, n_opt = 0, b_opt = 0, B_opt = 0, D_opt = 0; /* Command line options. */ char P_opt[16]; - char l_opt[80]; + char l_opt_logdir[80]; + char L_opt_logfile[80]; char input_file[80]; // char timestamp[16]; @@ -215,7 +216,8 @@ int main (int argc, char *argv[]) int E_tx_opt = 0; /* "-E n" Error rate % for clobbering trasmit frames. */ int E_rx_opt = 0; /* "-E Rn" Error rate % for clobbering receive frames. */ - strlcpy(l_opt, "", sizeof(l_opt)); + strlcpy(l_opt_logdir, "", sizeof(l_opt_logdir)); + strlcpy(L_opt_logfile, "", sizeof(L_opt_logfile)); strlcpy(P_opt, "", sizeof(P_opt)); #if __WIN32__ @@ -356,7 +358,7 @@ int main (int argc, char *argv[]) /* ':' following option character means arg is required. */ - c = getopt_long(argc, argv, "P:B:D:c:pxr:b:n:d:q:t:Ul:Sa:E:", + c = getopt_long(argc, argv, "P:B:D:c:pxr:b:n:d:q:t:Ul:L:Sa:E:", long_options, &option_index); if (c == -1) break; @@ -532,11 +534,17 @@ int main (int argc, char *argv[]) exit (0); break; - case 'l': /* -l for log file directory name */ + case 'l': /* -l for log directory with daily files */ - strlcpy (l_opt, optarg, sizeof(l_opt)); + strlcpy (l_opt_logdir, optarg, sizeof(l_opt_logdir)); break; + case 'L': /* -L for log file name with full path */ + + strlcpy (L_opt_logfile, optarg, sizeof(L_opt_logfile)); + break; + + case 'S': /* Print symbol tables and exit. */ symbols_init (); @@ -674,8 +682,19 @@ int main (int argc, char *argv[]) audio_config.recv_error_rate = E_rx_opt; - if (strlen(l_opt) > 0) { - strlcpy (misc_config.logdir, l_opt, sizeof(misc_config.logdir)); + if (strlen(l_opt_logdir) > 0 && strlen(L_opt_logfile) > 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Logging options -l and -L can't be used together. Pick one or the other.\n"); + exit(1); + } + + if (strlen(L_opt_logfile) > 0) { + misc_config.log_daily_names = 0; + strlcpy (misc_config.log_path, L_opt_logfile, sizeof(misc_config.log_path)); + } + else if (strlen(l_opt_logdir) > 0) { + misc_config.log_daily_names = 1; + strlcpy (misc_config.log_path, l_opt_logdir, sizeof(misc_config.log_path)); } misc_config.enable_kiss_pt = enable_pseudo_terminal; @@ -794,7 +813,7 @@ int main (int argc, char *argv[]) * log the tracker beacon transmissions with fake channel 999. */ - log_init(misc_config.logdir); + log_init(misc_config.log_daily_names, misc_config.log_path); mheard_init (d_m_opt); beacon_init (&audio_config, &misc_config, &igate_config); diff --git a/doc/User-Guide.pdf b/doc/User-Guide.pdf index 6c636e7..ebfb20c 100644 Binary files a/doc/User-Guide.pdf and b/doc/User-Guide.pdf differ diff --git a/log.c b/log.c index f515e02..2894bc3 100644 --- a/log.c +++ b/log.c @@ -28,6 +28,13 @@ * unreadable, format, write separated properties into * CSV format for easy reading and later processing. * + * There are two alternatives here. + * + * -L logfile Specify full file path. + * + * -l logdir Daily names will be created here. + * + * Use one or the other but not both. * *------------------------------------------------------------------*/ @@ -91,26 +98,39 @@ static void quote_for_csv (char *out, size_t outsize, const char *in) { * * Purpose: Initialization at start of application. * - * Inputs: path - Path of log file directory. + * Inputs: daily_names - True if daily names should be generated. + * In this case path is a directory. + * When false, path would be the file name. + * + * path - Log file name or just directory. * Use "." for current directory. * Empty string disables feature. * - * Global Out: g_log_dir - Save directory here for later use. + * Global Out: g_daily_names - True if daily names should be generated. + * + * g_log_path - Save directory or full name here for later use. + * * g_log_fp - File pointer for writing. + * Note that file is kept open. + * We don't open/close for every new item. + * * g_open_fname - Name of currently open file. + * Applicable only when g_daily_names is true. * *------------------------------------------------------------------*/ -static char g_log_dir[80]; +static int g_daily_names; +static char g_log_path[80]; static FILE *g_log_fp; static char g_open_fname[20]; -void log_init (char *path) +void log_init (int daily_names, char *path) { struct stat st; - strlcpy (g_log_dir, "", sizeof(g_log_dir)); + g_daily_names = daily_names; + strlcpy (g_log_path, "", sizeof(g_log_path)); g_log_fp = NULL; strlcpy (g_open_fname, "", sizeof(g_open_fname)); @@ -118,42 +138,57 @@ void log_init (char *path) return; } - if (stat(path,&st) == 0) { - // Exists, but is it a directory? - if (S_ISDIR(st.st_mode)) { - // Specified directory exists. - strlcpy (g_log_dir, path, sizeof(g_log_dir)); + if (g_daily_names) { + +// Original strategy. Automatic daily file names. + + if (stat(path,&st) == 0) { + // Exists, but is it a directory? + if (S_ISDIR(st.st_mode)) { + // Specified directory exists. + strlcpy (g_log_path, path, sizeof(g_log_path)); + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Log file location \"%s\" is not a directory.\n", path); + dw_printf ("Using current working directory \".\" instead.\n"); + strlcpy (g_log_path, ".", sizeof(g_log_path)); + } } else { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Log file location \"%s\" is not a directory.\n", path); - dw_printf ("Using current working directory \".\" instead.\n"); - strlcpy (g_log_dir, ".", sizeof(g_log_dir)); + // Doesn't exist. Try to create it. + // parent directory must exist. + // We don't create multiple levels like "mkdir -p" +#if __WIN32__ + if (_mkdir (path) == 0) { +#else + if (mkdir (path, 0777) == 0) { +#endif + // Success. + text_color_set(DW_COLOR_INFO); + dw_printf ("Log file location \"%s\" has been created.\n", path); + strlcpy (g_log_path, path, sizeof(g_log_path)); + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Failed to create log file location \"%s\".\n", path); + dw_printf ("%s\n", strerror(errno)); + dw_printf ("Using current working directory \".\" instead.\n"); + strlcpy (g_log_path, ".", sizeof(g_log_path)); + } } } else { - // Doesn't exist. Try to create it. - // parent directory must exist. - // We don't create multiple levels like "mkdir -p" -#if __WIN32__ - if (_mkdir (path) == 0) { -#else - if (mkdir (path, 0777) == 0) { -#endif - // Success. - text_color_set(DW_COLOR_INFO); - dw_printf ("Log file location \"%s\" has been created.\n", path); - strlcpy (g_log_dir, path, sizeof(g_log_dir)); - } - else { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Failed to create log file location \"%s\".\n", path); - dw_printf ("%s\n", strerror(errno)); - dw_printf ("Using current working directory \".\" instead.\n"); - strlcpy (g_log_dir, ".", sizeof(g_log_dir)); - } + +// Added in version 1.5. Single file. +// Typically logrotate would be used to keep size under control. + + text_color_set(DW_COLOR_INFO); + dw_printf ("Log file is \"%s\"\n", path); + strlcpy (g_log_path, path, sizeof(g_log_path)); } -} + +} /* end log_init */ @@ -177,71 +212,119 @@ void log_init (char *path) void log_write (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, retry_t retries) { - time_t now; // make 'now' a parameter so we can process historical data ??? - char fname[20]; + time_t now; struct tm tm; - if (strlen(g_log_dir) == 0) return; + if (strlen(g_log_path) == 0) return; - // Generate the file name from current date, UTC. - - now = time(NULL); + now = time(NULL); // Get current time. (void)gmtime_r (&now, &tm); - // Microsoft doesn't recognize %F as equivalent to %Y-%m-%d - strftime (fname, sizeof(fname), "%Y-%m-%d.log", &tm); + if (g_daily_names) { - // Close current file if name has changed +// Original strategy. Automatic daily file names. - if (g_log_fp != NULL && strcmp(fname, g_open_fname) != 0) { - log_term (); - } + char fname[20]; - // Open for append if not already open. + // Generate the file name from current date, UTC. + // Why UTC rather than local time? I don't recall the reasoning. + // It's been there a few years and no on complained so leave it alone for now. - if (g_log_fp == NULL) { - char full_path[120]; - struct stat st; - int already_there; + // Microsoft doesn't recognize %F as equivalent to %Y-%m-%d - strlcpy (full_path, g_log_dir, sizeof(full_path)); + strftime (fname, sizeof(fname), "%Y-%m-%d.log", &tm); + + // Close current file if name has changed + + if (g_log_fp != NULL && strcmp(fname, g_open_fname) != 0) { + log_term (); + } + + // Open for append if not already open. + + if (g_log_fp == NULL) { + char full_path[120]; + struct stat st; + int already_there; + + strlcpy (full_path, g_log_path, sizeof(full_path)); #if __WIN32__ - strlcat (full_path, "\\", sizeof(full_path)); + strlcat (full_path, "\\", sizeof(full_path)); #else - strlcat (full_path, "/", sizeof(full_path)); + strlcat (full_path, "/", sizeof(full_path)); #endif - strlcat (full_path, fname, sizeof(full_path)); + strlcat (full_path, fname, sizeof(full_path)); - // See if it already exists. - // This is used later to write a header if it did not exist already. + // See if file already exists and not empty. + // This is used later to write a header if it did not exist already. - already_there = stat(full_path,&st) == 0; + already_there = (stat(full_path,&st) == 0) && (st.st_size > 0); - text_color_set(DW_COLOR_INFO); - dw_printf("Opening log file \"%s\".\n", fname); + text_color_set(DW_COLOR_INFO); + dw_printf("Opening log file \"%s\".\n", fname); - g_log_fp = fopen (full_path, "a"); + g_log_fp = fopen (full_path, "a"); - if (g_log_fp != NULL) { - strlcpy (g_open_fname, fname, sizeof(g_open_fname)); - } - else { - text_color_set(DW_COLOR_ERROR); - dw_printf("Can't open log file \"%s\" for write.\n", full_path); - dw_printf ("%s\n", strerror(errno)); - strlcpy (g_open_fname, "", sizeof(g_open_fname)); - return; - } + if (g_log_fp != NULL) { + strlcpy (g_open_fname, fname, sizeof(g_open_fname)); + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf("Can't open log file \"%s\" for write.\n", full_path); + dw_printf ("%s\n", strerror(errno)); + strlcpy (g_open_fname, "", sizeof(g_open_fname)); + return; + } - // Write a header suitable for importing into a spreadsheet - // only if this will be the first line. + // Write a header suitable for importing into a spreadsheet + // only if this will be the first line. - if ( ! already_there) { - fprintf (g_log_fp, "chan,utime,isotime,source,heard,level,error,dti,name,symbol,latitude,longitude,speed,course,altitude,frequency,offset,tone,system,status,telemetry,comment\n"); + if ( ! already_there) { + fprintf (g_log_fp, "chan,utime,isotime,source,heard,level,error,dti,name,symbol,latitude,longitude,speed,course,altitude,frequency,offset,tone,system,status,telemetry,comment\n"); + } } } + else { + +// Added in version 1.5. Single file. + + // Open for append if not already open. + + if (g_log_fp == NULL) { + struct stat st; + int already_there; + + // See if file already exists and not empty. + // This is used later to write a header if it did not exist already. + + already_there = (stat(g_log_path,&st) == 0) && (st.st_size > 0); + + text_color_set(DW_COLOR_INFO); + dw_printf("Opening log file \"%s\"\n", g_log_path); + + g_log_fp = fopen (g_log_path, "a"); + + if (g_log_fp == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf("Can't open log file \"%s\" for write.\n", g_log_path); + dw_printf ("%s\n", strerror(errno)); + strlcpy (g_log_path, "", sizeof(g_log_path)); + return; + } + + // Write a header suitable for importing into a spreadsheet + // only if this will be the first line. + + if ( ! already_there) { + fprintf (g_log_fp, "chan,utime,isotime,source,heard,level,error,dti,name,symbol,latitude,longitude,speed,course,altitude,frequency,offset,tone,system,status,telemetry,comment\n"); + } + } + } + + +// Add line to file if it is now open. if (g_log_fp != NULL) { @@ -439,7 +522,13 @@ void log_term (void) if (g_log_fp != NULL) { text_color_set(DW_COLOR_INFO); - dw_printf("Closing log file \"%s\".\n", g_open_fname); + + if (g_daily_names) { + dw_printf("Closing log file \"%s\".\n", g_open_fname); + } + else { + dw_printf("Closing log file \"%s\".\n", g_log_path); + } fclose (g_log_fp); diff --git a/log.h b/log.h index fc6ca44..3afb6b1 100644 --- a/log.h +++ b/log.h @@ -10,7 +10,7 @@ -void log_init (char *path); +void log_init (int daily_names, char *path); void log_write (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, retry_t retries); diff --git a/man1/direwolf.1 b/man1/direwolf.1 index d137221..fe45702 100644 --- a/man1/direwolf.1 +++ b/man1/direwolf.1 @@ -27,8 +27,12 @@ RMS Express, and many others. Read configuration file from specified location rather than the default locations. .TP -.BI "-l " "dir" -Generate log files in specified directory. Use "." for current directory. +.BI "-l " "logdir" +Generate daily log files in specified directory. Use "." for current directory. + +.TP +.BI "-L " "logfile" +Generate single log file with fixed name. .TP .BI "-r " "n"