0

Edit 1: This is NOT a duplicate of this question because that person wants to know how to get the executable path. I can get the path just fine, I'm just wondering if there's a more convenient way of USING the path without modifying hundreds of calls to ifstream in my code.

Edit 2: First I'm moving Edit #1 to the top because people still think that the other question answers mine. It does not. I am NOT asking that question. Also I'm going to clarify that my initial question was probably phrased incorrectly. Someone posted in the comments a solution that works for me.

Here is how I would rephrase my question.

I have a program which spawns child processes, in other words it's a program that runs other programs. Program A spawns B, C, D, E, F, etc. My files are organized like so: Program A is at "dir/a/" and B is at "dir/a/b/" and C is at "dir/a/c/" etc.

Now assuming that Program B is written in C++ and it uses ifstream to deal with reading and writing files, my initial question was "is there a way to write Program B's code so that it can deal with reading in files from the executable directory and not from the directory it was spawned?" Because when A runs B, B thinks it's in "dir/a/" when the file is in "dir/a/b/file.txt". So calling ifstream("file.txt") goes to the wrong place. I would need to rewrite the code to ifstream(prefix + "file.txt") everywhere in Program B's code instead. This also needs to work such that I can run B directly rather than through A. Other programs like C, D, E, F, etc. may or may not be written in C++ and may or may not have implemented their own ways of handling this problem.

Many of the solutions posted are just different ways of doing the prefix + filename trick which I already knew about before I asked this question.

The solution posted below that I found which worked is to just change the directory before spawning the process. Thus when the process is spawned, it thinks it is in the new directory. This solves my problem because now it will work for any program, whether it is B, C, D, E, etc or whether it is in C++ or uses ifstream at all. This should have been really obvious in hindsight but for some reason I didn't realize I could run a command to change the directory before spawning the process. So this works for my case and so that's why it's the answer to my question.

Other people keep writing answers to the wrong question, though maybe that's my fault for not writing in the right way or understanding what my problem really was in the first place.

-- The original post --

I have two programs. Program A spawns the child process Program B. Program B uses std::ifstream("file.txt") to read in a file. However, Program A is located in /programs/a/ while Program B is in /programs/a/children/b/. Meaning that when I spawn B as a child process, Program B thinks its current directory is /programs/a and looks for file.txt there (but file.txt is actually in /programs/a/children/b).

I know that the first argument passed to Program B is always the executable path, so I can just take that and modify my calls to file I/O functions to prefix the path with that. But how do I do this in a good way? Is there a convenient way to get this to work without changing much code in Program B? I want to be able to run Program B on its own, as well as from Program A, and in both cases it should be able to read file.txt. That's why I can't just hardcode the filepath, I want it to work in both cases without changing any code between them.

For instance, I can think of making a wrapper class and then changing every call of std::ifstream so that it calls the wrapper instead, kind of like this:

std::ifstream Wrapper::ifstream(const std::string& path)
{
  return std::ifstream(prefix + path); // prefix is member variable
}

But for a project with many calls to ifstream this is inconvenient and possibly inconsistent. Any accidental use of ifstream directly could cause a crash. I would also need to do the same thing for any output filestreams too. And I would really like to minimize any external dependencies, so I'd prefer if there was a built-in way to avoid this problem. It would be nice if I could just toggle some kind of option in the ifstream class so that any call to ifstream("file.txt") becomes ifstream("path/to/file.txt"). Is this the best I can do, or is there any better option out there?

