Avoiding a newline when using fprintf with time.h - c

I keep getting a new line printed after the current time is written to a log.txt file. In this example I want the time and logMessage printed on the same line. Could you point me in the right direction as to what I'm missing here?
The logWrite function is called from the main function:
strcpy(logMessage, "**********RESTART**********");
logWrite(logMessagePtr);
The logWrite function is:
void logWrite(char * logMessagePtr)
{
/* Initialise time variables for log file. */
int hours, minutes, seconds, day, month, year;
// time_t is an arithmetic time type
time_t now;
// Update current time using time(&now);
time(&now);
FILE * logFilePtr;
logFilePtr = fopen ("C:\\log.txt", "a");
if (logFilePtr != NULL)
{
fprintf(logFilePtr, ("%s", ctime(&now)));
fprintf(logFilePtr, "%s", logMessagePtr);
fprintf(logFilePtr,"\n");
fclose(logFilePtr);
printf("Log file found\n"); //debug
}
else
{
perror("log.txt");
}
}
The log.txt file is sucessfully (created) appended to but I consistently get a new line feed after the time is written to the file.
When I run the program twice in a row for example, the log.txt reads:
Sat May 22 09:16:47 2021
**********RESTART**********
Sat May 22 09:16:48 2021
**********RESTART**********
What I want is:
Sat May 22 09:16:47 2021 **********RESTART**********
Sat May 22 09:16:48 2021 **********RESTART**********
Info on time.h

ctime(3) - Linux man page says:
The call ctime(t) is equivalent to asctime(localtime(t)). It converts the calendar time t into a null-terminated string of the form
"Wed Jun 30 21:49:08 1993\n"
It looks like adding newline character is included in the specification of ctime(). You should use strftime() to manually format the date and time without newline.
char date[128];
strftime(date, sizeof(date), "%a %b %d %T %Y", localtime(&now));
fprintf(logFilePtr, "%s", date);

The ctime function always adds '\n' to the end of string. There are many different ways to solve the problem, but the easiest way is to remove this symbol. This can be done, for example, like this (based on the fact that the ctime (see man) returns char *, not const char *):
#include <string.h>
...
char *ctime_line = ctime (&now);
ctime_line[strlen (ctime_line) - 1] = '\0';
and then print this line:
fprintf (logFilePtr, "%s", ctime_line);
P.S. I do not recommend using a record like this, since the string AAA may contain, for example, the % character, which will be interpreted as a special character.
// bad
fprintf (logFilePtr, logMessagePtr);
// good
fprintf (logFilePtr, "%s", logMessagePtr);
P.P.S. The following three lines
fprintf (logFilePtr, "%s", ctime (&now));
fprintf (logFilePtr, "%s", logMessagePtr);
fprintf (logFilePtr, "\n");
can be combined into one
fprintf (logFilePtr, "%s %s\n", ctime (&now), logMessagePtr);

As per documentation of ctime, the string has a newline embedded. You need to remove it from consideration for printing.
Since the string returned by ctime has a specified format of constant width, this should accomplish that:
fprintf(logFilePtr, "%.24s ", ctime(&now));
(edit: This is assuming you wish to use ctime in the first place, as you do in the question. There are other, potentially better, options, such as strftime.)

Related

String buffer overflow produced by asctime() in C

