server-info.con commit general improvements (43abf13)
   1#include "cache.h"
   2#include "dir.h"
   3#include "repository.h"
   4#include "refs.h"
   5#include "object.h"
   6#include "commit.h"
   7#include "tag.h"
   8#include "packfile.h"
   9#include "object-store.h"
  10#include "strbuf.h"
  11
  12struct update_info_ctx {
  13        FILE *cur_fp;
  14        FILE *old_fp; /* becomes NULL if it differs from cur_fp */
  15        struct strbuf cur_sb;
  16        struct strbuf old_sb;
  17};
  18
  19static void uic_mark_stale(struct update_info_ctx *uic)
  20{
  21        fclose(uic->old_fp);
  22        uic->old_fp = NULL;
  23}
  24
  25static int uic_is_stale(const struct update_info_ctx *uic)
  26{
  27        return uic->old_fp == NULL;
  28}
  29
  30static int uic_printf(struct update_info_ctx *uic, const char *fmt, ...)
  31{
  32        va_list ap;
  33        int ret = -1;
  34
  35        va_start(ap, fmt);
  36
  37        if (uic_is_stale(uic)) {
  38                ret = vfprintf(uic->cur_fp, fmt, ap);
  39        } else {
  40                ssize_t r;
  41                struct strbuf *cur = &uic->cur_sb;
  42                struct strbuf *old = &uic->old_sb;
  43
  44                strbuf_reset(cur);
  45                strbuf_vinsertf(cur, 0, fmt, ap);
  46
  47                strbuf_reset(old);
  48                strbuf_grow(old, cur->len);
  49                r = fread(old->buf, 1, cur->len, uic->old_fp);
  50                if (r != cur->len || memcmp(old->buf, cur->buf, r))
  51                        uic_mark_stale(uic);
  52
  53                if (fwrite(cur->buf, 1, cur->len, uic->cur_fp) == cur->len)
  54                        ret = 0;
  55        }
  56
  57        va_end(ap);
  58
  59        return ret;
  60}
  61
  62/*
  63 * Create the file "path" by writing to a temporary file and renaming
  64 * it into place. The contents of the file come from "generate", which
  65 * should return non-zero if it encounters an error.
  66 */
  67static int update_info_file(char *path,
  68                        int (*generate)(struct update_info_ctx *),
  69                        int force)
  70{
  71        char *tmp = mkpathdup("%s_XXXXXX", path);
  72        int ret = -1;
  73        int fd = -1;
  74        FILE *to_close;
  75        struct update_info_ctx uic = {
  76                .cur_fp = NULL,
  77                .old_fp = NULL,
  78                .cur_sb = STRBUF_INIT,
  79                .old_sb = STRBUF_INIT
  80        };
  81
  82        safe_create_leading_directories(path);
  83        fd = git_mkstemp_mode(tmp, 0666);
  84        if (fd < 0)
  85                goto out;
  86        to_close = uic.cur_fp = fdopen(fd, "w");
  87        if (!uic.cur_fp)
  88                goto out;
  89        fd = -1;
  90
  91        /* no problem on ENOENT and old_fp == NULL, it's stale, now */
  92        if (!force)
  93                uic.old_fp = fopen_or_warn(path, "r");
  94
  95        /*
  96         * uic_printf will compare incremental comparison aginst old_fp
  97         * and mark uic as stale if needed
  98         */
  99        ret = generate(&uic);
 100        if (ret)
 101                goto out;
 102
 103        /* new file may be shorter than the old one, check here */
 104        if (!uic_is_stale(&uic)) {
 105                struct stat st;
 106                long new_len = ftell(uic.cur_fp);
 107                int old_fd = fileno(uic.old_fp);
 108
 109                if (new_len < 0) {
 110                        ret = -1;
 111                        goto out;
 112                }
 113                if (fstat(old_fd, &st) || (st.st_size != (size_t)new_len))
 114                        uic_mark_stale(&uic);
 115        }
 116
 117        uic.cur_fp = NULL;
 118        if (fclose(to_close))
 119                goto out;
 120
 121        if (uic_is_stale(&uic)) {
 122                if (adjust_shared_perm(tmp) < 0)
 123                        goto out;
 124                if (rename(tmp, path) < 0)
 125                        goto out;
 126        } else {
 127                unlink(tmp);
 128        }
 129        ret = 0;
 130
 131out:
 132        if (ret) {
 133                error_errno("unable to update %s", path);
 134                if (uic.cur_fp)
 135                        fclose(uic.cur_fp);
 136                else if (fd >= 0)
 137                        close(fd);
 138                unlink(tmp);
 139        }
 140        free(tmp);
 141        if (uic.old_fp)
 142                fclose(uic.old_fp);
 143        strbuf_release(&uic.old_sb);
 144        strbuf_release(&uic.cur_sb);
 145        return ret;
 146}
 147
 148static int add_info_ref(const char *path, const struct object_id *oid,
 149                        int flag, void *cb_data)
 150{
 151        struct update_info_ctx *uic = cb_data;
 152        struct object *o = parse_object(the_repository, oid);
 153        if (!o)
 154                return -1;
 155
 156        if (uic_printf(uic, "%s %s\n", oid_to_hex(oid), path) < 0)
 157                return -1;
 158
 159        if (o->type == OBJ_TAG) {
 160                o = deref_tag(the_repository, o, path, 0);
 161                if (o)
 162                        if (uic_printf(uic, "%s %s^{}\n",
 163                                oid_to_hex(&o->oid), path) < 0)
 164                                return -1;
 165        }
 166        return 0;
 167}
 168
 169static int generate_info_refs(struct update_info_ctx *uic)
 170{
 171        return for_each_ref(add_info_ref, uic);
 172}
 173
 174static int update_info_refs(int force)
 175{
 176        char *path = git_pathdup("info/refs");
 177        int ret = update_info_file(path, generate_info_refs, force);
 178        free(path);
 179        return ret;
 180}
 181
 182/* packs */
 183static struct pack_info {
 184        struct packed_git *p;
 185        int old_num;
 186        int new_num;
 187} **info;
 188static int num_pack;
 189
 190static struct pack_info *find_pack_by_name(const char *name)
 191{
 192        int i;
 193        for (i = 0; i < num_pack; i++) {
 194                struct packed_git *p = info[i]->p;
 195                if (!strcmp(pack_basename(p), name))
 196                        return info[i];
 197        }
 198        return NULL;
 199}
 200
 201/* Returns non-zero when we detect that the info in the
 202 * old file is useless.
 203 */
 204static int parse_pack_def(const char *packname, int old_cnt)
 205{
 206        struct pack_info *i = find_pack_by_name(packname);
 207        if (i) {
 208                i->old_num = old_cnt;
 209                return 0;
 210        }
 211        else {
 212                /* The file describes a pack that is no longer here */
 213                return 1;
 214        }
 215}
 216
 217/* Returns non-zero when we detect that the info in the
 218 * old file is useless.
 219 */
 220static int read_pack_info_file(const char *infofile)
 221{
 222        FILE *fp;
 223        struct strbuf line = STRBUF_INIT;
 224        int old_cnt = 0;
 225        int stale = 1;
 226
 227        fp = fopen_or_warn(infofile, "r");
 228        if (!fp)
 229                return 1; /* nonexistent is not an error. */
 230
 231        while (strbuf_getline(&line, fp) != EOF) {
 232                const char *arg;
 233
 234                if (!line.len)
 235                        continue;
 236
 237                if (skip_prefix(line.buf, "P ", &arg)) {
 238                        /* P name */
 239                        if (parse_pack_def(arg, old_cnt++))
 240                                goto out_stale;
 241                } else if (line.buf[0] == 'D') {
 242                        /* we used to emit D but that was misguided. */
 243                        goto out_stale;
 244                } else if (line.buf[0] == 'T') {
 245                        /* we used to emit T but nobody uses it. */
 246                        goto out_stale;
 247                } else {
 248                        error("unrecognized: %s", line.buf);
 249                }
 250        }
 251        stale = 0;
 252
 253 out_stale:
 254        strbuf_release(&line);
 255        fclose(fp);
 256        return stale;
 257}
 258
 259static int compare_info(const void *a_, const void *b_)
 260{
 261        struct pack_info *const *a = a_;
 262        struct pack_info *const *b = b_;
 263
 264        if (0 <= (*a)->old_num && 0 <= (*b)->old_num)
 265                /* Keep the order in the original */
 266                return (*a)->old_num - (*b)->old_num;
 267        else if (0 <= (*a)->old_num)
 268                /* Only A existed in the original so B is obviously newer */
 269                return -1;
 270        else if (0 <= (*b)->old_num)
 271                /* The other way around. */
 272                return 1;
 273
 274        /* then it does not matter but at least keep the comparison stable */
 275        if ((*a)->p == (*b)->p)
 276                return 0;
 277        else if ((*a)->p < (*b)->p)
 278                return -1;
 279        else
 280                return 1;
 281}
 282
 283static void init_pack_info(const char *infofile, int force)
 284{
 285        struct packed_git *p;
 286        int stale;
 287        int i;
 288        size_t alloc = 0;
 289
 290        for (p = get_all_packs(the_repository); p; p = p->next) {
 291                /* we ignore things on alternate path since they are
 292                 * not available to the pullers in general.
 293                 */
 294                if (!p->pack_local || !file_exists(p->pack_name))
 295                        continue;
 296
 297                i = num_pack++;
 298                ALLOC_GROW(info, num_pack, alloc);
 299                info[i] = xcalloc(1, sizeof(struct pack_info));
 300                info[i]->p = p;
 301                info[i]->old_num = -1;
 302        }
 303
 304        if (infofile && !force)
 305                stale = read_pack_info_file(infofile);
 306        else
 307                stale = 1;
 308
 309        for (i = 0; i < num_pack; i++)
 310                if (stale)
 311                        info[i]->old_num = -1;
 312
 313        /* renumber them */
 314        QSORT(info, num_pack, compare_info);
 315        for (i = 0; i < num_pack; i++)
 316                info[i]->new_num = i;
 317}
 318
 319static void free_pack_info(void)
 320{
 321        int i;
 322        for (i = 0; i < num_pack; i++)
 323                free(info[i]);
 324        free(info);
 325}
 326
 327static int write_pack_info_file(struct update_info_ctx *uic)
 328{
 329        int i;
 330        for (i = 0; i < num_pack; i++) {
 331                if (uic_printf(uic, "P %s\n", pack_basename(info[i]->p)) < 0)
 332                        return -1;
 333        }
 334        if (uic_printf(uic, "\n") < 0)
 335                return -1;
 336        return 0;
 337}
 338
 339static int update_info_packs(int force)
 340{
 341        char *infofile = mkpathdup("%s/info/packs", get_object_directory());
 342        int ret;
 343
 344        init_pack_info(infofile, force);
 345        ret = update_info_file(infofile, write_pack_info_file, force);
 346        free_pack_info();
 347        free(infofile);
 348        return ret;
 349}
 350
 351/* public */
 352int update_server_info(int force)
 353{
 354        /* We would add more dumb-server support files later,
 355         * including index of available pack files and their
 356         * intended audiences.
 357         */
 358        int errs = 0;
 359
 360        errs = errs | update_info_refs(force);
 361        errs = errs | update_info_packs(force);
 362
 363        /* remove leftover rev-cache file if there is any */
 364        unlink_or_warn(git_path("info/rev-cache"));
 365
 366        return errs;
 367}