Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: avoid EEXIST error and truncate if needed when opening an existing file #746

Merged
merged 9 commits into from
Mar 27, 2023

Conversation

adammoody
Copy link
Collaborator

@adammoody adammoody commented Dec 9, 2022

Description

This updates unifyfs_fid_open() to handle O_EXCL and EEXIST. Checks from posix wrappers are removed. It splits unifyfs_fid_open() into parts.

  • a new unifyfs_gfid_create() call is responsible for creating the gfid entry on the server. This does not modify the local fid cache at all.
  • a new unifyfs_fid_fetch() call retrieves file meta data for the corresponding gfid from the server. It allocates an entry in the fid cache, or if one already exists, it updates the existing cache entry.

The client API library has been updated to call these new functions. unifyfs_create() calls unifyfs_gfid_create() to create the gfid entry, and then it calls unifyfs_open(). unifyfs_open() calls unifyfs_fid_fetch() to allocate an entry in the fid cache and populate it with data about the gfid from the server.

A new UNIFYFS_CLIENT_EXCL_PRIVATE config option is added to toggle whether O_EXCL implies a private file. By default, O_EXCL creates private files. One can create shared files using O_EXCL by setting UNIFYFS_CLIENT_EXCL_PRIVATE=0.

Motivation and Context

When passed the O_CREAT flag, an open call on an existing file should succeed as long as O_EXCL is not also given. We return an EEXIST error from unifyfs_fid_open when the file already exists. In our open and creat wrappers, this is then handled as a special case where we do a second call to unifyfs_fid_open after masking out the O_CREAT flag.

int rc = unifyfs_fid_open(posix_client, upath, flags, mode,
&fid, &pos);
if (rc == EEXIST) {
/* POSIX allows O_CREAT on existing file */
if ((flags & O_CREAT) && !(flags & O_EXCL)) {
flags -= O_CREAT;
rc = unifyfs_fid_open(posix_client, upath, flags, mode,
&fid, &pos);
}
}

However, that special case logic is missing from our fopen wrapper, which fails with an error when calling fopen(name, "w") on an existing file. This means that the following case returns an EEXIST error when it should succeed:

// create a file
FILE* fp = fopen(file, "w");
fclose(fp);

// fopen an existing file
// this was incorrectly returning an EEXIST error
fp = fopen(file, "w");

This refactors unifyfs_fid_open to simplify checking when opening laminated files, truncating files, and allocating a new local fid structure when needed.

How Has This Been Tested?

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Performance enhancement (non-breaking change which improves efficiency)
  • Code cleanup (non-breaking change which makes code smaller or more readable)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Testing (addition of new tests or update to current tests)
  • Documentation (a change to man pages or other documentation)

Checklist:

  • My code follows the UnifyFS code style requirements.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.
  • All commit messages are properly formatted.

@adammoody adammoody changed the title Create existing fix: avoid EEXIST error and truncate if needed when opening an existing file Jan 9, 2023
@adammoody
Copy link
Collaborator Author

adammoody commented Jan 9, 2023

This PR changes unifyfs_fid_open to be more like POSIX open to that it's successful on existing files when only given O_CREAT, while it fails with EEXIST when given O_CREAT | O_EXCL.

@MichaelBrim , the PR is failing on a test of the client API here:

