Skip to content

file_dialog_open/save fixes#1210

Merged
jdumas merged 4 commits intolibigl:devfrom
evouga:dialogfixes
Jun 16, 2019
Merged

file_dialog_open/save fixes#1210
jdumas merged 4 commits intolibigl:devfrom
evouga:dialogfixes

Conversation

@evouga
Copy link
Copy Markdown
Contributor

@evouga evouga commented Jun 3, 2019

Attempt to fix several issues with the libigl file dialogs, so that they correctly return an empty string rather than garbage when:

  • the user cancels the dialog
  • the user selects a file somehow longer than the 1024-character limit
  • the file selection using zenity etc. fails for any other reason.

There is a strange erasure of the last character in the Linux version
(presumably to remove some trailing newline?) that I did not touch.

Fixes #1209.

Check all that apply (change to [x])

  • All changes meet libigl style-guidelines.
  • Adds new .cpp file.
  • Adds corresponding unit test.
  • Adds corresponding python binding.
  • This is a minor change.

correctly return an empty string rather than garbage when:
 - the user cancels the dialog
 - the user selects a file somehow longer than the 1024-character limit
 - the file selection using zenity etc. fails for any other reason.
There is a strange erasure of the last character in the Linux version
(presumably to remove some trailing newline?) that I did not touch.
Comment thread include/igl/file_dialog_open.cpp Outdated
" set existing_file_path to (POSIX path of (existing_file))\n"
"\" 2>/dev/null | tr -d '\n' ","r");
while ( fgets(buffer, FILE_DIALOG_MAX_BUFFER, output) != NULL )
if( !output || fgets(buffer, FILE_DIALOG_MAX_BUFFER, output) == NULL || fgets(buffer, FILE_DIALOG_MAX_BUFFER, output) != NULL)
Copy link
Copy Markdown
Collaborator

@jdumas jdumas Jun 8, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand the point of the check fgets(...) == NULL || fgets(...) != NULL? I don't understand why there was a while loop in the first place (maybe because fgets stops reading upon a newline? But even then the old code looks incorrect).

Would the following fix also work in the situations you are describing?

buffer[0] = '\0';
FILE * output = ...
if (output) {
  fgets(...);
  buffer[FILE_DIALOG_MAX_BUFFER - 1] = '\0';
}

Copy link
Copy Markdown
Contributor Author

@evouga evouga Jun 8, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are several potential failure cases:

  1. popen() fails and sets output to NULL;
  2. output is empty (i.e. the user canceled the file dialog without picking a file);
  3. the pipe breaks while reading the filename (so that fgets() fails and sets an error flag);
  4. the chosen file is too long to fit in buffer (and hence will become invalid if naively truncated to FILE_DIALOG_MAX_BUFFER-1 characters.)

In all these cases the buffer needs to be cleared of its contents so that the function returns an empty string (per the specification in the header).

Your version handles case 1 only. Mine handles 1, 2, 4, and almost 3 (I just noticed that there is a corner case I missed: the user picks an overlong file, which is partially read by the first fgets() call, and then the pipe breaks for some reason before the second fgets() call, whose NULL return is incorrectly interpreted as an EOF. This (presumably, very rare) eventuality can be detected by appending || ferror(output) to the conditional expression.

Copy link
Copy Markdown
Collaborator

@jdumas jdumas Jun 8, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I see. Cramming all those checks in a single if() statement makes it difficult to follow the flow. Also, with your double fgets() you are reading the stream multiple times, which is a bit weird (or maybe I'm still missing something here?).

How about the following:

buffer[0] = '\0';
buffer[FILE_DIALOG_MAX_BUFFER - 1] = 'x'; // Initialize last character with a char != '\0'
FILE * output = ...
if (output) {
  auto ret = fgets(buffer, FILE_DIALOG_MAX_BUFFER, output);
  if (ret == NULL || ferror(output)) {
    // I/O error
    buffer[0] = '\0';
  }
  if (buffer[FILE_DIALOG_MAX_BUFFER - 1] == '\0') {
    // File name too long, buffer has been filled, so we return empty string instead
    buffer[0] = '\0';
  }
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure that seems fine (other than disallowing names of length exactly FILE_DIALOG_MAX_BUFFER - 1, which may be unimportant).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can replace FILE_DIALOG_MAX_BUFFER by 1024+1 if you prefer :D

evouga and others added 3 commits June 8, 2019 15:06
Also handle case that the second fgets() call fails rather than reads EOF.
Also handle the case that the second fgets() fails due to an error rather than reaching EOF
@jdumas jdumas merged commit 119f3bd into libigl:dev Jun 16, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants