Copy File Function (PROS)

This function works when compiled with g++ (with some modification) but not in my PROS project. Also when printing to the “console” it shows all the data that is supposed to be written in the file, and it creates a file in the SD card with the proper name. The only problem is the file has nothing written in it. What am I doing wrong?

Brain::printf(); is a custom function to print data on the screen in a console like style.

void copyFile(const char *sourceFile, const char *newFile)

{

  int tempChar;

  FILE *readFile = fopen(sourceFile, "r");

  FILE *writeFile = fopen(newFile, "w");

  if (readFile == NULL)

    Brain::printf("copyFile(): Read file returned NULL");

  if (writeFile == NULL)

    Brain::printf("copyFile(): Write file returned NULL");

  while ((tempChar = fgetc(readFile)) != EOF)
  {
    Brain::printf("%c", tempChar);
    fputc(tempChar, writeFile);
  }

  fclose(readFile);

  fclose(writeFile);

}

First of all, have you looked at this document?

https://pros.cs.purdue.edu/v5/tutorials/topical/filesystem.html

Among other things it lists specifics of working with files on V5, like that microSD card has to be formatted as FAT-32 and when you access files stored on SD card from within V5, the path needs to be prepended with “/usd/”, etc…

Also, please, consider this debugging advice from @hotel who is one of PROS developers:

Finally, are the files binary? Then you would need “rb” and “wb” arguments…

2 Likes

@weilin Yes I have read the documentation for the microSD card. The SD card is formatted with FAT-32 and /usd/ is also the first part of the string. I’m able to write and read files fine, doing it in this function seems to be the problem.

@weilin I just tired to debug through the PROS terminal but nothing was displayed. The program still runs after the function completes, just no data is written to the write file that is created. Also, the files are not in binary. I would also like to stick with using C file I/O as well.

Untitled (2)

Is there any other factors that could cause the file to be created but not written to?

@weilin I just discovered what the PROS terminal is and how to use it, I probably wouldn’t have bothered to make a makesift terminal on the brain screen if I knew about this beforehand. Anyways when I print the data that is read from the readfile it doesn’t show all the data in the file. This is very odd because the Brain “console” shows all the data.

Untitled3 (2)

Also, I deleted a few lines of text in the file and it still didn’t display the last 4 charcters

My guess is that there’s an issue with multiple files being open at once? Try buffering all the data you read, then closing the file being copied before writing to the destination file.

2 Likes

Unfortunatelly, I don’t have access to V5 now because of covid to test things out, but that sounds very strange that something would work in main but would not inside the function.

Are you sure the code is identical? Are you calling the function from the callback? Are filenames you are calling it with are the same? Can you be sure you are not calling the function multiple times at once like @ungato said?

I think you may have misunderstood my post; calling fopen multiple times on different files should totally work, and that’s why it works for him on g++. I was just trying to rule out this issue being caused by a problem in PROS’s or vexOS’s filesystem implementation, as the only thing that’s changing is the platform that the code is running on.

Perhaps try fflush()ing the output too? Again, it shouldn’t be necessary (already supposed to be handled by fclose()), just trying to give the underlying PROS/vexOS implementation the best chance to work properly. Something like this:

#include <stdio.h>
#include <stdlib.h>

void copyFile(const char *sourceFile, const char *newFile) {
  int tempChar;
  FILE *readFile = fopen(sourceFile, "r");
  char* buffer;
  int fileLength;
  int i = 0;

  if (readFile == NULL)
    Brain::printf("copyFile(): Read file returned NULL");

  fseek(readFile, 0, SEEK_END);
  fileLength = ftell(readFile);
  buffer = (char*)malloc(fileLength);
  fseek(readFile, 0, SEEK_SET);

  while((tempChar = fgetc(readFile)) != -1) {
    buffer[i++] = tempChar;
  }
  fclose(readFile);

  FILE *writeFile = fopen(newFile, "w");
  if (writeFile == NULL)
    Brain::printf("copyFile(): Write file returned NULL");

  for(i = 0; i < fileLength; i++) {
    Brain::printf("%c", tempChar);
    fputc(buffer[i], writeFile);
  }
  fflush(writeFile);
  fclose(writeFile);
  free(buffer);
}

1 Like

FYI, vexos has a limit of 8 simultaneous open files, so opening two to make a copy should not be an issue.

8 Likes

@ungato I tried using your function and I also tried to create my own in the same fashion. Both of them failed at writing any data to the file. I also tried to modify my old function by adding a “fflush()” call at the end for both files and it still did the same thing.

test (2)

I also just created the file highlighted in yellow with my write function, just as a sanity check. Then tried to copy this file and still got the same result.

The only noticeable difference in the function that writes data is that it uses a call to “fputs()” instead of “fputc()”, which I doubt would make a difference.

Update: I tried to write the data as C-Strings but it still did not write any data. Also when I try to “fopen()” a file for writing( in this function) and another file exists with the same name it returns NULL. I’m almost positive this is an error because my function that writes data replaces a file if it has the same name. Also, this is how it works in normal C as well.

hm, that is strange. can you check the value of errno after you get NULL back from fopen?

1 Like

Could it be related to the way case sensitivity in FAT-32 file names is handled?

Also, @6403B if you right click to see file properties on Windows, what does it show for file attributes: read-only, hidden, etc…?

1 Like

