Skip to content

Commit 177978f

Browse files
mhaggergitster
authored andcommitted
raceproof_create_file(): new function
Add a function that tries to create a file and any containing directories in a way that is robust against races with other processes that might be cleaning up empty directories at the same time. The actual file creation is done by a callback function, which, if it fails, should set errno to EISDIR or ENOENT according to the convention of open(). raceproof_create_file() detects such failures, and respectively either tries to delete empty directories that might be in the way of the file or tries to create the containing directories. Then it retries the callback function. This function is not yet used. Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu> Reviewed-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent 204a047 commit 177978f

File tree

2 files changed

+111
-0
lines changed

2 files changed

+111
-0
lines changed

cache.h

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1057,6 +1057,49 @@ enum scld_error {
10571057
enum scld_error safe_create_leading_directories(char *path);
10581058
enum scld_error safe_create_leading_directories_const(const char *path);
10591059

1060+
/*
1061+
* Callback function for raceproof_create_file(). This function is
1062+
* expected to do something that makes dirname(path) permanent despite
1063+
* the fact that other processes might be cleaning up empty
1064+
* directories at the same time. Usually it will create a file named
1065+
* path, but alternatively it could create another file in that
1066+
* directory, or even chdir() into that directory. The function should
1067+
* return 0 if the action was completed successfully. On error, it
1068+
* should return a nonzero result and set errno.
1069+
* raceproof_create_file() treats two errno values specially:
1070+
*
1071+
* - ENOENT -- dirname(path) does not exist. In this case,
1072+
* raceproof_create_file() tries creating dirname(path)
1073+
* (and any parent directories, if necessary) and calls
1074+
* the function again.
1075+
*
1076+
* - EISDIR -- the file already exists and is a directory. In this
1077+
* case, raceproof_create_file() removes the directory if
1078+
* it is empty (and recursively any empty directories that
1079+
* it contains) and calls the function again.
1080+
*
1081+
* Any other errno causes raceproof_create_file() to fail with the
1082+
* callback's return value and errno.
1083+
*
1084+
* Obviously, this function should be OK with being called again if it
1085+
* fails with ENOENT or EISDIR. In other scenarios it will not be
1086+
* called again.
1087+
*/
1088+
typedef int create_file_fn(const char *path, void *cb);
1089+
1090+
/*
1091+
* Create a file in dirname(path) by calling fn, creating leading
1092+
* directories if necessary. Retry a few times in case we are racing
1093+
* with another process that is trying to clean up the directory that
1094+
* contains path. See the documentation for create_file_fn for more
1095+
* details.
1096+
*
1097+
* Return the value and set the errno that resulted from the most
1098+
* recent call of fn. fn is always called at least once, and will be
1099+
* called more than once if it returns ENOENT or EISDIR.
1100+
*/
1101+
int raceproof_create_file(const char *path, create_file_fn fn, void *cb);
1102+
10601103
int mkdir_in_gitdir(const char *path);
10611104
extern char *expand_user_path(const char *path);
10621105
const char *enter_repo(const char *path, int strict);

sha1_file.c

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,74 @@ enum scld_error safe_create_leading_directories_const(const char *path)
179179
return result;
180180
}
181181

182+
int raceproof_create_file(const char *path, create_file_fn fn, void *cb)
183+
{
184+
/*
185+
* The number of times we will try to remove empty directories
186+
* in the way of path. This is only 1 because if another
187+
* process is racily creating directories that conflict with
188+
* us, we don't want to fight against them.
189+
*/
190+
int remove_directories_remaining = 1;
191+
192+
/*
193+
* The number of times that we will try to create the
194+
* directories containing path. We are willing to attempt this
195+
* more than once, because another process could be trying to
196+
* clean up empty directories at the same time as we are
197+
* trying to create them.
198+
*/
199+
int create_directories_remaining = 3;
200+
201+
/* A scratch copy of path, filled lazily if we need it: */
202+
struct strbuf path_copy = STRBUF_INIT;
203+
204+
int ret, save_errno;
205+
206+
/* Sanity check: */
207+
assert(*path);
208+
209+
retry_fn:
210+
ret = fn(path, cb);
211+
save_errno = errno;
212+
if (!ret)
213+
goto out;
214+
215+
if (errno == EISDIR && remove_directories_remaining-- > 0) {
216+
/*
217+
* A directory is in the way. Maybe it is empty; try
218+
* to remove it:
219+
*/
220+
if (!path_copy.len)
221+
strbuf_addstr(&path_copy, path);
222+
223+
if (!remove_dir_recursively(&path_copy, REMOVE_DIR_EMPTY_ONLY))
224+
goto retry_fn;
225+
} else if (errno == ENOENT && create_directories_remaining-- > 0) {
226+
/*
227+
* Maybe the containing directory didn't exist, or
228+
* maybe it was just deleted by a process that is
229+
* racing with us to clean up empty directories. Try
230+
* to create it:
231+
*/
232+
enum scld_error scld_result;
233+
234+
if (!path_copy.len)
235+
strbuf_addstr(&path_copy, path);
236+
237+
do {
238+
scld_result = safe_create_leading_directories(path_copy.buf);
239+
if (scld_result == SCLD_OK)
240+
goto retry_fn;
241+
} while (scld_result == SCLD_VANISHED && create_directories_remaining-- > 0);
242+
}
243+
244+
out:
245+
strbuf_release(&path_copy);
246+
errno = save_errno;
247+
return ret;
248+
}
249+
182250
static void fill_sha1_path(struct strbuf *buf, const unsigned char *sha1)
183251
{
184252
int i;

0 commit comments

Comments
 (0)