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