-1

I have a C-program built on macOS High Sierra using gcc and a makefile. The first thing the program does is read a binary inputfile. The filename can be either specified from the terminal command line together with the shell command or the program will ask for it when it’s not specified.

My issue is that when the input file is not specified together with the shell command, the program returns an error, saying it cannot open the file.

Here’s what works and what doesn’t: (program and input file are in the same directory)

  1. open terminal
  2. from the command line type:

    ./program_name –i input.dat

=> works fine

  1. open terminal
  2. from the commandline type:

    ./program_name

  3. program prompts: Inputfile:
  4. I type: input.dat

=> error opening file

  1. open Finder and go to directory with program and input file
  2. doubleclick on program_name icon
  3. program starts in terminal and prompts: Inputfile:
  4. I type: input.dat

=> error opening file

I run the very same source code on linux and windows where it works ok, so I think it must be an OS thing that I don't understand?

I can add that the program was untrusted because it doesn't come from the app store. CTRL-click on the icon solved that.

--EDIT - sorry for not adding the verifyable code.

To clarify: the argc/argv part works fine. it's the last section of the routine where it prompts for the file name where it goes wrong. Maybe it's indeed the path as Jabberwocky suggested. I'll check on that tonight and will follow-up here.

void GetFileName(nr_args, args, filename, json)
 int  nr_args;
 char **args;
 char *filename;
 int* json;
{
  int i = 1;

  filename[0]  = '\0';

  /* the command 'interpreter' itself is stored in argv[0] */

  while (i<nr_args) {
    if (strcmp(args[i], "-e") == 0) {
  /* we cannot set the json flag here, because */
  /* flags have not been initialized yet       */
  *json = 1;
  i++;
}
else {
  if (strcmp(args[i], "-i") == 0) {
    if (nr_args > i+1) {
      /* inputfile was specified */
      strncpy(filename, args[++i], MAX_ID_LENGTH);
      i++;
        }
      }
      else {
        PrintError(41, NULL, args[i]);
        i++;
      }
    }
  }

  if (filename[0] == '\0') {
    printf("\n\nInputfile: ");
    scanf("%19s", filename);
    filename[MAX_ID_LENGTH] = '\0';
    /* clear the input buffer, to prevent parsing an */
    /* empty string as the first user command        */
    /* always do a getchar() independent of OS */
    getchar();

    printf("\n");
  }
}

And this is the part where the file is opened (from main() )

  /* Get filename */
  GetFileName(argc, argv, inputfile, &json);

  /* Open the datafile */
  if ((datafile = fopen(inputfile, "rb")) == NULL) {
    PrintError(40, NULL, inputfile);
    ExitProgram();
    return(OK);
  }

EDIT2-- As per Andrew Henle's reply, this is the prototype.

void    GetFileName(int, char**, char*, int*);

The function is called from the same file as it is defined in.