I want to print out the time in C using asctime(), but when the text gets printed out random charaters are appended after timeString. Also, the text syslog() prints out in the log file differs from the text printed in the shell by printf(). Under the code I've provided the exact output from both outputs. How do I get rid of this behavior? The code is running on a RaspberryPi and I'm logged in via the default macOS terminal.
time_t rawTime;
time(&rawTime);
struct tm timeInfo = *gmtime(&rawTime);
// ...
char *log;
char *timeString = strdup(asctime(&timeInfo));
asprintf(&log, "UTC: %s %.*s Last status: %s. New status: %s.",
timeString, 5, " ", "Hello", "World");
openlog("httpd-status-notifier", LOG_PID, LOG_USER);
syslog(logLevel, "%s", log);
printf("%s\n", log);
// ...
Syslog:
Dec 22 17:18:17 rasp httpd-status-notifier[25458]: UTC: Sun Dec 22 17:18:17 2019#012 Last status: Hello. New status: World.
(Here syslog produces #012)
Shell (printf):
UTC: Sun Dec 22 17:18:17 2019
Last status: Hello. New status: World.
(Here printf produces a new line charater)
Btw., yes I did notice that syslog already logs the date.
The problem is that asctime() produces a new line character and this gets represented as the octal ASCII value 012 in the log file as pointed out by #mangusta in the comment section. So trimming the string solves the problem.

Repetition of the last string when reading from .txt file C

This is the code, it reads from a file and then it prints what is written in that file.
I don't know why but the last string of the file is readed twice.
Code
FILE* src = fopen(name_email_src, "r");
if (src == NULL)
{
printf("ERROR source file not found");
}
while(fgets(buff_src, sizeof(buff_src), src) != NULL)
{
fputs(buff_src, stdout);
}
fclose(src);
printf("%s", buff_src);
This is the output:
Date: Tue, 07 Feb 2017 21:32:46 +0100 (CET)
From: Rental <rental#house-rental.com>
To: me <me#upf.edu>
Message-ID: message2
Subject: Paga el alquiler ya.
Dear customer,
you are late in your payment, please pay or LEAVE!
Sincerely yours,
House rental
House rental
What can I do to solve this problem? Thank you.
printf("%s", buff_src); is printing the last line.
You have an extra call to printf() after your while loop:
while(fgets(buff_src, sizeof(buff_src), src) != NULL)
{
fputs(buff_src, stdout); // prints each line
}
fclose(src);
printf("%s", buff_src); // prints buff_src which still holds the last line
Just remove this unnecessary call to printf() and it will work as you expect.
fgets() reads line by line from the file. From the man page of fgets()
If a newline is read, it is stored into the buffer.
fgets(buff_src, sizeof(buff_src), src) /* read upto New line or EOF from src and store into buff_src */
when loop fails whatever buff_src contains that's been printed using last printf statement.

fopen a file and skip character

Is there a clean way to open a file like this without system calls:
ID*_LogConfig.csv
I tried the following but it didn't worked.
/*Read setup file*/
getcwd(cwd, sizeof(cwd));
snprintf(source, sizeof(source),"%s/ID%*d_LogConfig.csv",cwd);
if( NULL == (input = fopen(source,"r")))
{
snprintf(errbuffer,sizeof(errbuffer), "Could not open file %s - check existence/rights\n", source);
exitHandler(1, errbuffer);
}
It outputs:
/mnt/dataflash/canfilter.d/ID*_LogConfig.csv not found
But with e.g. cat /mnt/dataflash/canfilter.d/ID*_LogConfig.csv it shows the file content.
My compromise solution would be a system call ll ID*_LogConfig.csv and using the output as filename.
This line
snprintf(source, sizeof(source),"%s/ID%*d_LogConfig.csv",cwd);
likely does not produce what you think it does.
The %*d portion is an format specifier with a field-width, per the POSIX printf() documentation
A field width, or precision, or both, may be indicated by an
( '*' ). In this case an argument of type int supplies the
field width or precision. Applications shall ensure that arguments
specifying field width, or precision, or both appear in that order
before the argument, if any, to be converted. A negative field width
is taken as a '-' flag followed by a positive field width. A negative
precision is taken as if the precision were omitted. In format strings
containing the "%n$" form of a conversion specification, a field width
or precision may be indicated by the sequence "*m$", where m is a
decimal integer in the range [1,{NL_ARGMAX}] giving the position in
the argument list (after the format argument) of an integer argument
containing the field width or precision, for example:
printf("%1$d:%2$.*3$d:%4$.*3$d\n", hour, min, precision, sec);
So, this line
snprintf(source, sizeof(source),"%s/ID%*d_LogConfig.csv",cwd);
expects two more integer arguments to be passed. Since you don't pass them, you invoke undefined behavior.
See this answer: https://stackoverflow.com/a/19897395/4756299
Is there a clean way to open a file like this without system calls
No.
fopen() makes use of a system call. You cannot 'open' a file without a system call.
If you're referring to the system(3) function, then you're probably in for some pain - it's best to avoid it if possible, from a performance, reliability and security point of view.
If you want to open 'all files that match the pattern', then look at glob(3), which is likely what your shell is using to handle such wildcards.
You will need to iterate over each of the resulting paths, calling fopen(), fread() and fclose() on each.
Example usage of glob(3):
#include <stdio.h>
#include <glob.h>
void main(void) {
int ret;
int i;
glob_t glob_info;
ret = glob("*.csv", 0, NULL, &glob_info);
if (ret != 0)
{
fprintf(stderr, "glob() failed: %d\n", ret);
return;
}
for (i = 0; i < glob_info.gl_pathc; i++) {
printf("%d: %s\n", i, glob_info.gl_pathv[i]);
}
globfree(&glob_info);
}
It is not really a good idea to open lots of files and treat the stream as a single 'thing' (as you are doing with your cat example).
As #Andrew has pointed out, you must be careful with your use of printf() format strings...
You have provided the following: %s/ID%*d_LogConfig.csv. A % denotes the beginning of a format specifier, you have thus given the following:
%s - a char * (string) parameter follows
%*d - similar to %d, but the * means that the precision is provided as an int parameter, followed by the number itself.
For example:
printf(">%s< >%*d<\n", "Hello", 5, 3);
Will output: (note the 5 characters that the %d outputs)
>Hello< > 3<
If you are after a *, then just put a * in the format string.
If you are after a %, then you need to escape the % but putting %% in the format string.
Ok I solved the "problem" by using the following:
(Processing the output of ls -t and use the newest file as config-file)
/*Search for config-file*/
FILE *file_config;
file_config = popen("ls -t ID*_LogConfig.csv","r");
if (file_config == NULL) {
exitHandler(1, "Error opening date pipe.");
}
fgets(configfile, sizeof(configfile), file_config);
if (strlen(configfile) > 0)
configfile[strlen(configfile)-1] = '\0';
else {
exitHandler(1, "Could not find a ID*_LogConfig.csv\n");
}
getcwd(cwd, sizeof(cwd));
snprintf(source, sizeof(source),"%s/%s",cwd,configfile);
/*Read setup file*/
if( NULL == (input = fopen(source,"r")))
{
snprintf(errbuffer,sizeof(errbuffer), "Could not open file |%s| - check existence/rights\n", source);
exitHandler(1, errbuffer);
}
It seems that this is the only simple way.
Thanks to all.

Formatting date/time in the same way as "ls -l" command

I am implementing a version of ls -la command in OS X. I happened to notice that ls command lists years for some files and times for others as shown below:
-rwxr-xr-x 1 Jenna 197609 3584 Mar 13 2015 untitled1.exe*
drwxr-xr-x 1 Jenna 197609 0 Mar 2 07:48 Videos/
After some research, I found out that files older than 6 months gets a year while those that are not get the actual time. How do I get the date and times of these files to correctly display either the year or time ? Currently I have the following code which always displays the time:
int main()
{
struct stat *fileInfo;
char dir[] = "~/Desktop/file.txt";
stat(dir, &fileInfo);
printf("%.12s ", 4+ctime(&fileInfo->st_mtimespec));
return 0;
}
strftime is your friend here. Formatting it like ls is not that straight forward. The two formats that ls in coreutils is using are
"%b %e %Y"
"%b %e %H:%M"
Heres one paragraph from the docs of the ls in coreutils
/* TRANSLATORS: ls output needs to be aligned for ease of reading,
so be wary of using variable width fields from the locale.
Note %b is handled specially by ls and aligned correctly.
Note also that specifying a width as in %5b is erroneous as strftime
will count bytes rather than characters in multibyte locales. */
N_("%b %e %H:%M")
They're taking into consideration things like Locale ie multibyte characters etc. They also discuss things like when a file is older than N months they change how it's displayed etc. You can find a lot of information here...
http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/ls.c
For your needs something simpler might be fine, heres some code that might point you in the correct direction. You can change the format to whatever you want by changing the third argument to strftime.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
int main(void) {
time_t t;
srand((unsigned) time(&t));
size_t i = 0;
struct timespec ttime;
while(i < 0xffffffff) {\
ttime.tv_sec = i;
i += 3600;
struct tm time_struct;
localtime_r(&ttime.tv_sec, &time_struct);
char time_str[1024];
if(rand() > RAND_MAX/2) {
strftime(time_str, sizeof(time_str), "a == %b %e %Y", &time_struct);
}
else {
strftime(time_str, sizeof(time_str), "b == %b %e %H:%M", &time_struct);
}
printf("%s\n", time_str);
}
exit(0);
}
Try it and see what it does. From the man page, tweaking it will get you really close to what you need.
size_t strftime(char *restrict s, size_t maxsize, const char *restrict format, const struct tm *restrict timeptr);
The strftime() function formats the information from timeptr into the
buffer s, according to the string pointed to by format.
If I recall correctly, the determining factor used by ls -l to output a date/time or output a date/year is whether the mtime year returned by stat matches the current year. If the file mtime year matches the current year, then date/time information is provided, if the mtime year is not the current year, then date/year information is provided.
In order to make the comparison, you need to fill a struct tm for both the file mtime and now. The following example uses localtime_r to fill each struct tm, makes the comparison, and then outputs the file details accordingly.
(the code will read/compare mtimess and output the time format for all filenames provided as arguments. (a placeholder string is used to provide basic formatting for the file permissions, number, user, group, size, etc.)
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#define MAXC 64
void dump_tm (struct tm *t);
int main(int argc, char **argv)
{
int i = 0;
char time_str[MAXC] = "";
time_t now = time (NULL);
struct stat sb;
struct tm tmfile, tmnow;
if (argc < 2) { /* validate sufficient arguments provided */
fprintf (stderr, "error: insufficient input, usage: %s <pathname>\n",
argv[0]);
return 1;
}
for (i = 1; i < argc; i++) { /* for each file given as arg */
if (stat(argv[i], &sb) == -1) { /* validate stat of file */
perror("stat");
return 1;
}
localtime_r (&sb.st_mtime, &tmfile); /* get struct tm for file */
localtime_r (&now, &tmnow); /* and now */
if (tmfile.tm_year == tmnow.tm_year) { /* compare year values */
strftime (time_str, sizeof (time_str), "%b %e %H:%M",
&tmfile); /* if year is current output date/time */
printf ("permission 1 user group 12345 %s %s\n",
time_str, argv[i]);
}
else { /* if year is not current, output time/year */
strftime (time_str, sizeof (time_str), "%b %e %Y",
&tmfile);
printf ("permission 1 user group 12345 %s %s\n",
time_str, argv[i]);
}
}
return 0;
}
Example ls -l Output
$ ls -l tmp.c ls_time_date_fmt.c
-rw-r--r-- 1 david david 1312 Apr 6 03:15 ls_time_date_fmt.c
-rw-r--r-- 1 david david 2615 May 23 2014 tmp.c
Program Use/Output
$ ./bin/ls_time_date_fmt ls_time_date_fmt.c tmp.c
permission 1 user group 12345 Apr 6 03:15 ls_time_date_fmt.c
permission 1 user group 12345 May 23 2014 tmp.c
Let me know if you have any questions.

sscanf: get first and last token in a string

Is it possible by using sscanf to get the first token then skip some tokens and then get the last one?
For example, the output of /bin/ps -fu1000
cm 2249 1548 0 0:00.00 ttys001 0:00.01 man sscanf
I have tried:
sscanf(line, "%s %[^\n]", user, cmd);
The result should be:
user = "cm";
cmd = "man sscanf":
But it does not work.
Yes, but it's ugly and can't be error checked properly.
/* assuming `user` and `cmd` are character arrays defined with 42 bytes */
if (sscanf(line, "%41s%*s%*s%*s%*s%*s%*s %41[^\n]", user, cmd) != 2) {
/* handle error */
} else {
/* hopefully ok */
}
You can replace some of the %*s with %*d. The * means that the item is parsed but not assigned anywhere (it is ignored).
In the statement, there are 6 ignored items corresponding to the items between "cm" and "man sscanf" in your example.
Also note I limited the input to 41 characters in the scanf itself. Make sure you do not write outside the objects.
EDIT: I added a space before the last conversion because, unlike %s or %d conversions, the %[ conversion does not skip leading whitespace.
Perhaps you'd better look at strtok

Resources