rc = unifyfs_create(*fshdl, t2_flags, testfile2, &dummy_gfid);
ok(rc != UNIFYFS_SUCCESS && dummy_gfid == UNIFYFS_INVALID_GFID,
"%s:%d unifyfs_create(%s) for existing file fails: rc=%d (%s)",

I see our implementation only sets the O_CREAT flag.

int create_flags = O_CREAT;
int rc = unifyfs_fid_open(client, filepath, create_flags, mode,
&fid, &filepos);

We could change this to O_CREAT | O_EXCL. Another option might be to leave this as O_CREAT but modify the test to expect success. What do you think?

@MichaelBrim
Copy link
Collaborator

We could change this to O_CREAT | O_EXCL. Another option might be to leave this as O_CREAT but modify the test to expect success. What do you think?

The semantics of unifyfs_create() match that of open(O_CREAT | O_EXCL), so my suggestion is to change the code in unifyfs_create() to pass O_EXCL as well when calling unifyfs_fid_open().

@adammoody
Copy link
Collaborator Author

adammoody commented Jan 10, 2023

@MichaelBrim , I added O_EXCL. But now I think it's tripping a test on our "private file" semantic which is triggered with O_EXCL:

if (!ok((true == fmeta.is_shared),
"unifyfs_get_server_file_meta(): is file shared")) {

We indicate that the exclusive bit was set in the open flags and pass that to unifyfs_fid_create.

int exclusive = flags & O_EXCL;

fid = unifyfs_fid_create_file(client, path, exclusive);

and then in unifyfs_fid_create, we use that last parameter to mark the file as private (not shared).

int unifyfs_fid_create_file(unifyfs_client* client,
const char* path,
int private)

meta->attrs.is_shared = !private;

So we currently have two meanings for O_EXCL:

  • mark file as private (not shared)
  • throw EEXIST if file already exists on O_CREAT

I guess we'd need to either pick a different bit flag to indicate private files or I could revert back to handling the O_EXCL logic up in the sysio/stdio wrappers. What do you think?

@MichaelBrim
Copy link
Collaborator

I'm a bit torn on this dilemma. I think it's useful to maintain the idea of a private (non-shared) file, although most of our usefulness to current applications comes from shared files. Using O_EXCL to mean private was always a hack, but I still can't find a better standard flag to substitute for it. If we want to maintain the private notion, the exclusive-create logic would need to live in the POSIX wrappers layer. We could funnel all open() or fopen() calls that should create files through posix_create() and handle exclusive-create there.

@adammoody adammoody force-pushed the create_existing branch 6 times, most recently from 3936370 to fd28d55 Compare February 1, 2023 00:08
TEST_CHECKPATCH_SKIP_FILES="common/src/unifyfs_configurator.h"
TEST_CHECKPATCH_SKIP_FILES="common/src/unifyfs_configurator.h"
TEST_CHECKPATCH_SKIP_FILES="common/src/unifyfs_configurator.h"
@adammoody
Copy link
Collaborator Author

adammoody commented Feb 1, 2023

@MichaelBrim , I've got this about halfway done now. As it stands unifyfs_fid_open still does a lot of posix-like processing based on the flags it's given. Since it's pretty posix-specific, it may be better to move this up to the posix wrappers like you suggested. Maybe we could come up with a unifyfs_fid_open that is free from any O_* flags.

To avoid breaking things, I have unifyfs_api call the old unifyfs_fid_open implementation, which I renamed to unifyfs_fid_open2. I also dropped the fid and pos params from that function since the API didn't use them.

Which O_* flags do we want to expose to users in unifyfs_open()?

The unifyfs_fid_open2 implementation currently processes O_DIRECTORY, O_TRUNC, and O_APPEND. Do we want to keep those?

Should we throw an error if a user passes O_CREAT and/or O_EXCL in their call to unifyfs_open() or just ignore them?

@MichaelBrim
Copy link
Collaborator

Since the library API assumes stateless file access,O_APPEND has no useful meaning. O_TRUNC is accomplished via an explicit UNIFYFS_IOREQ_OP_TRUNC operation. We don't have directories in the library API yet, so O_DIRECTORY isn't useful either.

I think my conclusion is the only O_XXX flags we should allow in unifyfs_open() are the read/write access ones. It does make sense to check for the other POSIX file-create related flags and throw an error if somebody passes those.

Also, we should define our own flag values for use in unifyfs_create() for all the per-file behaviors we want. But that's a job for me independent of this PR.

TEST_CHECKPATCH_SKIP_FILES="common/src/unifyfs_configurator.h"
TEST_CHECKPATCH_SKIP_FILES="common/src/unifyfs_configurator.h"
TEST_CHECKPATCH_SKIP_FILES="common/src/unifyfs_configurator.h"
TEST_CHECKPATCH_SKIP_FILES="common/src/unifyfs_configurator.h"
@adammoody
Copy link
Collaborator Author

@MichaelBrim , this is ready for another look when you have a chance. The main changes since you last looked at it are in the client API library.

I updated the API calls to use the new gfid_create and fid_fetch calls. I added a check so that unifyfs_open only accepts O_RDONLY/WRONLY/RDWR flags, and there's one test case added to check for O_TRUNC as an example. I dropped the old unifyfs_fid_open() call.

I think it's good to go. Let me know if you have suggestions.

Copy link
Collaborator

@MichaelBrim MichaelBrim left a comment

Choose a reason for hiding this comment

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

Just had the one suggestion to remove an '#if 0'block, but otherwise looks good.

client/src/unifyfs_fid.c Outdated Show resolved Hide resolved
TEST_CHECKPATCH_SKIP_FILES="common/src/unifyfs_configurator.h"
@adammoody adammoody merged commit 1657721 into LLNL:dev Mar 27, 2023
@adammoody adammoody deleted the create_existing branch March 27, 2023 20:02
@adammoody
Copy link
Collaborator Author

Thanks, @MichaelBrim !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants