can i just mmap a file and revise it without writting back? - c++

I want to open a file in memory, and revise some elememts.
here is the code:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include<sys/mman.h>
#include<sys/types.h>
#include<string.h>
#include<unistd.h>
#include <iostream>
using namespace std;
int main(int argc,char *argv[]) {
int fd;
if (argc < 2) {
printf("./app filename\n");
exit(1);
}
fd = open(argv[1], O_CREAT | O_RDWR | O_TRUNC, 0777);
// fd = open(argv[1], O_RDONLY, 0777);
lseek(fd, 128*sizeof(int)+1, SEEK_SET);
write(fd,"",1);
int* p = (int*)mmap(NULL,128*sizeof(int),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
for (int i = 0;i < 128; ++i) {
*(p+i) = i;
sleep(1);
cout << "writing " << i << " as " << i << endl;
}
close(fd);
munmap(p, 128*sizeof(int));
return 0;
}
but i want to keep the file clean, which means i dont want write back when exit the code.
I know when the code exit, it will write it back whether i call munmap or not.
So, how can i keep the file clean, and revise the element just in memory?

You want MAP_PRIVATE flag to mmap. It is defined as following:
MAP_PRIVATE:
Create a private copy-on-write mapping. Updates to the mapping are not visible to other processes mapping the same file, and are not carried through to the underlying file. It is unspecified whether changes made to the file after the mmap() call are visible in the mapped region.
This means that you'll get the file, but the first time you change the file, it will create a private copy just for you. All changes will go to this copy, and will disappear once program exits.

Related

Non-blocking way to check if there is data on a iofstream

I need to have a way to check if there is data to read on a file (fifo) in a non-blocking way.
I have tried using peek; but it is blocking, I have tried to get and then unget a character in order to check the file without altering the contents; but once again get is blocking...
The only non-blocking solution I have found is to use std::getline(file, line_str) and check if the string is empty; however this does not suit my needs as it alters the data on the file. (The data is a serialized object I will read once I detect there is something to read).
Note: I need this to be non-blocking: I have multiple file streams and need to check all of them regularly to see if there is an object to read/deserialize.
Here is a simple example of what I am trying to achieve:
Sender.cpp:
#include <fstream>
#include <iostream>
#include <string>
extern "C"{
#include <sys/stat.h> // S_IRUSR, S_IWUSR, mkfifo
}
#include <cerrno> // errno
int main(int, char** argv) {
std::string pipe = "foobar";
if(mkfifo(pipe.c_str(), S_IRUSR | S_IWUSR) < 0){
if (errno != EEXIST){
std::cerr << errno;
}
}
std::ofstream file{pipe.c_str()};
file.write("boop", 4); // Simulated object serialization
}
Reader.cpp:
#include <fstream>
#include <iostream>
#include <string>
extern "C"{
#include <sys/stat.h> // S_IRUSR, S_IWUSR, mkfifo
}
#include <cerrno> // errno
int main(int, char** argv) {
std::string pipe = "foobar";
if(mkfifo(pipe.c_str(), S_IRUSR | S_IWUSR) < 0){
if (errno != EEXIST){
std::cerr << errno;
}
}
std::ifstream file{pipe.c_str()};
// ...
/* Do check for data and read/deserialize if any data */
// This is in some sort of loop that goes over the different
// filestreams and checks to see if they have data to treat
}
Any help is really appreciated...
EDIT:
Following Zoso's answer I tried using the file size to determine if the file had been changed; however attempeting to get the size of a fifo named pipe is not possible : filesystem error: cannot get file size: Operation not supported [myFilePath]
I'm not sure if this would work for your particular use case but you could use the filesystem APIs. A simple example is
#include <iostream>
#include <fstream>
#include <filesystem>
namespace fs = std::filesystem;
int main()
{
while (true) {
auto path = fs::current_path().append("test");
std::cout <<"Press enter to know file size of "<<path.c_str() <<'\n';
char c= getchar();
try {
std::cout<<"Size of "<<path.c_str()<<"is "<<fs::file_size(path)<<'\n';
} catch(fs::filesystem_error& e) {
std::cout << e.what() << '\n';
}
}
}
As and when the file gets data, that can be kept track of based on the increasing size and the data to be processed can be tracked as and when that data is consumed.

cin.tellg returns -1 when receiving input from a pipe

I have a case where I need pipe the output of a child process to an ifstream.
I am trying both creating an ifstream from a file descriptor using the method here: How to construct a c++ fstream from a POSIX file descriptor?
and I am also trying to just use a pipe from the child stderr to my own stdin and using cin as my stream.
In both cases I am getting -1 when I call tellg.
Here is my code with the pipe from child stderr to parent stdin:
#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
using namespace std;
int
main()
{
int mypipe[2];
pipe(mypipe);
dup2(mypipe[0], STDIN_FILENO);
dup2(mypipe[1], STDERR_FILENO);
__pid_t pid = fork();
if(pid == 0)
{
// this is a process that outputs stuff into std::err
char* argv[] = {"copy-feats", nullptr};
int ret = execvp(argv[0], argv);
exit(ret);
}
int status;
waitpid(pid, &status, WNOHANG);
cin.clear(); // attempting to clear the error state. Not working.
long size = cin.tellg();
cout << size << endl;
}
so as I said the output of tellg is -1.
thought if I try to use getline(cin, some_string) I'll actually be able to see the output of the child program.
I tried creating a stream from a pipe, but it still gives me -1.
Here's the code I used:
#include <iostream>
#include <fstream>
#include <ext/stdio_filebuf.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#define READ_FD 0
#define WRITE_FD 1
using namespace std;
using FilebufType = __gnu_cxx::stdio_filebuf<std::ifstream::char_type>;
int
main()
{
int mypipe[2];
pipe(mypipe);
__pid_t pid = fork();
if(pid == 0)
{
dup2(mypipe[WRITE_FD], STDERR_FILENO);
char* argv[] = {"copy-feats", nullptr};
int ret = execvp(argv[0], argv);
exit(ret);
}
int status;
waitpid(pid, &status, WNOHANG);
FilebufType filebuf(mypipe[READ_FD], std::ios::in);
istream is(&filebuf);
is.clear();
auto size = is.tellg();
cout << size << endl;
}
Thanks in advance.
In practice tellg() returns an actual file position only when the input stream is a real file. That is, when the input stream is a std::ifstream, and then only when the underlying file is a plain file.
Pipes, and other non-plain files don't have a concept of a file position.
On Linux, which you are using, tellg() is typically implemented (indirectly, but that's not relevant here) by using lseek(2), and lseek(2)'s documentation explicitly specifies that it returns an ESPIPE error if the file descriptor is a pipe. And an error return, eventually, translates to tellg() returning -1.

How To: Obtain the device node of the device containing a file, given the file descriptor

Simple as that. I have the file descriptor of an opened file, and I want to know the node name of the device which contain it.
This can be made in an easy way using libudev and fstat.
#include <libudev.h> // udev headers.
#include <sys/stat.h> // for fstat function and stat struct.
#include <iostream> // for printing ouput.
#include <fcntl> // for open function.
using namespace std;
int main(int argc, char *argv[])
{
int fd = open(argv[1], O_RDONLY); // The file can be opened using any other mode, Eg. O_RDWR, O_APPEND, etc...
struct udev *udev = udev_new();
struct stat tb;
fstat(fd, &tb);
struct udev_device* dev = udev_device_new_from_devnum(udev, 'b', tb.st_dev);
cout << "The opened file is located in the device: " << udev_device_get_devnode(dev) << endl;
return 0;
}

How to determine size of a huge binary file in c++

To determine a size of a binary file seems to always involve read the whole file into memory. How do I determine the size of a very large binary file which is known way bigger than the memory can take?
On most systems, there's stat() and
fstat() functions (not part of ANSI-C, but part of POSIX). For Linux, look at the man page.
EDIT: For Windows, the documentation is here.
EDIT: For a more portable version, use the Boost library:
#include <iostream>
#include <boost/filesystem.hpp>
using namespace boost::filesystem;
int main(int argc, char* argv[])
{
if (argc < 2)
{
std::cout << "Usage: tut1 path\n";
return 1;
}
std::cout << argv[1] << " " << file_size(argv[1]) << '\n';
return 0;
}
#include <cstdio>
FILE *fp = std::fopen("filename", "rb");
std::fseek(fp, 0, SEEK_END);
long filesize = std::ftell(fp);
std::fclose(fp);
Or, use ifstream:
#include <fstream>
std::ifstream fstrm("filename", ios_base::in | ios_base::binary);
fstrm.seekg(0, ios_base::end);
long filesize = fstrm.tellg();
This should work:
uintmax_t file_size(std::string path) {
return std::ifstream(path, std::ios::binary|std::ios::ate).tellg();
}

file descriptors, difference between printf and std::cout

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
//#include <iostream>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
int main(){
int stdout = dup(1);
char p[] = "test.txt";
close(1);
int output = open(p, O_WRONLY | O_CREAT | O_TRUNC, 0777);
//std::cout << "lala" << endl;
printf("lala\n");
close(output);
dup(stdout);
close(stdout);
printf("lolo\n");
// std::cout << "lolo" << endl;
return 0;
}
I think that printf and std::cout have to output the same thing, I want "lala" on the file, and "lolo" on the terminal screen, why this version (with prinf) print everything on screen and the version with "std::cout" print the things as I like.
This has to do with the internal buffering of the stdio library. "lala\n" is too small to be flushed to the stream straight away, so it is kept in printf internal buffer until it is flushed later.
If you add an fflush then you get the desired result:
int main(){
int stdout_fd = dup(1);
char p[] = "test.txt";
close(1);
int output = open(p, O_WRONLY | O_CREAT | O_TRUNC, 0777);
//std::cout << "lala" << endl;
printf("lala\n");
fflush(stdout); // flush the internal buffer
close(output); // fclose would flush too
dup(stdout_fd);
close(stdout_fd);
printf("lolo\n");
// std::cout << "lolo" << endl;
return 0;
}
Also I have renamed your local variable stdout because that is a globally defined one by stdio. Also stdin and stderr are FILE * pointing to the stdio control streams for those.
The correct way of redirecting streams is http://www.cplusplus.com/reference/cstdio/freopen/