Merge branch 'tr/protect-low-3-fds'
authorJunio C Hamano <gitster@pobox.com>
Mon, 22 Jul 2013 18:23:35 +0000 (11:23 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 22 Jul 2013 18:23:35 +0000 (11:23 -0700)
When "git" is spawned in such a way that any of the low 3 file
descriptors is closed, our first open() may yield file descriptor 2,
and writing error message to it would screw things up in a big way.

* tr/protect-low-3-fds:
git: ensure 0/1/2 are open in main()
daemon/shell: refactor redirection of 0/1/2 from /dev/null

1  2 
cache.h
daemon.c
git.c
setup.c
shell.c
diff --combined cache.h
index 2d061691554272e53802611bf7c9ffb4d3603550,91974710c3c105c054b7e591fb44742ccc0e594b..b89409bbf20a4fd64ae6aa278ee49b657f85ce39
+++ b/cache.h
@@@ -34,7 -34,6 +34,7 @@@ int git_inflate(git_zstream *, int flus
  
  void git_deflate_init(git_zstream *, int level);
  void git_deflate_init_gzip(git_zstream *, int level);
 +void git_deflate_init_raw(git_zstream *, int level);
  void git_deflate_end(git_zstream *);
  int git_deflate_abort(git_zstream *);
  int git_deflate_end_gently(git_zstream *);
@@@ -119,19 -118,15 +119,19 @@@ struct cache_time 
        unsigned int nsec;
  };
  
 +struct stat_data {
 +      struct cache_time sd_ctime;
 +      struct cache_time sd_mtime;
 +      unsigned int sd_dev;
 +      unsigned int sd_ino;
 +      unsigned int sd_uid;
 +      unsigned int sd_gid;
 +      unsigned int sd_size;
 +};
 +
  struct cache_entry {
 -      struct cache_time ce_ctime;
 -      struct cache_time ce_mtime;
 -      unsigned int ce_dev;
 -      unsigned int ce_ino;
 +      struct stat_data ce_stat_data;
        unsigned int ce_mode;
 -      unsigned int ce_uid;
 -      unsigned int ce_gid;
 -      unsigned int ce_size;
        unsigned int ce_flags;
        unsigned int ce_namelen;
        unsigned char sha1[20];
  #define CE_UNPACKED          (1 << 24)
  #define CE_NEW_SKIP_WORKTREE (1 << 25)
  
 +/* used to temporarily mark paths matched by pathspecs */
 +#define CE_MATCHED           (1 << 26)
 +
  /*
   * Extended on-disk flags
   */
   * another. But we never change the name, or the hash state!
   */
  #define CE_STATE_MASK (CE_HASHED | CE_UNHASHED)
 -static inline void copy_cache_entry(struct cache_entry *dst, struct cache_entry *src)
 +static inline void copy_cache_entry(struct cache_entry *dst,
 +                                  const struct cache_entry *src)
  {
        unsigned int state = dst->ce_flags & CE_STATE_MASK;
  
@@@ -227,8 -218,7 +227,8 @@@ static inline unsigned int create_ce_mo
                return S_IFGITLINK;
        return S_IFREG | ce_permissions(mode);
  }
 -static inline unsigned int ce_mode_from_stat(struct cache_entry *ce, unsigned int mode)
 +static inline unsigned int ce_mode_from_stat(const struct cache_entry *ce,
 +                                           unsigned int mode)
  {
        extern int trust_executable_bit, has_symlinks;
        if (!has_symlinks && S_ISREG(mode) &&
@@@ -317,7 -307,6 +317,7 @@@ extern void free_name_hash(struct index
  #define resolve_undo_clear() resolve_undo_clear_index(&the_index)
  #define unmerge_cache_entry_at(at) unmerge_index_entry_at(&the_index, at)
  #define unmerge_cache(pathspec) unmerge_index(&the_index, pathspec)
 +#define read_blob_data_from_cache(path, sz) read_blob_data_from_index(&the_index, (path), (sz))
  #endif
  
  enum object_type {
@@@ -341,11 -330,9 +341,11 @@@ static inline enum object_type object_t
                OBJ_BLOB;
  }
  
 +/* Double-check local_repo_env below if you add to this list. */
  #define GIT_DIR_ENVIRONMENT "GIT_DIR"
  #define GIT_NAMESPACE_ENVIRONMENT "GIT_NAMESPACE"
  #define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE"
 +#define GIT_PREFIX_ENVIRONMENT "GIT_PREFIX"
  #define DEFAULT_GIT_DIR_ENVIRONMENT ".git"
  #define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY"
  #define INDEX_ENVIRONMENT "GIT_INDEX_FILE"
  #define GIT_NOTES_DISPLAY_REF_ENVIRONMENT "GIT_NOTES_DISPLAY_REF"
  #define GIT_NOTES_REWRITE_REF_ENVIRONMENT "GIT_NOTES_REWRITE_REF"
  #define GIT_NOTES_REWRITE_MODE_ENVIRONMENT "GIT_NOTES_REWRITE_MODE"
 +#define GIT_LITERAL_PATHSPECS_ENVIRONMENT "GIT_LITERAL_PATHSPECS"
  
  /*
 - * Repository-local GIT_* environment variables
 - * The array is NULL-terminated to simplify its usage in contexts such
 - * environment creation or simple walk of the list.
 - * The number of non-NULL entries is available as a macro.
 + * This environment variable is expected to contain a boolean indicating
 + * whether we should or should not treat:
 + *
 + *   GIT_DIR=foo.git git ...
 + *
 + * as if GIT_WORK_TREE=. was given. It's not expected that users will make use
 + * of this, but we use it internally to communicate to sub-processes that we
 + * are in a bare repo. If not set, defaults to true.
   */
 -#define LOCAL_REPO_ENV_SIZE 9
 -extern const char *const local_repo_env[LOCAL_REPO_ENV_SIZE + 1];
 +#define GIT_IMPLICIT_WORK_TREE_ENVIRONMENT "GIT_IMPLICIT_WORK_TREE"
 +
 +/*
 + * Repository-local GIT_* environment variables; these will be cleared
 + * when git spawns a sub-process that runs inside another repository.
 + * The array is NULL-terminated, which makes it easy to pass in the "env"
 + * parameter of a run-command invocation, or to do a simple walk.
 + */
 +extern const char * const local_repo_env[];
  
  extern int is_bare_repository_cfg;
  extern int is_bare_repository(void);
@@@ -425,6 -400,8 +425,8 @@@ extern int path_inside_repo(const char 
  extern int set_git_dir_init(const char *git_dir, const char *real_git_dir, int);
  extern int init_db(const char *template_dir, unsigned int flags);
  
+ extern void sanitize_stdfds(void);
  #define alloc_nr(x) (((x)+16)*3/2)
  
  /*
@@@ -472,13 -449,11 +474,13 @@@ extern int remove_file_from_index(struc
  #define ADD_CACHE_IGNORE_ERRORS       4
  #define ADD_CACHE_IGNORE_REMOVAL 8
  #define ADD_CACHE_INTENT 16
 +#define ADD_CACHE_IMPLICIT_DOT 32     /* internal to "git add -u/-A" */
  extern int add_to_index(struct index_state *, const char *path, struct stat *, int flags);
  extern int add_file_to_index(struct index_state *, const char *path, int flags);
  extern struct cache_entry *make_cache_entry(unsigned int mode, const unsigned char *sha1, const char *path, int stage, int refresh);
  extern int ce_same_name(struct cache_entry *a, struct cache_entry *b);
  extern int index_name_is_other(const struct index_state *, const char *, int);
 +extern void *read_blob_data_from_index(struct index_state *, const char *, unsigned long *);
  
  /* do stat comparison even if CE_VALID is true */
  #define CE_MATCH_IGNORE_VALID         01
  #define CE_MATCH_RACY_IS_DIRTY                02
  /* do stat comparison even if CE_SKIP_WORKTREE is true */
  #define CE_MATCH_IGNORE_SKIP_WORKTREE 04
 -extern int ie_match_stat(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
 -extern int ie_modified(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
 +extern int ie_match_stat(const struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
 +extern int ie_modified(const struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
 +
 +#define PATHSPEC_ONESTAR 1    /* the pathspec pattern sastisfies GFNM_ONESTAR */
  
  struct pathspec {
        const char **raw; /* get_pathspec() result, not freed by free_pathspec() */
        struct pathspec_item {
                const char *match;
                int len;
 -              unsigned int use_wildcard:1;
 +              int nowildcard_len;
 +              int flags;
        } *items;
  };
  
@@@ -509,27 -481,10 +511,27 @@@ extern int init_pathspec(struct pathspe
  extern void free_pathspec(struct pathspec *);
  extern int ce_path_match(const struct cache_entry *ce, const struct pathspec *pathspec);
  
 +extern int limit_pathspec_to_literal(void);
 +
  #define HASH_WRITE_OBJECT 1
  #define HASH_FORMAT_CHECK 2
  extern int index_fd(unsigned char *sha1, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags);
  extern int index_path(unsigned char *sha1, const char *path, struct stat *st, unsigned flags);
 +
 +/*
 + * Record to sd the data from st that we use to check whether a file
 + * might have changed.
 + */
 +extern void fill_stat_data(struct stat_data *sd, struct stat *st);
 +
 +/*
 + * Return 0 if st is consistent with a file not having been changed
 + * since sd was filled.  If there are differences, return a
 + * combination of MTIME_CHANGED, CTIME_CHANGED, OWNER_CHANGED,
 + * INODE_CHANGED, and DATA_CHANGED.
 + */
 +extern int match_stat_data(const struct stat_data *sd, struct stat *st);
 +
  extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
  
  #define REFRESH_REALLY                0x0001  /* ignore_valid */
@@@ -566,7 -521,6 +568,7 @@@ extern int delete_ref(const char *, con
  /* Environment bits from configuration mechanism */
  extern int trust_executable_bit;
  extern int trust_ctime;
 +extern int check_stat;
  extern int quote_path_fully;
  extern int has_symlinks;
  extern int minimum_abbrev, default_abbrev;
@@@ -593,12 -547,6 +595,12 @@@ extern int core_preload_index
  extern int core_apply_sparse_checkout;
  extern int precomposed_unicode;
  
 +/*
 + * The character that begins a commented line in user-editable file
 + * that is subject to stripspace.
 + */
 +extern char comment_line_char;
 +
  enum branch_track {
        BRANCH_TRACK_UNSPECIFIED = -1,
        BRANCH_TRACK_NEVER = 0,
@@@ -743,7 -691,8 +745,7 @@@ enum sharedrepo 
        PERM_EVERYBODY      = 0664
  };
  int git_config_perm(const char *var, const char *value);
 -int set_shared_perm(const char *path, int mode);
 -#define adjust_shared_perm(path) set_shared_perm((path), 0)
 +int adjust_shared_perm(const char *path);
  int safe_create_leading_directories(char *path);
  int safe_create_leading_directories_const(const char *path);
  int mkdir_in_gitdir(const char *path);
@@@ -793,6 -742,9 +795,6 @@@ extern int parse_sha1_header(const cha
  /* global flag to enable extra checks when accessing packed objects */
  extern int do_check_packed_object_crc;
  
 -/* for development: log offset of pack access */
 -extern const char *log_pack_access;
 -
  extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type);
  
  extern int move_temp_to_file(const char *tmpfile, const char *filename);
@@@ -928,7 -880,6 +930,7 @@@ void show_date_relative(unsigned long t
                        struct strbuf *timebuf);
  int parse_date(const char *date, char *buf, int bufsize);
  int parse_date_basic(const char *date, unsigned long *timestamp, int *offset);
 +int parse_expiry_date(const char *date, unsigned long *timestamp);
  void datestamp(char *buf, int bufsize);
  #define approxidate(s) approxidate_careful((s), NULL)
  unsigned long approxidate_careful(const char *, int *);
@@@ -1040,32 -991,15 +1042,32 @@@ struct ref 
        unsigned char old_sha1[20];
        unsigned char new_sha1[20];
        char *symref;
 -      unsigned int force:1,
 -              merge:1,
 -              nonfastforward:1,
 -              deletion:1;
 +      unsigned int
 +              force:1,
 +              forced_update:1,
 +              deletion:1,
 +              matched:1;
 +
 +      /*
 +       * Order is important here, as we write to FETCH_HEAD
 +       * in numeric order. And the default NOT_FOR_MERGE
 +       * should be 0, so that xcalloc'd structures get it
 +       * by default.
 +       */
 +      enum {
 +              FETCH_HEAD_MERGE = -1,
 +              FETCH_HEAD_NOT_FOR_MERGE = 0,
 +              FETCH_HEAD_IGNORE = 1
 +      } fetch_head_status;
 +
        enum {
                REF_STATUS_NONE = 0,
                REF_STATUS_OK,
                REF_STATUS_REJECT_NONFASTFORWARD,
 +              REF_STATUS_REJECT_ALREADY_EXISTS,
                REF_STATUS_REJECT_NODELETE,
 +              REF_STATUS_REJECT_FETCH_FIRST,
 +              REF_STATUS_REJECT_NEEDS_FORCE,
                REF_STATUS_UPTODATE,
                REF_STATUS_REMOTE_REJECT,
                REF_STATUS_EXPECTING_REPORT
@@@ -1089,9 -1023,7 +1091,9 @@@ struct extra_have_objects 
        int nr, alloc;
        unsigned char (*array)[20];
  };
 -extern struct ref **get_remote_heads(int in, struct ref **list, unsigned int flags, struct extra_have_objects *);
 +extern struct ref **get_remote_heads(int in, char *src_buf, size_t src_len,
 +                                   struct ref **list, unsigned int flags,
 +                                   struct extra_have_objects *);
  extern int server_supports(const char *feature);
  extern int parse_feature_request(const char *features, const char *feature);
  extern const char *server_feature_value(const char *feature, int *len_ret);
@@@ -1099,9 -1031,6 +1101,9 @@@ extern const char *parse_feature_value(
  
  extern struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path);
  
 +/* A hook for count-objects to report invalid files in pack directory */
 +extern void (*report_garbage)(const char *desc, const char *path);
 +
  extern void prepare_packed_git(void);
  extern void reprepare_packed_git(void);
  extern void install_packed_git(struct packed_git *pack);
@@@ -1130,7 -1059,6 +1132,7 @@@ extern int unpack_object_header(struct 
  struct object_info {
        /* Request */
        unsigned long *sizep;
 +      unsigned long *disk_sizep;
  
        /* Response */
        enum {
@@@ -1200,9 -1128,6 +1202,9 @@@ extern int check_repository_format_vers
  extern int git_env_bool(const char *, int);
  extern int git_config_system(void);
  extern int config_error_nonbool(const char *);
 +#if defined(__GNUC__) && ! defined(__clang__)
 +#define config_error_nonbool(s) (config_error_nonbool(s), -1)
 +#endif
  extern const char *get_log_output_encoding(void);
  extern const char *get_commit_output_encoding(void);
  
@@@ -1216,28 -1141,12 +1218,28 @@@ struct config_include_data 
  #define CONFIG_INCLUDE_INIT { 0 }
  extern int git_config_include(const char *name, const char *value, void *data);
  
 +/*
 + * Match and parse a config key of the form:
 + *
 + *   section.(subsection.)?key
 + *
 + * (i.e., what gets handed to a config_fn_t). The caller provides the section;
 + * we return -1 if it does not match, 0 otherwise. The subsection and key
 + * out-parameters are filled by the function (and subsection is NULL if it is
 + * missing).
 + */
 +extern int parse_config_key(const char *var,
 +                          const char *section,
 +                          const char **subsection, int *subsection_len,
 +                          const char **key);
 +
  extern int committer_ident_sufficiently_given(void);
  extern int author_ident_sufficiently_given(void);
  
  extern const char *git_commit_encoding;
  extern const char *git_log_output_encoding;
  extern const char *git_mailmap_file;
 +extern const char *git_mailmap_blob;
  
  /* IO helper functions */
  extern void maybe_flush_or_die(FILE *, const char *);
@@@ -1358,31 -1267,4 +1360,31 @@@ int checkout_fast_forward(const unsigne
  
  int sane_execvp(const char *file, char *const argv[]);
  
 +/*
 + * A struct to encapsulate the concept of whether a file has changed
 + * since we last checked it. This uses criteria similar to those used
 + * for the index.
 + */
 +struct stat_validity {
 +      struct stat_data *sd;
 +};
 +
 +void stat_validity_clear(struct stat_validity *sv);
 +
 +/*
 + * Returns 1 if the path is a regular file (or a symlink to a regular
 + * file) and matches the saved stat_validity, 0 otherwise.  A missing
 + * or inaccessible file is considered a match if the struct was just
 + * initialized, or if the previous update found an inaccessible file.
 + */
 +int stat_validity_check(struct stat_validity *sv, const char *path);
 +
 +/*
 + * Update the stat_validity from a file opened at descriptor fd. If
 + * the file is missing, inaccessible, or not a regular file, then
 + * future calls to stat_validity_check will match iff one of those
 + * conditions continues to be true.
 + */
 +void stat_validity_update(struct stat_validity *sv, int fd);
 +
  #endif /* CACHE_H */
diff --combined daemon.c
index 6aeddcb98d2694a705d34e80293fc81a20bc39a8,89aad6c4a39f384fc5218b7cc4b7ce2d9c519caa..973ec38fafd6e679eeebc7c013163fbefe57ec05
+++ b/daemon.c
@@@ -600,7 -600,7 +600,7 @@@ static void parse_host_arg(char *extra_
  
  static int execute(void)
  {
 -      static char line[1000];
 +      char *line = packet_buffer;
        int pktlen, len, i;
        char *addr = getenv("REMOTE_ADDR"), *port = getenv("REMOTE_PORT");
  
                loginfo("Connection from %s:%s", addr, port);
  
        alarm(init_timeout ? init_timeout : timeout);
 -      pktlen = packet_read_line(0, line, sizeof(line));
 +      pktlen = packet_read(0, NULL, NULL, packet_buffer, sizeof(packet_buffer), 0);
        alarm(0);
  
        len = strlen(line);
@@@ -1047,18 -1047,6 +1047,6 @@@ static int service_loop(struct socketli
        }
  }
  
- /* if any standard file descriptor is missing open it to /dev/null */
- static void sanitize_stdfds(void)
- {
-       int fd = open("/dev/null", O_RDWR, 0);
-       while (fd != -1 && fd < 2)
-               fd = dup(fd);
-       if (fd == -1)
-               die_errno("open /dev/null or dup failed");
-       if (fd > 2)
-               close(fd);
- }
  #ifdef NO_POSIX_GOODIES
  
  struct credentials;
diff --combined git.c
index 4359086fd6c47f1bfc3fc408b7bc986517eea9a0,02b4b140ae3c8070473a7a9c90584de7fbcf30b9..6104d5eefc882cdbf978b5a141c93c20b78fcb2b
--- 1/git.c
--- 2/git.c
+++ b/git.c
@@@ -4,7 -4,6 +4,7 @@@
  #include "help.h"
  #include "quote.h"
  #include "run-command.h"
 +#include "commit.h"
  
  const char git_usage_string[] =
        "git [--version] [--help] [-c name=value]\n"
@@@ -14,9 -13,7 +14,9 @@@
        "           <command> [<args>]";
  
  const char git_more_info_string[] =
 -      N_("See 'git help <command>' for more information on a specific command.");
 +      N_("'git help -a' and 'git help -g' lists available subcommands and some\n"
 +         "concept guides. See 'git help <command>' or 'git help <concept>'\n"
 +         "to read about a specific subcommand or concept.");
  
  static struct startup_info git_startup_info;
  static int use_pager = -1;
@@@ -128,7 -125,6 +128,7 @@@ static int handle_options(const char **
                        static char git_dir[PATH_MAX+1];
                        is_bare_repository_cfg = 1;
                        setenv(GIT_DIR_ENVIRONMENT, getcwd(git_dir, sizeof(git_dir)), 0);
 +                      setenv(GIT_IMPLICIT_WORK_TREE_ENVIRONMENT, "0", 1);
                        if (envchanged)
                                *envchanged = 1;
                } else if (!strcmp(cmd, "-c")) {
                        git_config_push_parameter((*argv)[1]);
                        (*argv)++;
                        (*argc)--;
 +              } else if (!strcmp(cmd, "--literal-pathspecs")) {
 +                      setenv(GIT_LITERAL_PATHSPECS_ENVIRONMENT, "1", 1);
 +                      if (envchanged)
 +                              *envchanged = 1;
 +              } else if (!strcmp(cmd, "--no-literal-pathspecs")) {
 +                      setenv(GIT_LITERAL_PATHSPECS_ENVIRONMENT, "0", 1);
 +                      if (envchanged)
 +                              *envchanged = 1;
 +              } else if (!strcmp(cmd, "--shallow-file")) {
 +                      (*argv)++;
 +                      (*argc)--;
 +                      set_alternate_shallow_file((*argv)[0]);
 +                      if (envchanged)
 +                              *envchanged = 1;
                } else {
                        fprintf(stderr, "Unknown option: %s\n", cmd);
                        usage(git_usage_string);
@@@ -323,7 -305,6 +323,7 @@@ static void handle_internal_command(in
                { "bundle", cmd_bundle, RUN_SETUP_GENTLY },
                { "cat-file", cmd_cat_file, RUN_SETUP },
                { "check-attr", cmd_check_attr, RUN_SETUP },
 +              { "check-ignore", cmd_check_ignore, RUN_SETUP | NEED_WORK_TREE },
                { "check-ref-format", cmd_check_ref_format },
                { "checkout", cmd_checkout, RUN_SETUP | NEED_WORK_TREE },
                { "checkout-index", cmd_checkout_index,
@@@ -514,9 -495,8 +514,9 @@@ static int run_argv(int *argcp, const c
  }
  
  
 -int main(int argc, const char **argv)
 +int main(int argc, char **av)
  {
 +      const char **argv = (const char **) av;
        const char *cmd;
  
        startup_info = &git_startup_info;
        if (!cmd)
                cmd = "git-help";
  
+       /*
+        * Always open file descriptors 0/1/2 to avoid clobbering files
+        * in die().  It also avoids messing up when the pipes are dup'ed
+        * onto stdin/stdout/stderr in the child processes we spawn.
+        */
+       sanitize_stdfds();
        git_setup_gettext();
  
        /*
diff --combined setup.c
index 94c1e61bda747ed5c664bbbf8431234dc8276d29,12a85d16138f1500d8e86c4c5f80273b615e657a..88aab94f1595de5a52c8c99ae8b8c4b9322b2fcf
+++ b/setup.c
@@@ -66,14 -66,7 +66,14 @@@ int check_filename(const char *prefix, 
        const char *name;
        struct stat st;
  
 -      name = prefix ? prefix_filename(prefix, strlen(prefix), arg) : arg;
 +      if (!prefixcmp(arg, ":/")) {
 +              if (arg[2] == '\0') /* ":/" is root dir, always exists */
 +                      return 1;
 +              name = arg + 2;
 +      } else if (prefix)
 +              name = prefix_filename(prefix, strlen(prefix), arg);
 +      else
 +              name = arg;
        if (!lstat(name, &st))
                return 1; /* file exists */
        if (errno == ENOENT || errno == ENOTDIR)
@@@ -207,11 -200,10 +207,11 @@@ static const char *prefix_pathspec(cons
                     *copyfrom && *copyfrom != ')';
                     copyfrom = nextat) {
                        size_t len = strcspn(copyfrom, ",)");
 -                      if (copyfrom[len] == ')')
 -                              nextat = copyfrom + len;
 -                      else
 +                      if (copyfrom[len] == ',')
                                nextat = copyfrom + len + 1;
 +                      else
 +                              /* handle ')' and '\0' */
 +                              nextat = copyfrom + len;
                        if (!len)
                                continue;
                        for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++)
                                die("Invalid pathspec magic '%.*s' in '%s'",
                                    (int) len, copyfrom, elt);
                }
 -              if (*copyfrom == ')')
 -                      copyfrom++;
 +              if (*copyfrom != ')')
 +                      die("Missing ')' at the end of pathspec magic in '%s'", elt);
 +              copyfrom++;
        } else {
                /* shorthand */
                for (copyfrom = elt + 1;
                return prefix_path(prefix, prefixlen, copyfrom);
  }
  
 +/*
 + * N.B. get_pathspec() is deprecated in favor of the "struct pathspec"
 + * based interface - see pathspec_magic above.
 + *
 + * Arguments:
 + *  - prefix - a path relative to the root of the working tree
 + *  - pathspec - a list of paths underneath the prefix path
 + *
 + * Iterates over pathspec, prepending each path with prefix,
 + * and return the resulting list.
 + *
 + * If pathspec is empty, return a singleton list containing prefix.
 + *
 + * If pathspec and prefix are both empty, return an empty list.
 + *
 + * This is typically used by built-in commands such as add.c, in order
 + * to normalize argv arguments provided to the built-in into a list of
 + * paths to process, all relative to the root of the working tree.
 + */
  const char **get_pathspec(const char *prefix, const char **pathspec)
  {
        const char *entry = *pathspec;
@@@ -525,12 -497,6 +525,12 @@@ static const char *setup_explicit_git_d
                        set_git_work_tree(core_worktree);
                }
        }
 +      else if (!git_env_bool(GIT_IMPLICIT_WORK_TREE_ENVIRONMENT, 1)) {
 +              /* #16d */
 +              set_git_dir(gitdirenv);
 +              free(gitfile);
 +              return NULL;
 +      }
        else /* #2, #10 */
                set_git_work_tree(".");
  
@@@ -609,8 -575,6 +609,8 @@@ static const char *setup_bare_git_dir(c
        if (check_repository_format_gently(".", nongit_ok))
                return NULL;
  
 +      setenv(GIT_IMPLICIT_WORK_TREE_ENVIRONMENT, "0", 1);
 +
        /* --work-tree is set without --git-dir; use discovered one */
        if (getenv(GIT_WORK_TREE_ENVIRONMENT) || git_work_tree_cfg) {
                const char *gitdir;
@@@ -804,9 -768,9 +804,9 @@@ const char *setup_git_directory_gently(
  
        prefix = setup_git_directory_gently_1(nongit_ok);
        if (prefix)
 -              setenv("GIT_PREFIX", prefix, 1);
 +              setenv(GIT_PREFIX_ENVIRONMENT, prefix, 1);
        else
 -              setenv("GIT_PREFIX", "", 1);
 +              setenv(GIT_PREFIX_ENVIRONMENT, "", 1);
  
        if (startup_info) {
                startup_info->have_repository = !nongit_ok || !*nongit_ok;
@@@ -908,3 -872,15 +908,15 @@@ const char *resolve_gitdir(const char *
                return suspect;
        return read_gitfile(suspect);
  }
+ /* if any standard file descriptor is missing open it to /dev/null */
+ void sanitize_stdfds(void)
+ {
+       int fd = open("/dev/null", O_RDWR, 0);
+       while (fd != -1 && fd < 2)
+               fd = dup(fd);
+       if (fd == -1)
+               die_errno("open /dev/null or dup failed");
+       if (fd > 2)
+               close(fd);
+ }
diff --combined shell.c
index 1429870a8f29d3d8b8e4d1d7ba3e228d15a73759,d6fb9f05045bc11582259a6e1d6c1db0e198a66c..66350b220cc74abd383e10770f8325ba4e658e87
+++ b/shell.c
@@@ -6,7 -6,6 +6,7 @@@
  
  #define COMMAND_DIR "git-shell-commands"
  #define HELP_COMMAND COMMAND_DIR "/help"
 +#define NOLOGIN_COMMAND COMMAND_DIR "/no-interactive-login"
  
  static int do_generic_cmd(const char *me, char *arg)
  {
@@@ -66,18 -65,6 +66,18 @@@ static void run_shell(void
  {
        int done = 0;
        static const char *help_argv[] = { HELP_COMMAND, NULL };
 +
 +      if (!access(NOLOGIN_COMMAND, F_OK)) {
 +              /* Interactive login disabled. */
 +              const char *argv[] = { NOLOGIN_COMMAND, NULL };
 +              int status;
 +
 +              status = run_command_v_opt(argv, 0);
 +              if (status < 0)
 +                      exit(127);
 +              exit(status);
 +      }
 +
        /* Print help if enabled */
        run_command_v_opt(help_argv, RUN_SILENT_EXEC_FAILURE);
  
@@@ -147,7 -134,6 +147,6 @@@ int main(int argc, char **argv
        char *prog;
        const char **user_argv;
        struct commands *cmd;
-       int devnull_fd;
        int count;
  
        git_setup_gettext();
  
        /*
         * Always open file descriptors 0/1/2 to avoid clobbering files
-        * in die().  It also avoids not messing up when the pipes are
-        * dup'ed onto stdin/stdout/stderr in the child processes we spawn.
+        * in die().  It also avoids messing up when the pipes are dup'ed
+        * onto stdin/stdout/stderr in the child processes we spawn.
         */
-       devnull_fd = open("/dev/null", O_RDWR);
-       while (devnull_fd >= 0 && devnull_fd <= 2)
-               devnull_fd = dup(devnull_fd);
-       if (devnull_fd == -1)
-               die_errno("opening /dev/null failed");
-       close (devnull_fd);
+       sanitize_stdfds();
  
        /*
         * Special hack to pretend to be a CVS server