c++ inotify - watch multiple directories / subdirectories - c++

First of all, if there is an easier way than using inotify, please tell me!
Basically what I would like to do is watching a root directory with inotify with these flags: IN_CREATE | IN_MODIFY | IN_DELETE.
When it's IN_CREATE and IN_ISDIR I would like to watch that folder too. But the main thing I need is whether a file was created, deleted or modified even in subdirectories. Now I know I can just add multiple directories with inotify_add_watch(), but when I read the event->name how can I know which directory it belongs to? The inotify_event struct doesn't seem to hold that value. So if I have a structure like this:
/root
Then I create a directory "a":
/root/a
Then create a file:
/root/a/tmp.txt
When I read event->name it'll only say tmp.txt, but how can I know it is in the "a" subdirectory? How can I know what the watch descriptor was?

In inotify_event structure the name field contains the name of the object to which the event occurred, relative to wd. You need to get the absolute path of the parent directory and concatenate the name of the file/directory to get the full path.
Also in Inotify_event structure the mask field, you can use the IN_ISDIR mask bit to know if the event that has occurred for that wd is a file or a directory.
This is from the inotify
here
The name field is only present when an event is returned for a file inside a watched directory; it identifies the file pathname relative to the watched directory. This pathname is null-terminated, and may include further null bytes to align subsequent reads to a suitable address boundary.

Here's how I did it:
I created a hashmap (QHash<int, QString> fd_to_path) during inotify_add_watch() time to couple the received wd with its corresponding directory string:
int wd = inotify_add_watch(...next_dir_path..);
if (wd != -1)
fd_to_path.insert(wd, next_dir_path);
Then when reading the received inotify event after struct inotify_event *ev = (...); you just query the corresponding dir path with:
QString dir_path = fd_to_path.value(ev->wd);

Related

How to create dir if needed from filename of non-existing file in Qt?

I wish my app to write a file in a specified location, and therefore create the appropriate directory if needed.
The create dir operation isn't a problem for me, but I need the dir path.
I could extract if from the file path, but maybe is there a quick/concise/convenient way of doing the full operation?
I repeat, I'm not searching the basic makedir function, but one which would take the filename of a possibly non-existing file, or a simple qt function to extract the dir path string from the file path string, so I dont' have to write a func for such a basic task.
Use the following code:
const QString filePath = "C:/foo/bar/file.ini";
QDir().mkpath(QFileInfo(filePath).absolutePath());
This code will automatically create the path to the specified (nonexistent) file.
QFileInfo::absolutePath() extracts the absolute path to the specified file.
QDir::mkpath() creates the previously extracted path.
If you have a full path to the file and need to extract the folder path, you can do it this way:
QFile file(full_path_to_the_file);
QFileInfo info(file);
QString directoryPath = info.absolutePath();
//now you can check if the dir exists:
if(QDir(directoryPath).exists())
//do stuff
Depending on what exactly you need, you may prefer to use QFileInfo::canonicalPath() instead of absolutePath
Alternatively, you may also use QFileInfo::absoluteDir:
QFile file(full_path_to_the_file);
QFileInfo info(file);
if(info.absoluteDir().exists())
//do stuff

c++ - resolve all symbolic links defined in a file path

