I was trying to write a program that checks out if a location exists under the /home/user directory. To do that, I had to get the username with the whoami command and add the output of it to the buffer to use the locate command.
However, even though the snprintf read the whoami command, it didn't read the rest. I made a couple of searches and came to a result that NULL may not be terminated at the end of the string. Nevertheless, I couldn't find out how to terminate it manually. I am not sure what the problem is, so, here I am.
Here is the code for a better demonstration of my issue:
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
#include <string.h>
char *readFile(char *);
bool check();
bool check() {
char path[200] = { 0 };
snprintf(path, 200, "/home/%s/.example", readFile("whoami"));
char lll[300] = { 0 };
snprintf(lll, 300, "locate %s", path);
char *buffer = readFile(lll);
if (strcmp(buffer, path) == 0) {
return true;
} else {
return false;
}
}
char *readFile(char cmd[200]) {
char cmd1[99999] = { 0 };
system("touch cmd");
snprintf(cmd1, 99999, "%s >> cmd", cmd);
system(cmd1);
FILE *f = fopen("cmd", "rt");
assert(f);
fseek(f, 0, SEEK_END);
long length = ftell(f);
fseek(f, 0, SEEK_SET);
char *buffer = (char *)malloc(length + 1);
buffer[length] = '\0';
fread(buffer, 1, length, f);
fclose(f);
system("rm cmd");
return buffer;
}
int main() {
int x = check();
if (x == 1)
printf("There is a location like that");
else
printf("There isn't");
return 0;
}
The whoami command prints out the name of the current user, termintated by a newline. Reading back the file cmd into buffer will include that same newline character.
The path string will then be "/home/USERNAME\n/.example". Trying to execute
locate /home/USERNAME\n/.example will probably confuse the system function.
The solution should then be to strip away the last newline in readFile.
There are some problems in your readFile function:
the name is misleading, it should be runCommand or something equivalent.
you append the command output to the cmd file. This will cause unexpected results if the cmd file exists before you run your program. You should instead write:
snprintf(cmd1, 99999, "%s > cmd", cmd);
the command output probably has a trailing newline. You should trim the output.
Here is a modified version:
#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *readFile(const char *cmd) {
/* construct the command */
size_t size = 4 + strlen(cmd) + 1;
char *cmd1 = malloc(size);
assert(cmd1);
snprintf(cmd1, size, "%s > cmd", cmd);
/* run the command */
system(cmd1);
free(cmd1);
/* read the output file */
FILE *f = fopen("cmd", "rt");
assert(f);
fseek(f, 0, SEEK_END);
long length = ftell(f);
fseek(f, 0, SEEK_SET);
char *buffer = malloc(length + 1);
buffer[length] = '\0';
fread(buffer, 1, length, f);
fclose(f);
unlink("cmd");
/* trim trailing white space from the output */
while (length > 0 && isspace((unsigned char)buffer[length - 1])) {
buffer[--length] = '\0';
}
/* trim leading white space from the output */
size_t i = 0;
while (isspace((unsigned char)buffer[i]) {
i++;
}
if (i > 0) {
memmove(buffer, buffer + i, length - i + 1);
}
return buffer;
}
Since Linux has a shortcut for your home directory ("~"), the whole "whoami"/"getuid" mess can be avoided by just using:
char *buffer = readFile("locate ~/.example");
You will still potentially have to trim off trailing newline and/or CR characters, perhaps with:
strtok(buffer,"\r\n\t ");
Or better yet, as #chqrlie suggests below.
Related
I am writing a program in C. I use low level functions like open, read, close. I have a file descriptor, etc, but I don't know how to print only the first 2 lines from a file that has e.g. 30 lines of text. how to do it?
you need to read a file into a string, iterate through the string, concat any character into the string variable, define a int variable for lines count, when lines reaches 2, break the loop
here’s an example how you can do it
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *get_first_two_lines(char *file_name) {
FILE *file = fopen(file_name, "r");
fseek(file, 0, SEEK_END);
long size = ftell(file);
fseek(file, 0, SEEK_SET);
char *buffer = malloc(size);
fread(buffer, 1, size, file);
char *two_lines = calloc(1, sizeof(char));
unsigned int lines = 0;
for (int i=0;i<strlen(buffer);i++) {
if (lines == 2) break;
if (buffer[i] == '\n') {
if (lines < 1) {
two_lines = realloc(two_lines, (strlen(two_lines) + 2) * sizeof(char));
strcat(two_lines, (char []) {'\n', 0});
}
lines++;
continue;
}
two_lines = realloc(two_lines, (strlen(two_lines) + 2) * sizeof(char));
strcat(two_lines, (char []) {buffer[i], 0});
}
return two_lines;
}
int main(int argc, char *argv[]) {
char *first_two_lines = get_first_two_lines("file_name");
printf("%s", first_two_lines);
return 0;
}
char* freadline(FILE* fp){
fseek(fp, 0, SEEK_END);
int lSize = ftell(fp);
fseek(fp, 0, SEEK_SET);
char *buffer = malloc(lSize);
fread(buffer, 1, lSize, fp);
fgets(buffer, sizeof(lSize), fp);
return buffer;
}
but it doesn't read line by line any suggestions as to how this would be read line by line
There are couple solutions here.
The first is to get the size of the entire file using fseek and ftell fseek will allow you to go to the end of file and ftell will give you the current position which can be used as a size indicator. You can then allocate enough of a buffer to read the entire file then split them up into lines.
The other solution is to use a temporary buffer of 1000 or so like you're already doing, read a character at a time using fgetc in a loop and feed it into the temporary buffer until you hit a new line indicator , then use the strlen method to get the length and allocate a buffer of that size, copy the temporary buffer then return the allocated buffer.
There is also errors in your code as pointed out in the comments. You're discarding your allocated memory resulting in a leak. And your freadline doesn't actually read a line it just reads whatever size you're telling it to read.
the lines in the file could be of any length.
realloc() is a classic approach, but how about a simple, slow and plodding one:
Read once to find line length, seek, allocate, then read again to save the line.
#include <stdio.h>
char* freadline(FILE *fp) {
int length = 0;
long offset = ftell(fp);
if (offset == -1)
return NULL;
int scan_count = fscanf(fp, "%*[^\n]%n", &length); // Save scan length
if (scan_count == EOF)
return NULL;
if (fseek(fp, offset, SEEK_SET))
return NULL;
size_t n = length + 1u; // +1 for potential \n
char *buf = malloc(n + 1); // + 1 for \0
if (buf == NULL)
return NULL;
size_t len = fread(buf, 1, n, fp);
buf[len] = '\0';
return buf;
}
Test
#include <assert.h>
#include <stdlib.h>
int main() {
FILE *fp = fopen("tmp.txt", "w+");
assert(fp);
for (int i = 0; i < 10; i++) {
int l = i * 7;
for (int j = 0; j < l; j++) {
fputc(rand() % 26 + 'a', fp);
}
fputc('\n', fp);
}
rewind(fp);
char *s;
while ((s = freadline(fp)) != NULL) {
printf("<%s>", s);
free(s);
}
fclose(fp);
return 0;
}
Output
<
><lvqdyoq
><ykfdbxnqdquhyd
><jaeebzqmtblcabwgmscrn
><oiaftlfpcuqffaxozqegxmwgglkh
><vxtdhnzqankyprbwteazdafeqxtijjtkwea
><zqgmplohyxrutojvbzllqgjaidbtqibygdzcxkujvw
><ghwbmjjmbpksnzkgzgiluiggpkzwhaetclrcyxcsixsutjmrm
><vqlybsjnihnfqyfhyszwgpsvnhnngdnjzjypqcflnztrhcfgbkakzxam
><alsuauxxchqjxqaiddtjszgcbullyyjymytioyawpzshhfpqpsatddbcagjgobm
>
If you're ok targeting POSIX, it already has a function that does what you need: getline.
#include <stdio.h>
#include <stdlib.h>
FILE *fh = ...;
char *line = NULL;
size_t buf_size = 0;
while (1) {
ssize_t line_len = getline(&line, &buf_size, fh);
if (line_len == -1)
break;
// ...
}
free(line);
If not, getline can be implemented using using fgets and realloc in a loop. Just start with a arbitrarily-sized buffer.
I am new to C, this is my first project and have been teaching myself. Within my program, one of my functions needs to read a line from a file, and store it in a char array. When I trace the program with gdb the array (line[]) is simply zeros. This leads to my program returning the error "Error: a line in the asset file lacks a ':' separator\n"
Here is my code:
//return the line number (0 based) that the cmd is on, -1 if absent
int locateCmd(char cmd[]) {
int lineIndex = -1; //-1, because lineIndex is incramented before the posible return
char cmdTemp[10] = "\0";
//create a compareable cmd with correct cmd that has its remaining values zeroed out
char cmdCmp[10] = "\0";
memset(cmdCmp, 0, sizeof(cmdCmp));
for (int i = 0; i < strlen(cmd); i++) {
cmdCmp[i] = cmd[i];
}
FILE *file = fopen(ASSET_FILE, "r");
//loop until target line is reached
while (strcmp(cmdTemp, cmdCmp) != 0) {
//check if last line is read
if (lineIndex == lineCounter(file)-1) {
return -1;
}
memset(cmdTemp, 0, sizeof(cmdTemp));
char line[61];
fgets(line, 61, file);
//set cmdTemp to the command on current line
lineIndex++;
for (int i = 0; line[i] != ':'; i++) {
cmdTemp[i] = line[i];
//return error if line doesn't contain a ':'
if (line[i] = '\n') {
printf("Error: a line in the asset file lacks a ':' separator\n");
exit(1);
}
}
}
return lineIndex;
}
Some context, this function is passed a command, and its job is to read a document that appears like this:
command:aBunchOfInfoOnTheComand
anotherCommand:aBunchOfInfoOnTheComand
and pick out the line that the passed command (cmd[]) is stored on.
The issue is with the fgets on line 24. I have separated the relevant portion of this code out into a smaller test program and it works fine.
The test program that works is:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main (int argc, char *argv[]) {
FILE *file = fopen("tutorInfo.txt", "r");
char line[61];
fgets(line, 61, file);
printf("%s\n", line);
}
The proper exicution of my test program leads me to believe other code in my function is causing the issue, but i'm not sure what. It may be important to note, the problematic code has the same imports as my sample program. Any help would be much appreciated.
As OP didn't provide a Minimal, Complete, and Verifiable example, I have to base my answer on the functional description provided in the question.
I already covered some error and corner cases, but I'm sure I missed some. The approach is also inefficient, as the file is read over and over again, instead of parsing it once and returning a hash/map/directory for easy lookup. In real life code I would use something like GLib instead of wasting my time trying to re-invent the wheel(s)...
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define LINE_BUFFER_LENGTH 200
unsigned int locateCmd(FILE *fh, const char *key, const char **cmd_line) {
unsigned int found = 0;
size_t key_length = strlen(key);
*cmd_line = NULL;
/* make sure to start read from start of file */
rewind(fh);
unsigned int line_no = 0;
static char buffer[LINE_BUFFER_LENGTH];
while (!feof(fh) && (found == 0)) {
// NOTE: EOF condition will be checked on the next iteration
fgets(buffer, sizeof(buffer), fh);
size_t length = strlen(buffer);
line_no++;
if (buffer[length - 1] != '\n') {
printf("line %u is too long, aborting!\n", line_no);
return(0);
}
if ((strncmp(key, buffer, key_length) == 0) &&
(buffer[key_length] == ':')) {
found = line_no;
buffer[length - 1] = '\0'; // strip line ending
*cmd_line = &buffer[key_length + 1];
}
}
return(found);
}
int main(int argc, char *argv[]) {
FILE *fh = fopen("dummy.txt", "r");
if (!fh) {
perror("file open");
return(1);
}
int ret = 0;
while (--argc > 0) {
const char *cmd;
const char *key = *++argv;
unsigned line_no = locateCmd(fh, key, &cmd);
if (line_no != 0) {
printf("key '%s' found on line %u: %s\n", key, line_no, cmd);
ret = 0;
} else {
printf("key '%s' not found!\n", key);
};
}
if (fclose(fh) != 0) {
perror("file close");
return(1);
}
return(ret);
}
Test input dummy.txt:
command:aBunchOfInfoOnTheComand
anotherCommand:aBunchOfInfoOnTheComand
brokenline
foo:bar
toolong:sadflkjaLKFJASDJFLKASJDFLKSAJ DLFKJ SLDKJFLKASDFASDFKJASKLDJFLKASJDFLKJASDLKFJASLKDFJLKASDJFLKASJDLFKJASDKLFJKLASDJFLKSAJDFLKJASDLKFJKLASDJFLKASJDFKLJASDLKFJLKASDJFLKASJDFLKJSADLKFJASLKDJFLKC
Some test runs:
$ gcc -Wall -o dummy dummy.c
$ ./dummy command foo bar
key 'command' found on line 1: aBunchOfInfoOnTheComand
key 'foo' found on line 5: bar
line 6 is too long, aborting!
key 'bar' not found!
The following program finds and deletes words that begin and end with the same character. It works just fine, except I decided to take the code for printing result text in from deleteWords() and put it inside of main(). Therefore, the *fpOut parameter in became redundant in deleteWords(). Deleting the parameter results in
/bin/sh: line 1: 1371 Segmentation fault: 11 ./main duom.txt rez.txt make: *** [main] Error 139
However if I compile it and run it any third parameter (e.g. int useless argument instead of FILE *fpOut), it works without errors.
Has anybody have a clue what could be causing this problem?
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int checker (char zodis[]) {
size_t last = strlen(zodis);
if (zodis[0] == zodis[last-1])
return 0;
return 1;
}
void memAlloc (char **text, char **buffer, FILE **fp, char *fileName) {
int fileLength;
*fp = fopen(fileName, "r");
fseek(*fp, 0L, SEEK_END);
fileLength = fseek(*fp, 0L, SEEK_SET);
*text = malloc(fileLength * sizeof(char));
*buffer = malloc(fileLength * sizeof(char));
}
void deleteWords (FILE *fp, int anyUselessParameter, char *buffer) {
char *text;
while (fscanf(fp, "%s", text) == 1) {
if (checker(text)) {
printf("%s ", text);
sprintf(buffer + strlen(buffer), "%s ", text);
}
}
printf("\n");
}
int main(int argc, char *argv[]) {
FILE *fp, *fpOut;
int anyUselessParameter;
char *text, *buffer, *inputFileName = argv[1], *outputFileName = argv[2];
if (argc < 2)
return 0;
fpOut = fopen(outputFileName, "w");
memAlloc(&text, &buffer, &fp, inputFileName);
deleteWords(fp, anyUselessParameter, buffer);
fputs(buffer, fpOut);
fclose(fp);
fclose(fpOut);
free(text);
return 0;
}
char *text;
while (fscanf(fp, "%s", text) == 1) {
scanf needs the buffer to be allocated. Here it dereferences an uninitialized pointer text and writes to it. scanf tries to write to text[0], text[1].. and so on, so accesses text out of bounds and undefined behavior happen.
*buffer = malloc(fileLength * sizeof(char));
...
sprintf(buffer + strlen(buffer), "%s ", text);
buffer is uninitialized, so strlen(buffer) will result in some undefined value. Explicitly initialize buffer[0] = '\0' if you wish to use strlen later. Also you don't include memory for terminating '\0' character inside your buffer.
As you are trying to read the file into a buffer, that is allocated using the file size
if (fread(buffer, fileLenght, 1, fp) != fileLength) { /* handle error */ }
If you have to, use snprintf instead of sprintf just to be safe. snprinttf(buffer+strlen(buffer), fileLength - strlen(buffer), ...);
Also, try to never use scanf without specifing field length inside %s modifier. You can try:
char text[256]; // or other maximum word length
while (fscanf(fp, "%255s", text) == 1) {
As you already have allocated memory for the file, you can use it as a parameter to scanf, if you have to. One would need to prepare the format string for scanf as argument - it is a bit hard. See below:
for (;;) {
// prepare scanf %s format modifier to use with printf to write to buffer end
char fmt[20];
size_t buffer_size = fileLenght;
size_t free_in_buffer = buffer_size - strlen(buffer);
snprintf(fmt, 20, "%%%ds", free_in_buffer);
// we will write here: up to free_in_buffer
char *in = buffer + strlen(buffer);
if (fscanf(fp, fmt, in) != 1) break;
// we now check the last readed word form the file
if (!checker(in)) {
// if the last readed word is bad, we can revert it
in[0] = '\0'
}
}
This is wrong:
fileLength = fseek(*fp, 0L, SEEK_SET);
Per POSIX:
RETURN VALUE
The fseek() and fseeko() functions shall return 0 if they
succeed.
Otherwise, they shall return -1 and set errno to indicate the error.
Im trying to build a string to call a script with args, and one of the args is read from a file but im getting a lineshift and the output is like this
/home/glennwiz/develop/c/SnuPort/ExpGetConfig.sh xogs1a 3/37
> lastConfig.txt
i want the 3/37 and > lastConfig to be on the same line.
this is my code.
char getConfig[100] = "/home/glennwiz/develop/c/SnuPort/ExpGetConfig.sh ";
char filedumpto[50] = " > lastConfig.txt";
FILE* file = fopen("rport.txt","r");
if(file == NULL)
{
return NULL;
}
fseek(file, 0, SEEK_END);
long int size = ftell(file);
rewind(file);
char* port = calloc(size, 1);
fread(port,1,size,file);
strcat(getConfig, argv[1]);
strcat(getConfig, port);
strcat(getConfig, filedumpto);
printf(getConfig);
//system(getConfig);
return 0;
edit
i dumped the output to a file and opened it in vim to see and it sends a ^M after the variable, which is enter i believe? why does it do this iv tried the solutions under this post but its not working.
tester port print!!!!
/home/glennwiz/develop/c/SnuPort/ExpGetConfig.sh randa1ar2 5/48^M
> SisteConfig.txt
tester port print!!!!
The input file ("rport.txt") probably contains a newline. Strip whitespace from the end of the read input, and it should be ok.
The file probably ends with an end-of-line sequence.
Sleazy, brittle solution:
fread(port, 1,size-1, file); // If it's just a CR or LF
fread(port, 1,size-2, file); // If it's a combination of CRLF.
// your code continues here
A better, portable solution will do something like this:
char *port = calloc(size+1, sizeof(char)); // Ensure string will end with null
int len = fread(port, 1, size, file); // Read len characters
char *end = port + len - 1; // Last char from the file
// If the last char is a CR or LF, shorten the string.
while (end >= p) && ((*end == '\r') || (*end == '\n')) {
*(end--) = '\0';
}
Here's working code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char getConfig[100] = "/home/glennwiz/develop/c/SnuPort/ExpGetConfig.sh ";
const char *filedumpto = " > lastConfig.txt";
int main(char argc, char *argv[]) {
FILE *file = fopen("rport.txt", "r");
if (file == NULL) {
return 1;
}
fseek(file, 0, SEEK_END);
long int size = ftell(file);
rewind(file);
char *port = calloc(size+1, 1);
int len = fread(port, 1, size, file); // Read len characters
char *end = port + len - 1; // Last char from the file
// While the last char is a CR or LF, shorten the string.
while ((end >= port) && ((*end == '\r') || (*end == '\n'))) {
*(end--) = '\0';
}
strcat(getConfig, argv[1]);
strcat(getConfig, port);
strcat(getConfig, filedumpto);
printf("%s\n", getConfig);
return 0;
}