errno is hairy :-( You probably also want something like this : diff --git a/builtin/fetch.c b/builtin/fetch.c index 4603cb6..55e7dd8 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -426,7 +426,7 @@ static int update_local_ref(struct ref *ref, _("! %-*s %-*s -> %s (can't fetch in current branch TRANSPORT_SUMMARY(_("[rejected]")), REFCOL_WIDTH, remote, pretty_ref); - return 1; + return STORE_REF_ERROR_OTHER; } if (!is_null_sha1(ref->old_sha1) && @@ -513,7 +513,7 @@ static int update_local_ref(struct ref *ref, TRANSPORT_SUMMARY(_("[rejected]")), REFCOL_WIDTH, remote, pretty_ref, _("(non-fast-forward)")); - return 1; + return STORE_REF_ERROR_OTHER; } } To make it more clear that this function returns a specific error and not just a generic not-zero. On Fri, May 16, 2014 at 11:33 AM, Jonathan Nieder <jrnieder@xxxxxxxxx> wrote: > (cc-ing peff, who may remember this STORE_REF_ERROR_DF_CONFLICT case > from long ago) > Ronnie Sahlberg wrote: > >> In s_update_ref there are two calls that when they fail we return an error >> based on the errno value. In particular we want to return a specific error >> if ENOTDIR happened. Both these functions do have failure modes where they >> may return an error without updating errno > > That's a bug. Any function for which errno is supposed to be > meaningful when it returns an error should always set errno. > Otherwise errno may be set to ENOTDIR within the function by an > unrelated system call. > > Functions that fail and lead callers to check errno, based on a quick > search with 'git grep -e 'errno *[!=]=': > > fopen > lstat > builtin/apply.c::try_create_file (= mkdir, symlink, or open) > rmdir > open > mkdir > unlink > lock_any_ref_for_update > write_ref_sha1 > strtol > kill > odb_pack_keep > opendir > fgets > read > poll > pread > strtoimax > strtoumax > git_parse_int > git_parse_int64 > git_parse_ulong > write_in_full > credential-cache.c::send_request > fstat > run_command_v_opt > git.c::run_argv > readlink > resolve_ref_unsafe > hold_lock_file_for_update > unlink_or_warn > rename > execvp > waitpid > execv_git_cmd > execv_shell_cmd > sane_execvp > stat > read_object > git_mkstemp_mode > create_tmpfile > start_command > xwrite > iconv > fast_export_ls > fast_export_ls_rev > delete_ref > > lock_any_ref_for_update has failure paths > * check_ref_format [!] > * lock_ref_sha1_basic > > lock_ref_sha1_basic has failure paths > * remove_empty_directories > * resolve_ref_unsafe > * safe_create_leading_directories > * verify_lock > > remove_empty_directories has one failure path > * remove_dir_recursively > but could be more explicit about the need to preserve errno. > > errno from remove_dir_recursively is meaningful. > > resolve_ref_unsafe has failure paths > * check_refname_format [!] > * too much recursion [!] > * lstat, readlink, open > * handle_missing_loose_ref > * read_in_full, but errno gets clobbered > * invalid ref [!] > * invalid symref [!] > > verify_lock has failure paths > * read_ref_full, but errno gets clobbered > * old_sha1 didn't match [!] > > write_ref_sha1 has failure paths > * !lock [!] > * missing object [!] > * non-commit object [!] > * write_in_full, close_ref, but errno gets clobbered > * log_ref_write > * commit_ref > > log_ref_write has failure paths > * log_ref_setup > * write_in_full, close. errno gets clobbered > > commit_ref just calls commit_lock_file. > > log_ref_setup has failure paths > * safe_create_leading_directories, but errno gets clobbered > * remove_empty_directories, but errno gets clobbered > * open, but errno gets clobbered > > safe_create_leading_directories doesn't set errno for SCLD_EXISTS > but otherwise its errno is useful. > > That should cover the cases affecting the code path in fetch.c you > mentioned (I haven't checked the others). > > How about something like this? > > It's also tempting to teach vreportf and vwritef to save errno, which > would handle some of these cases automatically. > > diff --git i/refs.c w/refs.c > index 82a8d4e..f98acf0 100644 > --- i/refs.c > +++ w/refs.c > @@ -1333,8 +1333,10 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea > if (flag) > *flag = 0; > > - if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) > + if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) { > + errno = EINVAL; > return NULL; > + } > > for (;;) { > char path[PATH_MAX]; > @@ -1342,8 +1344,10 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea > char *buf; > int fd; > > - if (--depth < 0) > + if (--depth < 0) { > + errno = ELOOP; > return NULL; > + } > > git_snpath(path, sizeof(path), "%s", refname); > > @@ -1405,9 +1409,13 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea > return NULL; > } > len = read_in_full(fd, buffer, sizeof(buffer)-1); > - close(fd); > - if (len < 0) > + if (len < 0) { > + int save_errno = errno; > + close(fd); > + errno = save_errno; > return NULL; > + } > + close(fd); > while (len && isspace(buffer[len-1])) > len--; > buffer[len] = '\0'; > @@ -1424,6 +1432,7 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea > (buffer[40] != '\0' && !isspace(buffer[40]))) { > if (flag) > *flag |= REF_ISBROKEN; > + errno = EINVAL; > return NULL; > } > return refname; > @@ -1436,6 +1445,7 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea > if (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) { > if (flag) > *flag |= REF_ISBROKEN; > + errno = EINVAL; > return NULL; > } > refname = strcpy(refname_buffer, buf); > @@ -1908,14 +1918,17 @@ static struct ref_lock *verify_lock(struct ref_lock *lock, > const unsigned char *old_sha1, int mustexist) > { > if (read_ref_full(lock->ref_name, lock->old_sha1, mustexist, NULL)) { > + int save_errno = errno; > error("Can't verify ref %s", lock->ref_name); > unlock_ref(lock); > + errno = save_errno; > return NULL; > } > if (hashcmp(lock->old_sha1, old_sha1)) { > error("Ref %s is at %s but expected %s", lock->ref_name, > sha1_to_hex(lock->old_sha1), sha1_to_hex(old_sha1)); > unlock_ref(lock); > + errno = EBUSY; > return NULL; > } > return lock; > @@ -1928,15 +1941,17 @@ static int remove_empty_directories(const char *file) > * only empty directories), remove them. > */ > struct strbuf path; > - int result; > + int result, save_errno; > > strbuf_init(&path, 20); > strbuf_addstr(&path, file); > > result = remove_dir_recursively(&path, REMOVE_DIR_EMPTY_ONLY); > + save_errno = errno; > > strbuf_release(&path); > > + errno = save_errno; > return result; > } > > @@ -2137,8 +2152,10 @@ struct ref_lock *lock_any_ref_for_update(const char *refname, > const unsigned char *old_sha1, > int flags, int *type_p) > { > - if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) > + if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) { > + errno = EINVAL; > return NULL; > + } > return lock_ref_sha1_basic(refname, old_sha1, flags, type_p); > } > > @@ -2734,9 +2751,12 @@ int log_ref_setup(const char *refname, char *logfile, int bufsize) > starts_with(refname, "refs/remotes/") || > starts_with(refname, "refs/notes/") || > !strcmp(refname, "HEAD"))) { > - if (safe_create_leading_directories(logfile) < 0) > - return error("unable to create directory for %s", > - logfile); > + if (safe_create_leading_directories(logfile) < 0) { > + int save_errno = errno; > + error("unable to create directory for %s", logfile); > + errno = save_errno; > + return -1; > + } > oflags |= O_CREAT; > } > > @@ -2747,15 +2767,21 @@ int log_ref_setup(const char *refname, char *logfile, int bufsize) > > if ((oflags & O_CREAT) && errno == EISDIR) { > if (remove_empty_directories(logfile)) { > - return error("There are still logs under '%s'", > - logfile); > + int save_errno = errno; > + error("There are still logs under '%s'", logfile); > + errno = save_errno; > + return -1; > } > logfd = open(logfile, oflags, 0666); > } > > - if (logfd < 0) > - return error("Unable to append to %s: %s", > + if (logfd < 0) { > + int save_errno = errno; > + error("Unable to append to %s: %s", > logfile, strerror(errno)); > + errno = save_errno; > + return -1; > + } > } > > adjust_shared_perm(logfile); > @@ -2795,8 +2821,19 @@ static int log_ref_write(const char *refname, const unsigned char *old_sha1, > len += copy_msg(logrec + len - 1, msg) - 1; > written = len <= maxlen ? write_in_full(logfd, logrec, len) : -1; > free(logrec); > - if (close(logfd) != 0 || written != len) > - return error("Unable to append to %s", log_file); > + if (written != len) { > + int save_errno = errno; > + close(logfd); > + error("Unable to append to %s", log_file); > + errno = save_errno; > + return -1; > + } > + if (close(logfd)) { > + int save_errno = errno; > + error("Unable to append to %s", log_file); > + errno = save_errno; > + return -1; > + } > return 0; > } > > @@ -2811,8 +2848,10 @@ int write_ref_sha1(struct ref_lock *lock, > static char term = '\n'; > struct object *o; > > - if (!lock) > + if (!lock) { > + errno = EINVAL; > return -1; > + } > if (!lock->force_write && !hashcmp(lock->old_sha1, sha1)) { > unlock_ref(lock); > return 0; > @@ -2822,26 +2861,32 @@ int write_ref_sha1(struct ref_lock *lock, > error("Trying to write ref %s with nonexistent object %s", > lock->ref_name, sha1_to_hex(sha1)); > unlock_ref(lock); > + errno = EINVAL; > return -1; > } > if (o->type != OBJ_COMMIT && is_branch(lock->ref_name)) { > error("Trying to write non-commit object %s to branch %s", > sha1_to_hex(sha1), lock->ref_name); > unlock_ref(lock); > + errno = EINVAL; > return -1; > } > if (write_in_full(lock->lock_fd, sha1_to_hex(sha1), 40) != 40 || > - write_in_full(lock->lock_fd, &term, 1) != 1 > - || close_ref(lock) < 0) { > + write_in_full(lock->lock_fd, &term, 1) != 1 || > + close_ref(lock) < 0) { > + int save_errno = errno; > error("Couldn't write %s", lock->lk->filename); > unlock_ref(lock); > + errno = save_errno; > return -1; > } > clear_loose_ref_cache(&ref_cache); > if (log_ref_write(lock->ref_name, lock->old_sha1, sha1, logmsg) < 0 || > (strcmp(lock->ref_name, lock->orig_ref_name) && > log_ref_write(lock->orig_ref_name, lock->old_sha1, sha1, logmsg) < 0)) { > + int save_errno = errno; > unlock_ref(lock); > + errno = save_errno; > return -1; > } > if (strcmp(lock->orig_ref_name, "HEAD") != 0) { > @@ -2866,8 +2911,10 @@ int write_ref_sha1(struct ref_lock *lock, > log_ref_write("HEAD", lock->old_sha1, sha1, logmsg); > } > if (commit_ref(lock)) { > + int save_errno = errno; > error("Couldn't set %s", lock->ref_name); > unlock_ref(lock); > + errno = save_errno; > return -1; > } > unlock_ref(lock); > diff --git i/sha1_file.c w/sha1_file.c > index 3e9f55f..a7bbba7 100644 > --- i/sha1_file.c > +++ w/sha1_file.c > @@ -136,8 +136,10 @@ enum scld_error safe_create_leading_directories(char *path) > *slash = '\0'; > if (!stat(path, &st)) { > /* path exists */ > - if (!S_ISDIR(st.st_mode)) > + if (!S_ISDIR(st.st_mode)) { > + errno = EEXIST; > ret = SCLD_EXISTS; > + } > } else if (mkdir(path, 0777)) { > if (errno == EEXIST && > !stat(path, &st) && S_ISDIR(st.st_mode)) -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html