Intermittent ifstream::read()

I am creating a little project to help distribute a language as a standalone exe. It works by having a "container" exe, which then has the data needed added to the end of it. It is structured like this:

<container code>
<exe code>
<zip data>
<string data><null>
<size of zip data in bytes><null>
<size of exe data in bytes><null>
<size of container data in bytes>

For this post, I will cut out the zip data, and just focus on the exe.

This is my current code: (I've stripped away code, like error handling, but that does exist in the original code)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
class FileData
{
private:
	int tail = 0;
	int head = 0;
	int f_size = 0;
	ifstream* file;

public:
	FileData(std::ifstream* file)
	{
		this->file = file;

		//seek to the end so we can get the full size of the file
		this->file->seekg(EOF, this->file->end);
		this->f_size = this->file->tellg();
		this->file->seekg(0, this->file->beg);
	}

	string read_from_end_until_null()
	{
		string text = "";

		//move to the end (size - what we have already read)
		this->file->seekg(this->f_size - this->tail, this->file->beg);

		int i = this->file->tellg();

		for (i; i > 0; i--)
		{
			if (this->file->peek() == 0)
			{
				this->file->get();
				break;
			}

			this->file->seekg(i, this->file->beg);
		}

		getline(*this->file, text, '\0');

		//add 1 to skip null terminator character
		this->tail += text.length() + 1;

		return text;
	}

	char* read_f(int size)
	{
		char* text;

		//seek to the start since we are reading from the beginning
		this->file->seekg(0, this->file->beg);
		//now seek to where we should be
		this->file->seekg(this->head, this->file->cur);

		this->file->read(text, size);

		this->head += size;

		return text;
	}

	void move_f(int amount) 
	{
		this->head += amount;

		return;
	}
};

int main(int argc, char **argv)
{

	string file = "../../bin/windows.exe";

	srand(fs::file_size(file) * 3);
	fs::path tmp_folder_name = fs::temp_directory_path().concat("/_" + to_string(rand()));

	ifstream self;
	self.open(file, ios::in | ios::binary);

	FileData* data = new FileData(&self);
 
        //offset is equivalent to "<size of container data>" above
	int offset = stoi(data->read_from_end_until_null());
	int exe_size = stoi(data->read_from_end_until_null());
       
        //unused for this example
        int zip_data = stoi(data->read_from_end_until_null());

	string args = data->read_from_end_until_null();

	//skip the container's data
	data->move_f(offset);

	//create the temporary directory
	fs::create_directories(tmp_folder_name);

	//This is where the problem occurs
        //exe_data is always blank - nothing is ever read
	char* exe_data = data->read_f(exe_size);

	fs::path exe_name = tmp_folder_name / "run";
	//read data into executable
	ofstream exe(exe_name, ios::out | ios::binary);
	exe.write(exe_data, exe_size);
	exe.close();

	delete exe_data;
	delete data;

	self.close();

	return 0;
}

Note: in this, "data->move_f(offset);" does nothing, since the offset is 0, so everything below is relative to the start of the file.

The issue I am having is that "exe_data" is always empty. I know the exact size of the data in bytes, of the exe, so I read exactly that many bytes from the beginning of the file. I then write exactly that many bytes to the new file.
When running the program, it seems to be doing something because it freezes shortly, and the finishes.
However, when I check the directory, there isn't a file there. I called it intermittent in the title because depening on where some of the code is, and where "cout <<" statements are, sometimes a file is created but it still has no data within it.

I am really confused as to what is happening, since it seems like it should be reading and writing data. There are no errors anywhere (no files failing to open, etc..), it just seems to fail silently and not read / write any data.

Any help would be awesome!
Is the file stream pointer at end-of-file at some point? If it is, the file stream is considered 'fail' and further reads/seeks fail. Once the file is in 'fail' state, you need to use .clear() to reset the status to good before further file reads/seeks are performed.

You're removed your error handling code so we can't see what you're doing - but basically after every stream operation you should be checking the stream state - and if not as expected, then you need to deal with the issue. Many file functions (eg .read(), seek() ) return a stream reference which can be tested directly as a bool (there's conversion to bool and ! also works on a file stream). tell() return -1 if the stream state isn't good.
Is the file stream pointer at end-of-file at some point?

I believe it is only at the very end once, at thats on line 86, which is the first read from the end of file. From there though, it moves backwards away from the end.