Kinjo
  • 43
  • 1
  • 7
  • 1
    Well, essentially you can run the programs from any directory, and on other machines the installation paths are probably different, so you certainly cannot hardcode it. You have a few choices: (1) Use a known file name in a location that always exists, e.g. `/tmp/somefunnyname.txt` in *nix systems; or (2) perform some interprocess communication (which faces the same problem unless one process spawns the other); or(3) rely on an environment variable. You can fall back to (1) as a default. (3) is probably the way to go. – Peter - Reinstate Monica Jan 03 '22 at 23:18
  • Could you spawn the child process in the correct working directory? – Artyer Jan 03 '22 at 23:18
  • @Offtkp No, because 1. that relies on either boost or Windows.h and I'd like to involve neither of those, and 2. I can get the path just fine, it's the process of using the path to read in the file that I need to figure out one way or another. – Kinjo Jan 03 '22 at 23:49
  • @Artyer Not for this particular project. In addition to Program B I have Programs, C, D, E, F, etc. which are all in their own subfolders because users can add/remove them (modular components) to give different functionality to Program A – Kinjo Jan 03 '22 at 23:51
  • @Peter-ReinstateMonica Good suggestions, but I am not sure if they would work for my particular case. These sound more complicated than just rewriting my code to account for the executable path. Unless you can show how #3 would look like in code. Maybe I'm wrong and just having trouble understanding it. – Kinjo Jan 04 '22 at 00:07
  • 1
    @Kinjo Each of these components could have their own working directory since they are different processes. Most OSs either provide a way to spawn a process in a given directory or at the very least allow you to do `saved = getcwd(); chdir(directory_of_B); spawn_process(B); chdir(saved)`. You can also just have `chdir(prefix)` at the beginning of B. This does not require you to change the working directory of A. – Artyer Jan 04 '22 at 00:15
  • @Artyer Your suggestion actually worked! All I had to do was change the current directory before spawning the process. Of course that is way simpler than changing my C++ code. Thank you so much. If you post as an answer I will mark it correct. – Kinjo Jan 04 '22 at 02:44
  • @Kinjo if you change directory before spawning the process you will have to change it back again unless you don't care about where your parent process is working and opening files. –  Jan 04 '22 at 03:03
  • @Jellyboy I think in my particular project it won't matter. I'm able to spawn multiple processes without issue. I think I phrased my question poorly, but you did give a good answer too. Thank you. – Kinjo Jan 04 '22 at 03:25
  • Does this answer your question? [Get path of executable](https://stackoverflow.com/questions/1528298/get-path-of-executable) – Fedor Jan 04 '22 at 10:42

2 Answers2

4

There is a standard (since C++17) way to figure this out. It is important to notice that as this is standard, it should work with every platform where C++ is implemented: Windows, Linux, MacOS, etc. The standard will take care of handling idiosyncrasies as separators.

You can use std::filesystem::canonical to get the current executable's location. Then get the location of the parent directory and append the filename you want.

An example is:

namespace fs = std::filesystem;
int main( int argc, char* argv[] ) {
    fs::path path( fs::canonical( argv[0] ) );
    fs::path file = path.parent_path() / "file.txt";
    std::cout << file << std::endl;
}

This should print /programs/a/children/b/file.txt on Linux or Mac and "c:\Programs\A\Children\b\file.txt" on Windows (if paths are like that).

1

Here is an illustration of using an environment variable as a common information that any program started from a shell in which it is defined can use. The writer writes a short text to a file, the reader reads it out. Both programs try to read the environment variable A_B_PROG_FILE which should contain the path to a file (which may not exist). This variable name is hard-coded common information; if we had the opportunity to use a common header (but not a duplicated one!) we could as well communicate the path itself that way; this is something to consider. But let's assume that the programs do not share a build environment.

First the writer.cpp:

#include <iostream>
#include <fstream>
#include <cstdlib>

/** @return -1 if file could not be opened, -2 if a write error occurred,
    or 0 on success 
*/
int main(int argc, char **argv)
{
    const char *envPath = std::getenv("A_B_PROG_FILE");
    const char *fpath = envPath ? envPath : "/tmp/defaultname.txt";
    std::ofstream f(fpath);
    if(!f) 
    { 
        std::cerr << "Couldn't open " << fpath << " for writing, exiting\n";
        return -1;
    }
    
    f << "Hello, file ->" << fpath << "<- here!\n" 
      << "This was written by ->" << (argc? argv[0] : "Unknown exe path") << "<-\n";

    return f ? 0 : -2; // if f is not good exit with error code.
    
}

The reader is very similar:

#include <iostream>
#include <fstream>
#include <cstdlib> // getenv()
/** @return -1 if file could not be opened, -2 if a read error occurred,
    or 0 on success 
*/
int main()
{
    const char *envPath = std::getenv("A_B_PROG_FILE");
    const char *fpath = envPath ? envPath : "/tmp/defaultname.txt";
    std::ifstream f(fpath);
    if(!f) 
    { 
        std::cerr << "Couldn't open " << fpath << " for reading, exiting\n";
        return -1;
    }
    std::cout << "file ->" << fpath << "<- contains: ->";
    char c;
    while(f.get(c))
    {
        std::cout << c;
    }
    std::cout << "<-";
    if(!f.eof())
    { 
        std::cerr << "Read error from ->" << fpath << "<-, exiting\n";
        return -2;  
    }
}

Here is a sample session:

$ ls /tmp
$ unset A_B_PROG_FILE
$ ./reader
Couldn't open /tmp/defaultname.txt for reading, exiting
$ ./writer
$ ./reader
file ->/tmp/defaultname.txt<- contains: ->Hello, file ->/tmp/defaultname.txt<- here!
This was written by ->./writer<-
<-
$ export A_B_PROG_FILE=/tmp/ab
$ ./reader
Couldn't open /tmp/ab for reading, exiting
$ ./writer
$ ./reader
file ->/tmp/ab<- contains: ->Hello, file ->/tmp/ab<- here!
This was written by ->./writer<-
<-
$
Peter - Reinstate Monica
  • 15,048
  • 4
  • 37
  • 62
  • 1
    Hey thanks for writing the code. This might help me out later on and taught me some stuff about environment variables, but a different answer has given me a simpler solution. – Kinjo Jan 04 '22 at 02:45
  • @Kinjo If you have two unrelated executables (none is spawned by the other) relying on the path of an executable is not great: The users may copy the executables to arbitrary places. – Peter - Reinstate Monica Jan 04 '22 at 03:31