Why can't QFile read from the "~" directory? - c++

I've tried the following short example to find out about a bug in a bigger program I am working on. It looks like QFile doesn't support unix (or the shell's) notation for the home directory:
#include <QFile>
#include <QDebug>
int main()
{
QFile f("~/.vimrc");
if (f.open(QIODevice::ReadOnly))
{
qDebug() << f.readAll();
f.close();
}
else
{
qDebug() << f.error();
}
}
As soon as I replace the "~" with my real home directory path, it works. Is there an easy workaround - some setting to enable? Or do I have to go the "ugly" way and ask QDir for the home directory of the current user and prepend that manually to each path?
Addendum: It's clear that usually the shell performs the tilde expansion so programs would never see that. Still it is so convenient in unix shells that I hoped the Qt implementation for file access would have that expansion included.

You can just create a helper function to do this for you, something like:
QString morphFile(QString s) {
if ((s == "~") || (s.startsWith("~/"))) {
s.replace (0, 1, QDir::homePath());
}
return s;
}
:
QFile vimRc(morphFile("~/.vimrc"));
QFile homeDir(morphFile("~"));
A more complete solution, allowing for home directories of other users as well, may be:
QString morphFile(QString fspec) {
// Leave strings alone unless starting with tilde.
if (! fspec.startsWith("~")) return fspec;
// Special case for current user.
if ((fspec == "~") || (fspec.startsWith("~/"))) {
fspec.replace(0, 1, QDir::homePath());
return fspec;
}
// General case for any user. Get user name and length of it.
QString name (fspec);
name.replace(0, 1, ""); // Remove leading '~'.
int len = name.indexOf('/'); // Get name (up to first '/').
len = (len == -1)
? name.length()
: len - 1;
name = name.left(idx);
// Find that user in the password file, replace with home
// directory if found, then return it. You can also add a
// Windows-specific variant if needed.
struct passwd *pwent = getpwnam(name.toAscii().constData());
if (pwent != NULL)
fspec.replace(0, len+1, pwent->pw_dir);
return fspec;
}
Just one thing to keep in mind, the current solution is not portable to Windows (as per the comments in the code). I suspect this is okay for the immediate question since .vimrc indicates that's not the platform you're running on (it's _vimrc on Windows).
Tailoring the solution to that platform is possible, and indeed shows that the helper-function solution is a good fit since you'll only have to change one piece of code to add that.

It has nothing to do with not supporting UNIX; the expansion of tildes to the user's home directory is a substitution performed by the shell, so yes, you will have to manually replace them.

Please submit a suggestion to the Qt bugtracker.
https://bugreports.qt.io/

Take a look at the C library function glob, which will do tilde expansion (and possibly wildcard expansion and various other functions too).

Related

Special Characters in File Paths on Windows (c++)

I seem to be stuck on special characters (like äöü) in windows file paths. They are legal names for folders (users can set them).
A part of my program has to be able to traverse the filesystem. When trying to enter a childfolder with the name 'öö' (testcase) I get the error that the directory does not exist.
I am rather sure that the problem is this 'line':
wstring newPath = oldPath + ffd.cFileName + L"\\";
From
void reevaluateJob(wstring newPath) {
WIN32_FIND_DATA ffd;
HANDLE findFileHandle = FindFirstFile((newPath + L"\*").c_str(), &ffd);
//skipping invalid case handling and loop
if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
if ((wcscmp(ffd.cFileName, L".") != 0) && (wcscmp(ffd.cFileName, L"..") != 0))
reevaluateJob(newPath + ffd.cFileName + L"\\"); //<=== new path here
} else {
//skipping file part
}
}
Because printing the new path (or ffd.cFileName as wstring) results in different characters. Is there another data type that doesn't have this problem?
EDIT:
This works totally fine as long as the folder names do not contain special characters (like äöü etc.).
As pointed out by #ArnonZilca, #define UNICODE solved half of the problem. Sadly not all windows functions stick to that rule. Some also want #define _UNICODE.
In the process of trying to fix the problem, I also changed my whole code to use WCHAR* instead of wstring. I assume (but cannot be 100% sure) that this does NOT make a difference.

C++ Qt Finding every disk in /dev/

I have a C++ code that searches for every directory containing the word 'disk' inside the directory /dev/ (im on a mac) which is where all USB drives are, but when I'm doing QRegExp("disk*") it can't find any, but when I do 'mount' in Terminal, i can find my USB named disk1s5. Why can't I find it with QRegExp?
#ifdef __APPLE__
DIR *dir = NULL;
dirent *search = NULL;
dir = opendir("/dev/");
if (dir != NULL)
{
while ((search = readdir(dir)) != NULL)
{
QRegExp exp("disk*");
exp.setPatternSyntax(QRegExp::Wildcard);
exp.setCaseSensitivity(Qt::CaseInsensitive);
if (exp.exactMatch(search->d_name))
{
return 0;
} else {
return 2;
}
}
} else {
return 1;
}
endif
It seems you're confusing globbing (wildcards used in shells etc) and regular expressions. But they are not the same.
In regular expressions, * means the preceding item appears zero or more times. As such, disk* matches the strings dis, disk, diskk, diskkk and so on.
If you want to check for disk followed by anything (including nothing), you must use disk.* where . means any character so .* means any character appearing zero or more times. This corresponds exactly to the globbing pattern disk*.
Depending on your needs, you could also use disk.+ to match for disk followed by any character appearing one or more times (which, compared to disk.*, does not match the string disk itself).
I strongly suggest you read the documentation to understand how regular expressions really work.
If you just want to find all disk* dirs from /dev and you are using Qt, you can do it like this:
QDir dir("/dev");
QStringList nameFilter = QStringList() << "disk*";
QStringList disks = dir.entryList(nameFilter, QDir::System|QDir::Dirs);
foreach (QString d, disks)
qDebug() << d;

how to get system or user temp folder in unix and windows?

I am writing a C++ problem. It need to work on both Windows and Unix OS.
How to get user or system tmp folder on different OS?
Update: Thanks #RoiDanton, the most up to date answer is std::filesystem::temp_directory_path (C++17)
Try boost::filesystem's temp_directory_path() which internally uses:
ISO/IEC 9945 (POSIX): The path supplied by the first environment variable found in the list TMPDIR, TMP, TEMP, TEMPDIR. If none of these are found, "/tmp", or, if macro __ANDROID__ is defined, "/data/local/tmp"
Windows: The path reported by the Windows GetTempPath API function.
Interestingly, Window's GetTempPath uses similar logic to the POSIX version: the first environment variable in the list TMP, TEMP, USERPROFILE. If none of these are found, it returns the Windows directory.
The fact that these methods primarily rely on environment variables seems a bit yuck. But thats how it seems to be determined. Seeing as how mundane it really is, you could easily roll your own using cstdlib's getenv function, especially if you want specific order prioritization/requirements or dont want to use another library.
Use the $TMPDIR environment variable, according to POSIX.
char const *folder = getenv("TMPDIR");
if (folder == 0)
folder = "/tmp";
if you use QT(Core) you can try QString QDir::tempPath() , or use it's implementation in your code (QT is open, so, check how they do).
The doc say : On Unix/Linux systems this is usually /tmp; on Windows this is usually the path in the TEMP or TMP environment variable.
According to the docs, the max path is MAX_PATH (260). If the path happens to be 260, the code in the sample above (als plougy) will fail because 261 will be returned. Probably the buffer size should be MAX_PATH + 1.
TCHAR szPath[MAX_PATH + 1];
DWORD result = GetTempPath(MAX_PATH + 1, szPath);
if (result != ERROR_SUCCESS) {
// check GetLastError()
}
Handy function :
std::string getEnvVar( std::string const & key )
{
char * val = getenv( key.c_str() );
return val == NULL ? std::string("") : std::string(val);
}
I guess TEMP or something could be passed as an argument? Depending on the OS of course. getenv is part of stdlib so this should also be portable.
If you get an access to main() function code, may be better is to put necessary folder names through the main()'s **argv and use an OS-dependend batch launcher.
For example, for UNIX
bash a_launcher.sh
where a_launcher.sh is like
./a.out /tmp
On Windows: Use GetTempPath() to retrieve the path of the directory designated for temporary files.
wstring TempPath;
wchar_t wcharPath[MAX_PATH];
if (GetTempPathW(MAX_PATH, wcharPath))
TempPath = wcharPath;
None of these examples are really concrete and provide a working example (besides std::filesystem::temp_directory_path) rather they're referring you to microsoft's documentation, here's a working example using "GetTempPath()" (tested on windows 10):
//temp.cpp
#include <iostream>
#include <windows.h>
int main()
{
TCHAR path_buf[MAX_PATH];
DWORD ret_val = GetTempPath(MAX_PATH, path_buf);
if ( ret_val > MAX_PATH || (ret_val == 0) )
{
std::cout << "GetTempPath failed";
} else {
std::cout << path_buf;
}
}
outputs:
C:\>temp.exe
C:\Users\username\AppData\Local\Temp\

Open file by its full path in C++

I want the user to give me the full path where the file exists and not just the file name. How do I open the file this way?
Is it something like this:
ifstream file;
file.open("C:/Demo.txt", ios::in);
This doesn't seem to work.
Normally one uses the backslash character as the path separator in Windows. So:
ifstream file;
file.open("C:\\Demo.txt", ios::in);
Keep in mind that when written in C++ source code, you must use the double backslash because the backslash character itself means something special inside double quoted strings. So the above refers to the file C:\Demo.txt.
You can use a full path with the fstream classes. The folowing code attempts to open the file demo.txt in the root of the C: drive. Note that as this is an input operation, the file must already exist.
#include <fstream>
#include <iostream>
using namespace std;
int main() {
ifstream ifs( "c:/demo.txt" ); // note no mode needed
if ( ! ifs.is_open() ) {
cout <<" Failed to open" << endl;
}
else {
cout <<"Opened OK" << endl;
}
}
What does this code produce on your system?
The code seems working to me. I think the same with #Iothar.
Check to see if you include the required headers, to compile. If it is compiled, check to see if there is such a file, and everything, names etc, matches, and also check to see that you have a right to read the file.
To make a cross check, check if you can open it with fopen..
FILE *f = fopen("C:/Demo.txt", "r");
if (f)
printf("fopen success\n");
For those who are getting the path dynamicly... e.g. drag&drop:
Some main constructions get drag&dropped file with double quotes like:
"C:\MyPath\MyFile.txt"
Quick and nice solution is to use this function to remove chars from string:
void removeCharsFromString( string &str, char* charsToRemove ) {
for ( unsigned int i = 0; i < strlen(charsToRemove); ++i ) {
str.erase( remove(str.begin(), str.end(), charsToRemove[i]), str.end() );
}
}
string myAbsolutepath; //fill with your absolute path
removeCharsFromString( myAbsolutepath, "\"" );
myAbsolutepath now contains just C:\MyPath\MyFile.txt
The function needs these libraries: <iostream> <algorithm> <cstring>.
The function was based on this answer.
Working Fiddle: http://ideone.com/XOROjq
A different take on this question, which might help someone:
I came here because I was debugging in Visual Studio on Windows, and I got confused about all this / vs \\ discussion (it really should not matter in most cases).
For me, the problem was: the "current directory" was not set to what I wanted in Visual Studio. It defaults to the directory of the executable (depending on how you set up your project).
Change it via: Right-click the solution -> Properties -> Working Directory
I only mention it because the question seems Windows-centric, which generally also means VisualStudio-centric, which tells me this hint might be relevant (:

Get full path of executable of running process on HPUX

I want to get the full path of the running process (executable) without having root permission using C++ code. Can someone suggest a way to achieve this.
on Linux platforms i can do it by using following way.
char exepath[1024] = {0};
char procid[1024] = {0};
char exelink[1024] = {0};
sprintf(procid, "%u", getpid());
strcpy(exelink, "/proc/");
strcat(exelink, procid);
strcat(exelink, "/exe");
readlink(exelink, exepath, sizeof(exepath));
Here exepath gives us the full path of the executable.
Similarly for windows we do it using
GetModuleFileName(NULL, exepath, sizeof(exepath)); /* get fullpath of the service */
Please help me how to do it on HP-UX since there is no /proc directory in HP-UX.
First, I'd like to comment on your Linux solution: it is about 5 times as long as it needs to be, and performs a lot of completely unnecessary operations, as well as using 1024 magic number which is just plain wrong:
$ grep PATH_MAX /usr/include/linux/limits.h
#define PATH_MAX 4096 /* # chars in a path name */
Here is a correct minimal replacement:
#include <limits.h>
...
char exepath[PATH_MAX] = {0};
readlink("/proc/self/exe", exepath, sizeof(exepath));
Second, on HP-UX you can use shl_get_r() to obtain information about all loaded modules. At index 0, you will find information about the main executable. The desc.filename will point to the name of the executable at execve(2) time.
Unfortunately, that name is relative, so you may have to search $PATH, and may fail if the application did putenv("PATH=some:new:path") or if the original exename was e.g. ./a.out and the application has performed chdir(2) since.
On HP-UX, use pstat:
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <unistd.h>
#define _PSTAT64
#include <sys/pstat.h>
int main(int argc, char *argv[])
{
char filename[PATH_MAX];
struct pst_status s;
if (pstat_getproc(&s,sizeof(s),0,getpid()) == -1) {
perror("pstat_getproc");
return EXIT_FAILURE;
}
if (pstat_getpathname(filename,sizeof(filename),&s.pst_fid_text) == -1) {
perror("pstat_getpathname");
return EXIT_FAILURE;
}
printf("filename: %s\n",filename);
return EXIT_SUCCESS;
}
The earlier answer referring to the Unix Programming FAQ was right. The problem, even with the Linux /proc answer, is that the path to the executable may have changed since the exec(). In fact, the executable may have been deleted. Further complications arise from considering links (both symbolic and hard) -- there may be multiple paths to the same executable. There is no general answer that covers all cases, since there may not be a path remaining, and if there is one it may not be unique.
That said, using argv[0] with some logic, as advocated by cjhuitt earlier, will probably do what you want 99.9% of the time. I'd add a check for a path containing "/" before doing the relative path check (and note, you must do that before any cwd() calls). Note that if your calling program is feeling mischievous, there's a host of things that can be done between fork() and exec() to mess this up. Don't rely on this for anything that could affect application security (like location of configuration files).
For what purpose do you need the executable path? Bear in mind, as I put in my earlier post, that there is no guarantee that a path to the executable will exist, or that it will be unique.
I have done this before in a general case. The general idea is to grab argv[0], and do some processing on it:
int main( int argc, char** argv )
{
string full_prog_path = argv[0];
if ( full_prog_path[0] == "/" )
{ // It was specified absolutely; no processing necessary.
}
else
{
string check_cwd = getcwd();
check_cwd += argv[0];
if ( FileExists( check_cwd ) )
{ // It was specified relatively.
full_prog_path = check_cwd;
}
else
{ // Check through the path to find it
string path = getenv( "PATH" );
list<string> paths = path.split( ":" );
foreach( test_path, paths )
{
if ( FileExists( test_path + argv[0] ) )
{ // We found the first path entry with the program
full_prog_path = test_path + argv[0];
break;
}
}
}
}
cout << "Program path: " << full_prog_path << endl;
return 0;
}
Obviously, this has some assumptions that might break at some point, but it should work for most cases.