Short version
how do I get a resolved path from a path that one of its dirs are symbolic link:
example:
Say path = /x/y/d/f1 where y is a symbolic link to /a/b
so the result of resolved file path would be:
/x/a/b/d/f1
Long version
I'd like to write a c++ function that copy files from dir1 to dir2 (of course this is not the actual issue but a reduction of bigger and more complex problem).
Prior to the copy process I'd like to remove all files in dir2 that are going to be copied from dir1.
Say I have:
Dir1 = /a/b/c/d
Dir2 = /x/y/d/
Assume I have file 'f1' in dir1 and file 'f1' in dir2, so my process would do:
remove /x/y/d/f1
copy /a/b/c/d/f1 to /x/y/d/f1
My problem is the following:
Say dir 'y' is a symbolic link to /a/b/c/.
Now when I remove /x/y/d/f1, I am actually removing /a/b/c/d/f1.
(my example may have holes in it, but I hopw you get the idea)
I'd like to avoid this, meaning, when I come to remove /x/y/d/f1 I want to be able to know that I'll be removing /x/y/d/f1 and skip that remove
I tried using POSIX readlink() function but it only works when the file 'f1' itself is a symbolic link BUT does not work when one of its parent dirs is a symbolic link.
Any ideas?
Following link will give you the answer.
http://ubuntuforums.org/showthread.php?t=1126617
You can use following code to resolve symilnks:
char buf[512];
int count = readlink("/x/y/d", buf, sizeof(buf));
if (count >= 0) {
buf[count] = '\0';
printf("%s -> %s\n", argv[1], buf);
}
In above code "d" path component should be the symlink. So you will have to iterate and resolve each path component.
If you just want to avoid the said collision scenario, it is easier to create a canary file and try to access it through the other constructed path. If you meant the question in general, for full-dir operations.
If it's not clear: if you create /x/y/d/TEMP123456 it will appear as /a/b/c/d/TEMP123456 (or the other way around) if they actually point to same dirs.
For a single file it may be even easier: open the source file for exclusive access before trying to delete it in target dir. (I'm not sure how reliable that is if you add NFS systems to the mix.)

How to know when a file is edited?

Is there a way ( or an API ) to know when a text file is edited ( by a program or by a person ) and do a specific action ?
For example: I want to show a MessageBox when the file c:\Users\john\free.txt is edited.
Depends on when you exactly want to know it.
is your application running continuously and do you want to see any change as soon as possible?
is your application a simple command-line application that needs to check for changes once?
In the second case, you could check the modification dates of the file (as suggested by PoweRoy and Michal) or use a hash (as suggested by PoweRoy).
If your application is running continuously, you should use the FindFirstChangeNotification and ReadDirectoryChanges functions. You can read more about it on the following pages:
http://msdn.microsoft.com/en-us/library/windows/desktop/aa364417(v=vs.85).aspx
http://msdn.microsoft.com/en-us/library/windows/desktop/aa365465(v=vs.85).aspx.
Simplest: compare modification dates. But this can be manipulated.
Or make a hash of the original file and compare it with the current file.
GetFileTime should help you.
http://msdn.microsoft.com/en-us/library/ms724320%28v=vs.85%29.aspx
and there is GetFileAttributesEx as well.
check the file's last modify datetime.
This method retrieves status information related to a given CFile object instance or a given file path.
BOOL GetStatus(
CFileStatus& rStatus
) const;
static BOOL PASCAL GetStatus(
LPCTSTR lpszFileName,
CFileStatus& rStatus
);
Parameters
rStatus
A reference to a user-supplied CFileStatus structure that will receive the status information. The CFileStatus structure has the following fields:
CTime m_ctime The date and time the file was created.
CTime m_mtime The date and time the file was last modified.
CTime m_atime The date and time the file was last accessed for reading.
ULONGLONG m_size The logical size of the file in bytes, as reported by the DIR command.
BYTE m_attribute The attribute byte of the file.
char m_szFullName[_MAX_PATH] The absolute filename in the Windows character set.
lpszFileName
A string in the Windows character set that is the path to the desired file. The path can be relative or absolute, or it can contain a network path name.
Return Value
TRUE if the status information for the specified file is successfully obtained; otherwise, FALSE.
PS:information from MSDN

How to use fstream objects with relative path?

Do I always have to specify absolute path for objects instantiated from std::fstream class? In other words, is there a way to specify just relative path to them such as project path?
You can use relative paths as well. But they are relative to the environment you call your executable from.
This is OS dependent but all the major systems behave more or less the same AFAIK.
Windows example:
// File structure:
c:\folder\myprogram.exe
c:\myfile.txt
// Calling command from folder
c:\folder > myprogram.exe
In the above example you could access myfile.txt with "c:/myfile.txt" or "../myfile.txt". If myprogram.exe was called from the root c:\ only the absolute path would work, but instead "myfile.txt" would work.
As Rob Kennedy said in the comments there's really nothing special about paths regarding fstream. But here is a code example using a relative path:
#include <fstream>
int main() {
std::ifstream ifs("../myfile.txt");
... // Do something sensible with the file
}
If you have an .exe file running from C:\Users\Me
and you want to write a file to C:\Users\Me\You\text.txt,
then all what you need to do is to add the current path operator ., so:
std::ifstream ifs(".\\you\\myfile.txt");
will work
You can use relative paths. They're treated the same as relative paths for any other file operations, like fopen; there's nothing special about fstream in that regard.
Exactly how they're treated is implementation-defined; they'll usually be interpretted relative to your process's current working directory, which is not necessarily the same as the directory your program's executable file lives in. Some operating systems might also provide a single working directory shared by all threads, so you might get unexpected results if a thread changes the working directory at the same time another thread tries to use a relative path.
Say you have a src folder directly under your project directory and the src folder contains another tmp_folder folder which contains a txt file named readMe.txt. So the txt file can be read in this way
std::ifstream fin("../src/tmp_folder/readMe.txt");
The behaviour is OS specific. Therefore, the best way to handle this IMHO is to make it somebody else's problem. Read the path to the file to open as a string from the user (e.g: command line argument, config file, env variable etc..) then pass that string directly to the constructor of fstream. Document that this is how your program behaves.
I wrote more about path manipulation here: https://stackoverflow.com/a/40980510/2345997
You can specify a path relative to current directory. On Windows you may call GetCurrentDirectory to retrieve current directory or call SetCurrentDirectory to set current directory. There are also some CRT functions available.
On linux also:
// main.cpp
int main() {
ifstream myFile("../Folder/readme.txt");
// ...
}
Assuming the folder structure is something like this:
/usr/Douments/dev/MyProject/main.cpp
/usr/Documents/dev/MyProject/Folder/readme.txt
What I ended up using was a relative path as identified on How to open a file with relative path in C++? which ended up being:
myFile.open("../Release/frequency.dat", ios::in);
*changing myFile to whatever your variable is.

How can I get a dirent struct from a string path?

If given a directory path in the form of a std::string, how can I get a dirent struct pointing to that directory?
I could start by getting a DIR* to the needed directory using opendir(). Then I could use readdir() on my DIR*, but that tries to return entries within the DIR*, not the DIR* itself.
So what is the appropriate way to achieve this?
If you want the directory entry for the directory itself within its parent, do this:
stat(path). Record st_dev and st_ino.
stat(path + "/.."). If this st_dev is not equal to the st_dev you got for path, path is a mount point, and the rest of what I'm about to say will not work.
opendir(path + "/..") gives you a DIR* for the parent directory.
readdir on that until you find a dirent with d_ino equal to the st_ino you saved in step 1. That's the dirent you want.
The only piece of information you get from this algorithm that you can't get from just doing step 1, though, is the name of the directory within its parent directory. And there's a much easier way to get that information: call realpath() to canonicalize the path, then extract the last component of the result. No problems with mount points or symlinks, either.
Repeat readdir() until you reach the dirent named ., which is the directory itself. To see this from the shell, type ls -al.