I did have a bug where it read past end of the file, because I wasn't seeking back to the beginning of the file in read_f but with this->file->seekg(0, this->file->beg);, it will always read from the beginning (and that can be confirmed with this->file->tellg() returning 0 on line 54)

The total size of "bin/windows.exe" is 7,721,015 bytes, the exe I am reading from withing is 7,720,960 bytes. So, in theory, when reading the full 7,720,960 bytes it shouldn't read over the end of the file (that's assuming the read_from_end_until_null isn't ""removing"" data from the file, but that seems unlikely)

after every stream operation you should be checking the stream state

To be honest, my error handling doesn't do this.
When you say "stream state", do you mean the good / bad bit states? For now, I am going to assume "stream state" does mean those bits.
If I print the bits (order bad/good) on line 84, I get 0, 1. If I print them again on line 93, I get again 0, 1.

However, after the read_f (line 103), the bad/good bits are 0, 0, so am I correct in saying something has gone wrong?
read_f is broken. It reads from the "file" into an unspecified memory location. That's where you've seen a problem.

I am creating a little project to help distribute a language as a standalone exe.
Your class members don't seem to represent anything about language/locality information, it's just a bunch of file operations. So it's difficult to see how to use this thing. That's the real problem.
Last edited on
read_f is broken

In my code or just in general?

Your class members don't seem to represent anything about language/locality information, it's just a bunch of file operations. So it's difficult to see how to use this thing. That's the real problem.

I struggle to explain it well, but basically, it's for a language called SPWN. It's an interpreted language, so normally, you can't just distrubute an standalone exe.
The C++ code will compile into an exe, which I've called the container. A python script copies the SPWN.exe binary data to the end of the container, and also the binary data of a zip file of all of the .spwn files in your project.
When you run the exe, the above code will execute. It will open itself (argv[0] - but I'm testing with a different file at the moment.), and read the data.
In a temporary folder, it extracts the exe from itself, and also unzips the zip from within itself. The it executes the exe, with the project files.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
char* read_f(int size)
{
    char* text; // Declare a pointer without specifying where it points

    //seek to the start since we are reading from the beginning
    this->file->seekg(0, this->file->beg);
    //now seek to where we should be
    this->file->seekg(this->head, this->file->cur);

    this->file->read(text, size);  // write "size" bytes into wherever "text" is pointing

    this->head += size;

    return text; // return that random address
}


A read method needs a buffer to read into, and a size:
1
2
3
4
5
6
7
8
9
10
11
12
13
size_t read_f(char* buffer, size_t size)
{
    //seek to the start since we are reading from the beginning
    this->file->seekg(0, this->file->beg);
    //now seek to where we should be
    this->file->seekg(this->head, this->file->cur);

    file->read(buffer, size);  // write "size" bytes into the user's buffer

    this->head += size; // should be: head += file->gcount();

    return file->gcount(); // this is how much we actually read
}


You don't need all that this-> stuff, this isn't Java.
Last edited on
Right, that makes sense. Am I correct in saying I created a pointer to random location without specifying how much to allocate? And the fact that sometimes it created a file and sometimes not is because it could have potentially pointed into "bad memory"?

You don't need all that this-> stuff, this isn't Java.

I didn't know that, thanks!
Last edited on
Right, that makes sense. Am I correct in saying I created a pointer to random location without specifying how much to allocate?
No. You didn't allocate anything. You're just pointing to some indeterminate piece of memory, then writing into it. If you're lucky, you'll write into a write protected block and the program with crash. If not, it'll keep going with an invalid state.

And the fact that sometimes it created a file and sometimes not is because it could have potentially pointed into "bad memory"?
Yes.
A possibly easier way is to use std::string into which to read the read data (C++17 and later) - then you don't need to bother about memory allocation/free etc.

Possibly (NOT tried):

1
2
3
4
5
6
7
8
9
10
11
12
std::string read_f(size_t size) {
   // Do seeks as needed

    std::string buffer(size, 0);

    if (!file->read(buffer.data(), size) || file->gcount() != size)
        std::cout << "Error reading file\n";

    head += size;

    return buffer;
}

It's not an error to read less than "size" bytes.
In most cases probably not, but since if it doesn't read exactly the numbers of bytes, it could potentially corrupt the exe/zip, so it's fine to throw the error.
Topic archived. No new replies allowed.