could be, but then the ball is in vexos’s court. the virtual FS driver we have for the microSD card effectively just farms out all responsibility to the SDK functions after a cursory O_ACCMODE check:

int usd_open_r(struct _reent* r, const char* path, int flags, int mode) {
	FRESULT result = vexFileMountSD();
	if (result != F_OK) {
		r->_errno = FRESULTMAP[result];
		return -1;
	}

	usd_file_arg_t* file_arg = kmalloc(sizeof(*file_arg));

	switch (flags & O_ACCMODE) {
		case O_RDONLY:
			file_arg->ifi_fptr = vexFileOpen(path, "");  // mode is ignored
			break;
		case O_WRONLY:
			if (flags & O_APPEND) {
				file_arg->ifi_fptr = vexFileOpenWrite(path);
			} else {
				file_arg->ifi_fptr = vexFileOpenCreate(path);
			}
			break;
		default:
			r->_errno = EINVAL;
			return -1;
	}

	if (!file_arg->ifi_fptr) {
		r->_errno = ENFILE;  // up to 8 files max as of vexOS 0.7.4b55
		return -1;
	}
	return vfs_add_entry_r(r, usd_driver, file_arg);
}

this is why I’m curious to see what the errno value is, so I can narrow down which “bad end” we’re seeing reached here

5 Likes

I have found a “solution” to this problem. I put this in quotations because I feel it is more of a workaround. Also, I am going to expose the functions and the way in which I used them to make a copy of a file because this was kind of ridiculous.

// Get data from a file and return buffer address //

// ARG1: FILE* readFile -> Pass in a file object that has been open with reading capabilities

// ARG2: int64_t *fileLen -> Pass in an allocated integer, this will hold the size of the data buffer

// RETURN: char * -> Address to the data buffer

char *getFileData(FILE *readFile, int64_t *fileLen)

{

  int tempChar;

  char *buffer;

  int64_t iter = 0;

  if (readFile == NULL)

    Brain::printf("getFileData(): Read file returned NULL");

  fseek(readFile, 0, SEEK_END);

  *fileLen = ftell(readFile);

  buffer = (char *)malloc((*fileLen) * sizeof(char));

  fseek(readFile, 0, SEEK_SET);

  while ((tempChar = fgetc(readFile)) != EOF)

  {

    buffer[iter++] = tempChar;

  }

  fclose(readFile);

  return buffer;

}

// Write data to a file given the buffer address and length //

// ARG1: FILE *writeFile -> Pass in a file object that has been open with write capabilities

// ARG2: char *buffer -> Pass in a data buffer to write to the file

// ARG3: int64_t -> bufferLen -> Length of the allocated buffer

void writeFileData(FILE *writeFile, char *buffer, int64_t bufferLen)

{

  if (writeFile == NULL)

    Brain::printf("writeFileData(): Write file returned NULL");

  for (int64_t i = 0; i < bufferLen; i++)

  {

    fputc(buffer[i], writeFile);

  }

  fclose(writeFile);

}

// Using two functions to make a copy of a file //

void someFunction(void)
{
    int64_t fileSize;

    char *dataBuffer;

    FILE *readFile = fopen(fileName, "r");

    dataBuffer = getFileData(readFile, &fileSize);

    fclose(readFile);

    FILE *writeFile = fopen("/usd/test2.dat", "w");

    writeFileData(writeFile, dataBuffer, fileSize);

    fclose(writeFile);
}

This is the current solution I have found and I will use this unless another more simple method is found.

@hotel I will try this with my old function

@jpearman UPDATE: I have tried this and the writeFile returns 23 which in my errno.h is a macro for ENFILE. This CPP reference website says the error means "Too many files open in system ". I’d imagine this errno value is also being set when the file isn’t returning NULL as well.

ENFILE is returned if VexOs can’t open the file or pros is out of file handles (32, but they’re used for a couple of other things as well, besides sd card files). A couple of things you might want to look into:
Double check that you don’t have multiple handles open to the same file.
You don’t have a lot of files or what not open, and that you’re closing the ones you use. Seeing you’re resorting to copying files, my guess the problem is in this direction.

The last 4 chars not appearing on the console is probably because you weren’t flushing it.

4 Likes

@Johnmandrake I think the problem may have been multiple calls to test if a file existed EX: if(fopen("FILENAME","r") == NULL) thank you. I changed it to :
if( (readFile = fopen("FILENAME","r")) == NULL ) and call fclose when the file is not needed. But now errno is set to ENODEV (19) and the file still isn’t writting. What does this mean?

Edit: GNU error codes says: “No such device.” The wrong type of device was given to a function that expects a particular sort of device. This still doesn’t make much sense to me though

@technik3k The file is not hidden or set to read-only. The attribute reads “N” however, I don’t think that an indication of any error.

this is very strange. there’s no code path in the open syscall that sets ENODEV… and it doesn’t look like fopen does anything special with errno when it returns a null pointer.

can you summarize again what exactly the problem is? I’m having trouble stringing all the threads in this thread into a single coherent narrative.

from what I understand, you have code that

  • opens file A for reading
  • opens file B for writing
  • writes the contents of file A into file B
  • closes both files

and the result is that file B is created, but nothing is written to it?

are you able to reproduce this behavior with writing in the minimal case? i.e. open a single file and write to it.
if that works, are you able to write to more than one file at once? i.e. open two files, write to each of them, then close both.

make sure to log values of errno after every call to fopen, fwrite, fputc, fputs, etc.

4 Likes