2

I've written a user space program to read from a kernel device line by line, somehow, the data is always overriden with each read. Can you please tell me how to fix my code?

Here is the code:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <ctype.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/mman.h>
 

#define BUFFER_LENGTH 256

int main()

{

  int  ret,fd;
  char buffer[BUFFER_LENGTH];
  FILE * fPtr;
  unsigned int i=0;

  if((fd = open("/dev/show_log_device_dev", O_RDWR)) == -1){
    perror("Failed to open the file");
  }
  //printf("/dev/show_log_device_dev opened.\n");
  //fflush(stdout);

  fPtr = fopen("log.txt", "w+");

  int bytesRead = 0;

  while (bytesRead < sizeof(buffer)) {
    int ret = read(fd, buffer + bytesRead, sizeof(buffer) - bytesRead);
    if (ret == 0)
        break;
    if (ret == -1) {  
      perror("Failed to read the message from the device.");
      return errno;
      /* Handle error */
    }
    bytesRead += ret; 
    printf("read from /dev/show_log_device_dev. %s\n",buffer);

  }
  if(lseek(fPtr,0,SEEK_SET)!=0) {
    fprintf(fPtr,"%s",buffer);
  }

  fclose(fPtr);
}

I would like the output file "log.txt" to contain all the lines written to buffer with each read line after line sith skip-line between lines. Please show me the proper way to do it inctead of the fprintf attempt I've written above.

Thank you.

Tsyvarev
  • 60,011
  • 17
  • 110
  • 153
QueryMan
  • 25
  • 8
  • 1
    Why the `lseek()`? And are you ever making sure `buffer` is a proper 0 terminated string? – Shawn Nov 19 '21 at 12:27
  • I didn't verfiy that buffer is 0 terminated... Also, I used lseek to find the end of the file and write to it, at least that's what I though this from of writing does. I need the proper way to write into that file line by line, i.e. write `buffer` into the file at the first available spot in the following line. I have no idea how to do that. – QueryMan Nov 19 '21 at 12:37
  • 1
    Try following the solution here . – Mensch677 Nov 19 '21 at 12:39
  • 1
    You're seeking to the *beginning* of the file... (Also `lseek()` is for file descriptors, not `FILE` pointers. How does that compile?) – Shawn Nov 19 '21 at 12:40
  • @Mensch677 The sulotion doesn't help becuase the situation is too different, I'm not trying to read from a file, but from a device. I want to write to a file, and make it line by line. – QueryMan Nov 19 '21 at 13:12
  • @Shawn Then what is the proper way to write the lines so they would appear one after the other in log.txt? (Assume the line is written correctly into `buffer`) – QueryMan Nov 19 '21 at 13:13
  • 1
    User-space code looks reasonable. except `lseek` call for `FILE*`. "the situation is too different, I'm not trying to read from a file, but from a device." - Reading from the device shouldn't differ from reading from a text file... unless your device is very special. In the last case you need to provide the code for your device, otherwise we cannot help you. – Tsyvarev Nov 19 '21 at 13:21
  • when compling, always enable the warnings, then fix those warnings. (for gcc, at a minimum use: `gcc -c -Wall -Wextra -Wconversion -pedantic -std=gnu11` ) – user3629249 Nov 20 '21 at 01:18
  • regarding: `if(lseek(fPtr,0,SEEK_SET)!=0) {` This places the file pointer at the beginning of the file, Not what you want to do. also, `lseek()` returns a `off_t` not a `int` – user3629249 Nov 20 '21 at 01:22
  • regarding: `printf("read from /dev/show_log_device_dev. %s\n",buffer);` need to NUL terminate the 'buffer[]' data before trying to print it. Suggest inserting, just before the above line, `buffer[ bytesread ] = '\0';` – user3629249 Nov 20 '21 at 01:26
  • regarding: `int ret = read(fd, buffer + bytesRead, sizeof(buffer) - bytesRead);` The function: `read()` returns a `ssize_t`, not a `int` – user3629249 Nov 20 '21 at 01:28
  • at some point, the value calculated by `read(fd, buffer + bytesRead, sizeof(buffer) - bytesRead);` will exceed the length of the buffer and/or the calculation: `sizeof(buffer) - bytesRead` will return a negative number – user3629249 Nov 20 '21 at 01:31

1 Answers1

0
char buffer[BUFFER_LENGTH];
while()
{...read(fd, buffer + bytesRead, sizeof(buffer) - bytesRead);}

This line is a major problem. Basically this attempts to store the entire file in to buffer. But buffer has a small size (256 bytes).

If file is too large, read will store 256 in to buffer in the first pass. In the second pass in the loop, read will attempt to add another 256 bytes to the same buffer.

Instead you have to read small chunks and write back within the loop.

fPtr = fopen("log.txt", "w+");

This will open the log file in read/write mode, but you only need to write to it. Since this is a log file, consider using "a" option to append to the log file.

fprintf(fout, "%s", buffer);

When you read the buffer it may not be null terminated. Try fwrite instead, or make sure buffer is null terminated before using fprintf

#define BUFFER_LENGTH 256
int main()
{
    int fd = open("/dev/show_log_device_dev", O_RDONLY);
    if(fd == -1)
    { perror("open failed"); return 0; }

    FILE* fout = fopen("log.txt", "w"); //or "a" to create new file
    if(fout == NULL)
    { close(fd); perror("fopen failed, log.txt is busy!"); return 0; }

    while (1) 
    {
        char buffer[BUFFER_LENGTH];
        int ret = read(fd, buffer, sizeof(buffer));
        if (ret == 0)
            break;
        if (ret == -1) 
        {
            perror("Failed to read the message from the device.");
            return errno;
        }
        fwrite(buffer, 1, ret, fout);
        //fprintf(fout, "%s", buffer);
    }

    fclose(fout);
    close(fd);
    return 0;
}

Note, lseek(fPtr,0,SEEK_SET) is wrong and not necessary. This method is excepts a file descriptor obtained from open. But fPtr is FILE* handle obtained from fopen.

If possible, consider using standard c functions FILE*/fopen for both input and output. This way you will be able to use the same set of functions fopen,fread,fwrite,fclose,fseek,ftell. You only need one header file for this:

#include <stdio.h>

int main()
{
    FILE* fin = fopen("/dev/show_log_device_dev", "r");
    if (!fin)
    { perror("input error"); return 0; }

    FILE* fout = fopen("log.txt", "w"); //or "a" to append to log
    if (!fout) 
    {fclose(fin); perror("output error, log.txt is busy!"); return 0; }

    while (1)
    {
        char buffer[256];
        int ret = fread(buffer, 1, sizeof(buffer), fin);
        if (ret == 0)
            break;
        fwrite(buffer, 1, ret, fout);
    }

    fclose(fout);
    fclose(fin);
    return 0;
}
Barmak Shemirani
  • 30,904
  • 6
  • 40
  • 77
  • Thank you for the solution and the detailed explanation! – QueryMan Nov 19 '21 at 13:25
  • An answer should not merely refer to comments for information about why something is wrong. It should explain. Comments are ephemeral and may vanish at any moment. – Eric Postpischil Nov 19 '21 at 13:38
  • @EricPostpischil My answer had little to do with comments. I only backed up the `lseek` comments. A failed `lseek` attempt doesn't change the program's output. Anyway, updated the answer. – Barmak Shemirani Nov 19 '21 at 14:09
  • Updated, address `fopen` method for input. Changed output to `"w"`, you can change to `"a"` if append is needed. – Barmak Shemirani Nov 19 '21 at 18:38