Marnix
  • 59
  • 6
  • 1
    Please show the code that parses the command line arguments and opens the input file based on the args and the code that reads the file name input and tries to open the file. – Bodo Feb 13 '19 at 12:51
  • 1
    You should provide a [mcve]. – Eric Postpischil Feb 13 '19 at 13:01
  • 1
    Maybe because the current directory isn't what you think. Use [`getcwd`](http://pubs.opengroup.org/onlinepubs/9699919799/functions/getcwd.html) to get the current working directory and print that, this may give a hint. Please provide a [mcve]. Also read this: https://stackoverflow.com/questions/298510/how-to-get-the-current-directory-in-a-c-program – Jabberwocky Feb 13 '19 at 13:02
  • I've added the code that reads the file name. – Marnix Feb 13 '19 at 13:47
  • 1
    How are you calling `GetFileName()`? You need to provide a **complete** example, because that ancient style of function definition can be *dangerous* if you mix it with a current function prototype. – Andrew Henle Feb 13 '19 at 13:50
  • Yes, just realized that. I've added that part. But again, it works on windows and Linux. Only not on OSX – Marnix Feb 13 '19 at 13:51
  • Do you have a function prototype for `GetFileName()` in the file that calls it? – Andrew Henle Feb 13 '19 at 13:55
  • By the way, the current name of the operating system is macOS, not OS X. – Eric Postpischil Feb 13 '19 at 14:19
  • @Marnix What about the 3rd comment? Still waiting for a clarification... – Jabberwocky Feb 13 '19 at 15:22
  • @Jabberwocky: have no access to the code at this moment. Will check tonight when I get home and follow-up. – Marnix Feb 13 '19 at 15:31
  • It's the current directory, indeed. The program and input file are in: Users/mac/Documents/XVAN/escape. When I start from the command line in this directory, that's also the path so it works. When I doubleclick the program in the Finder, the path is /Users/mac. When I prefix the file name that I enter with the rest of the path it works. – Marnix Feb 13 '19 at 16:57
  • @Jabberwocky: thanks a lot for your path suggestion. – Marnix Feb 13 '19 at 17:07
  • @Andrew Henle: Also thanks to you. I did not know about default argument promotion and I will change my function definitions as you suggested. – Marnix Feb 13 '19 at 17:07
  • @Marnix and? Any conclusions? Is the path obtained with `getcwd` the path you expect? – Jabberwocky Feb 13 '19 at 17:08
  • @Jabberwocky: in the case that I doubleclicked from the finder not. It returns my home directory. In the case that I use a terminal, cd to the directory with the program and start it with ./program then yes. Apparently, doubleclicking gives the home directory as the path and not the directory the doubleclicked file is in. I don't know why that is, because I don't have that on Linux (Mint). So I think, I'll use getcwd() to catch the path and prefix the filename with it. – Marnix Feb 13 '19 at 17:14
  • @Marnix prefixing the filename with what you get from `getcwd()` won't help, latter is already the current directory. – Jabberwocky Feb 13 '19 at 17:16
  • @jabberwocky: you're right. I must find a way to set the program's home directory to the directory it is currently in. :-( – Marnix Feb 13 '19 at 18:01
  • 1
    @Marnix there is no universdal solution, this might be interesting: https://stackoverflow.com/q/933850/898348 – Jabberwocky Feb 13 '19 at 18:18
  • I got something. By using _NSGetExecutablePath() and realpath() I can get the full path, including the name of the program (I actually tested this). So if I strip /program_name from the end of the path name returned by realpath and then append the file name entered by the user it should work, I think. I'm going to try this and will post the code if it works. – Marnix Feb 13 '19 at 19:29

2 Answers2

0

I run the very same source code on linux and windows where it works ok

That is meaningless.

This is an old K&R-style function definition:

void GetFileName(nr_args, args, filename, json)
 int  nr_args;
 char **args;
 char *filename;
 int* json;
{
   ...

You can not call that function safely if the calling code uses a function prototype. A K&R-defined function expects all of its arguments to have underdone default argument promotion. A prototype for the function means the caller won't perform those promotions. The mismatch will result in undefined behavior.

Don't use such ancient functions. You can't use them with a prototype, and without a prototype you have no type safety in the function call.

Use a proper, C standard-compliant function definition:

void GetFileName( int  nr_args, char **args, char *filename, int* json )
{
...

And then provide a proper prototype for all calls to the function.

Andrew Henle
  • 32,625
  • 3
  • 24
  • 56
0

I made the following routine that works for me.

So, as an example:

  • if the program is in location /usr/mac/my-name/programs/
  • and the input filename that was entered is input.dat

Then this routine will return: /usr/mac/my-name/programs/input.dat

Thanks for all your help.

#include <sys/param.h>    /* realpath()            */
#include <limits.h>       /* PATH_MAX              */
#include <mach-o/dyld.h>  /* _NSGetExectablePath() */

char *GetFullPath(const char*);

#define MAX_FILENAME_LEN 100   /* or another value */


char *GetFullPath(const char *filename)
{
  char      path_buf[PATH_MAX + 1];
  char      resolved_name[PATH_MAX + 1];
  char      *real_path;
  char      *return_path;
  uint32_t  buf_size = sizeof(path_buf);
  int       index    = 1;

  /* this functions returns the full path of the current */
  /* running application appended with var 'filename' at */
  /* the end. In case of an error it returns NULL.       */

  if ((return_path = (char *) malloc(MAX_FILENAME_LEN)) == NULL) {
    printf("GetFullPath(): error in Malloc()\n");
    return(NULL);
  }

  /* get relative path */
  if (_NSGetExecutablePath(path_buf, &buf_size) != 0) {
    /* buffer too small */
    printf("File Path too long.");
    free(return_path);
    return(NULL);
  }

  /* convert to absolute path */
  if ( (real_path = realpath(path_buf, resolved_name)) == NULL) {
    printf("Could not determine path.\n");
    free(return_path);
    return(NULL);
  }

  /* strip the application name from the end of the path */
  index = strlen(real_path) - 1;
  while (real_path[index] != '/') {
    index--;
  }

  /* now check if there's enough room in return_path */
  if (strlen(real_path) + strlen(filename) >= MAX_FILENAME_LEN) {
    printf("File path too long.\n");
    free(return_path);
    return(NULL);
  }

  /* there's enough room, copy path and filename to return_path */
  strncpy(return_path, real_path, index+1);
  /* do not try to free() real_path */
  return_path[index+1] = '\0';
  strncat(return_path, filename, strlen(filename));

  return(return_path);  /* caller must free() return_path */
}
Marnix
  • 59
  • 6