How can I get a dirent struct from a string path? - c++

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.

Related

C++ - What permissions does stat() need?

I've got some problems using the stat() function.
What I'm doing is very simple, and I remember it worked on another machine.
I have a directory structure like this:
/home/bernd/dir
/home/bernd/dir/file.ext
/home/bernd/dir/anotherDir
and so on...
What I want to do is to distinguish between files and directories with this source code:
DIR *dir = opendir("/home/bernd/dir");
struct dirent *pent;
while(pent = readdir(dir))
{
if((strcmp(pent->d_name,".") == 0) || (strcmp(pent->d_name,"..") == 0)
continue;
struct stat st;
string tmp = "/home/bernd/dir/" + pent->d_name;
if(stat(tmp.c_str(),&st) == -1)
cout<<strerror(errno);
else
//would be happy to get here
}
As you can see I'm simply walking through the directory and calling stat on the current element, but the stat call always returns Permission Denied. I thought I was messing with relative paths at first, or I was calling stat on the wrong path, which is kept in the string tmp, but I checked them and everything was fine.
The next thing was of course to change the permissions of the files and directories, so anyone can read and write, but the result did not change.
I really hope you guys can help me in any way and your help is highly appreciated!
Thank you in advance!
Do you have execute permission to /home/bernd/dir? Read permission only allows you to list the directory without necessarily being able to access any of its contents.
(On the other hand, execute permission without read permission lets you access the contents but makes the directory unlistable (readdir would fail).)

c++ inotify - watch multiple directories / subdirectories

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);

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.)

Follow the symlink and get the absolute path with QFileInfo

I'm coding with C++ and Qt.
I want to follow the symlink and get the absolute path with QFileInfo.
For example, /usr/local/extra is an symlink for /home/extra.
Then I need to convert /usr/local/extra/my_directory/ to /home/extra/my_directory.
I tried QFileInfo(path).canonicalPath() but it returns the parent directory only.
Use QFileInfo::canonicalFilePath() instead. canonicalPath() always returns the parent directory, while canonicalFilePath() actually includes the file (or directory) itself.
How about QFileInfo::symLinkTarget() ?
QString QFileInfo::symLinkTarget() const Returns the absolute path to
the file or directory a symlink (or shortcut on Windows) points to, or
a an empty string if the object isn't a symbolic link. This name may
not represent an existing file; it is only a string.
QFileInfo::exists() returns true if the symlink points to an existing
file. This function was introduced in Qt 4.2. See also exists(),
isSymLink(), isDir(), and isFile().
After I asked the question, I think I found the solution.
I should use QDir(path).canonicalPath() instead of QFileInfo(path).canonicalPath().
QString QDir::canonicalPath () const Returns the canonical path, i.e.
a path without symbolic links or redundant "." or ".." elements. On
systems that do not have symbolic links this function will always
return the same string that absolutePath() returns. If the canonical
path does not exist (normally due to dangling symbolic links)
canonicalPath() returns an empty string. Example:
// where /local/bin is a symlink to /usr/bin
QString bin = "/local/bin";
QDir binDir(bin);
QString canonicalBin = binDir.canonicalPath();
// canonicalBin now equals "/usr/bin"
QString ls = "/local/bin/ls"; // where ls is the executable "ls"
QDir lsDir(ls);
QString canonicalLs = lsDir.canonicalPath();
// canonicalLS now equals "/usr/bin/ls".

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.