fork of https://github.com/sourcegraph/zoekt
1package main
2
3import (
4 "encoding/json"
5 "fmt"
6 "os"
7 "path/filepath"
8
9 "github.com/sourcegraph/zoekt"
10 "github.com/sourcegraph/zoekt/index"
11 "golang.org/x/sys/unix"
12)
13
14// mergeMeta updates the .meta files for the shards on disk for o.
15//
16// This process is best effort. If anything fails we return on the first
17// failure. This means you might have an inconsistent state on disk if an
18// error is returned. It is recommended to fallback to re-indexing in that
19// case.
20func mergeMeta(o *index.Options) error {
21 todo := map[string]string{}
22 for _, fn := range o.FindAllShards() {
23 repos, md, err := index.ReadMetadataPath(fn)
24 if err != nil {
25 return err
26 }
27
28 var repo *zoekt.Repository
29 for _, cand := range repos {
30 if cand.Name == o.RepositoryDescription.Name {
31 repo = cand
32 break
33 }
34 }
35
36 if repo == nil {
37 return fmt.Errorf("mergeMeta: could not find repo %s in shard %s", o.RepositoryDescription.Name, fn)
38 }
39
40 if updated, err := repo.MergeMutable(&o.RepositoryDescription); err != nil {
41 return err
42 } else if !updated {
43 // This shouldn't happen, but ignore it if it does. We may be working on
44 // an interrupted shard. This helps us converge to something correct.
45 continue
46 }
47
48 var merged interface{}
49 if md.IndexFormatVersion >= 17 {
50 merged = repos
51 } else {
52 // <= v16 expects a single repo, not a list.
53 merged = repo
54 }
55
56 dst := fn + ".meta"
57 tmp, err := jsonMarshalTmpFile(merged, dst)
58 if err != nil {
59 return err
60 }
61
62 todo[tmp] = dst
63
64 // if we fail to rename, this defer will attempt to remove the tmp file.
65 defer os.Remove(tmp)
66 }
67
68 // best effort once we get here. Rename everything. Return error of last
69 // failure.
70 var renameErr error
71 for tmp, dst := range todo {
72 if err := os.Rename(tmp, dst); err != nil {
73 renameErr = err
74 }
75 }
76
77 return renameErr
78}
79
80// jsonMarshalFileTmp marshals v to the temporary file p + ".*.tmp" and
81// returns the file name.
82//
83// Note: .tmp is the same suffix used by Builder. indexserver knows to clean
84// them up.
85func jsonMarshalTmpFile(v interface{}, p string) (_ string, err error) {
86 b, err := json.Marshal(v)
87 if err != nil {
88 return "", err
89 }
90
91 f, err := os.CreateTemp(filepath.Dir(p), filepath.Base(p)+".*.tmp")
92 if err != nil {
93 return "", err
94 }
95 defer func() {
96 f.Close()
97 if err != nil {
98 _ = os.Remove(f.Name())
99 }
100 }()
101
102 if err := f.Chmod(0o666 &^ umask); err != nil {
103 return "", err
104 }
105 if _, err := f.Write(b); err != nil {
106 return "", err
107 }
108
109 return f.Name(), f.Close()
110}
111
112// respect process umask. build does this.
113var umask os.FileMode
114
115func init() {
116 umask = os.FileMode(unix.Umask(0))
117 unix.Umask(int(umask))
118}