GitRoot
craft your forge, build your project, grow your community freely
1// SPDX-FileCopyrightText: 2025 Romain Maneschi <romain@gitroot.dev>
2//
3// SPDX-License-Identifier: EUPL-1.2
4
5package plugin
6
7import (
8 "bytes"
9 "context"
10 "crypto/rand"
11 "errors"
12 "fmt"
13 "os"
14 "path/filepath"
15 "unicode/utf16"
16
17 "github.com/go-git/go-git/v5/plumbing"
18 "github.com/samber/oops"
19 "github.com/tetratelabs/wazero"
20 "github.com/tetratelabs/wazero/api"
21 "github.com/tetratelabs/wazero/imports/assemblyscript"
22 "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
23 "github.com/tetratelabs/wazero/sys"
24 pluginLib "gitroot.dev/libs/golang/plugin"
25 grfs "gitroot.dev/server/fs"
26 "gitroot.dev/server/logger"
27 "gitroot.dev/server/repository"
28)
29
30type runtimeInputsKind int
31
32const (
33 runtimeInputsKindDiff runtimeInputsKind = iota
34 runtimeInputsKindWorktree runtimeInputsKind = iota
35)
36
37type runtimeInputs struct {
38 ctx context.Context
39 repoName string
40 kind runtimeInputsKind
41 plugins []Plugin
42 commands []CommandForDiff
43}
44
45type runtime struct {
46 ctx context.Context
47 manager *Manager
48 logger *logger.Logger
49 repo *repository.GitRootRepository
50 repoWriter *repository.GitRootRepositoryWrite
51 plugin Plugin
52 pluginRun PluginRun
53 command *CommandForDiff
54 wazRun wazero.Runtime
55 fsWorktree grfs.UpdatableFs
56 fsWebcontent grfs.UpdatableFs
57 fsCache grfs.UpdatableFs
58 commitHook func(h plumbing.Hash)
59}
60
61func newRuntime(ctx context.Context, manager *Manager, logger *logger.Logger) (*runtime, error) {
62 r := &runtime{
63 ctx: ctx,
64 manager: manager,
65 logger: logger,
66 wazRun: wazero.NewRuntime(ctx),
67 fsWorktree: grfs.NewUpdatableFs(ctx, nil),
68 fsWebcontent: grfs.NewUpdatableFs(ctx, nil),
69 fsCache: grfs.NewUpdatableFs(ctx, nil),
70 commitHook: nil,
71 }
72
73 _, err := assemblyscript.Instantiate(ctx, r.wazRun)
74 if err != nil {
75 return nil, err
76 }
77
78 _, err = r.wazRun.
79 NewHostModuleBuilder("gitroot").
80 NewFunctionBuilder().WithFunc(r.ForgeConf).Export("forgeConf").
81 NewFunctionBuilder().WithFunc(r.ModifyContentBuilder(false)).Export("modifyContent").
82 NewFunctionBuilder().WithFunc(r.ModifyContentBuilder(true)).Export("modifyContentAS").
83 NewFunctionBuilder().WithFunc(r.ModifyWebContentBuilder(false)).Export("modifyWebContent").
84 NewFunctionBuilder().WithFunc(r.ModifyWebContentBuilder(true)).Export("modifyWebContentAS").
85 NewFunctionBuilder().WithFunc(r.ReplaceWebContentBuilder(false)).Export("replaceWebContent").
86 NewFunctionBuilder().WithFunc(r.ReplaceWebContentBuilder(true)).Export("replaceWebContentAS").
87 NewFunctionBuilder().WithFunc(r.ModifyCacheContentBuilder(false)).Export("modifyCacheContent").
88 NewFunctionBuilder().WithFunc(r.ModifyCacheContentBuilder(true)).Export("modifyCacheContentAS").
89 NewFunctionBuilder().WithFunc(r.CommitAllBuilder(false)).Export("commitAll").
90 NewFunctionBuilder().WithFunc(r.CommitAllBuilder(true)).Export("commitAllAS").
91 NewFunctionBuilder().WithFunc(r.DiffWithParentBuilder(false)).Export("diffWithParent").
92 NewFunctionBuilder().WithFunc(r.DiffWithParentBuilder(true)).Export("diffWithParentAS").
93 NewFunctionBuilder().WithFunc(r.LogBuilder(false)).Export("log").
94 NewFunctionBuilder().WithFunc(r.LogBuilder(true)).Export("logAS").
95 NewFunctionBuilder().WithFunc(r.LogErrorBuilder(false)).Export("logError").
96 NewFunctionBuilder().WithFunc(r.LogErrorBuilder(true)).Export("logErrorAS").
97 NewFunctionBuilder().WithFunc(r.MergeBuilder(false)).Export("merge").
98 NewFunctionBuilder().WithFunc(r.MergeBuilder(true)).Export("mergeAS").
99 Instantiate(ctx)
100 if err != nil {
101 return nil, err
102 }
103 _, err = wasi_snapshot_preview1.Instantiate(ctx, r.wazRun)
104 if err != nil {
105 return nil, err
106 }
107 return r, nil
108}
109
110func (r *runtime) listen(c chan runtimeInputs) {
111 for i := range c {
112 r.logger.Debug("listen call", logger.NewLoggerPair("repo", i.repoName), logger.NewLoggerPair("isDiff", i.kind == runtimeInputsKindDiff), logger.NewLoggerPair("isWorktree", i.kind == runtimeInputsKindWorktree))
113 repo, err := r.manager.repoManager.Open(logger.AddCaller(r.ctx, "runtime.listen"), i.repoName)
114 if err != nil {
115 r.logger.Error("open error in listen", err)
116 repo.Close()
117 continue
118 }
119 repoWriter, err := repo.WillWrite(plumbing.HEAD) //TODO should mount good branc directly inside i.commands
120 if err != nil {
121 r.logger.Error("will write error in listen", err, logger.NewLoggerPair("repo", repo.Name()))
122 repo.Close()
123 continue
124 }
125
126 if i.kind == runtimeInputsKindDiff {
127 timerStop := r.logger.Time(fmt.Sprintf("Timer %s all plugins", repo.Name()))
128 if err := r.start(i.ctx, repo, repoWriter, i.plugins, i.commands); err != nil {
129 r.logger.Error("start error", err)
130 }
131 timerStop()
132 } else if i.kind == runtimeInputsKindWorktree {
133 r.logger.Info("start worktree", logger.NewLoggerPair("repo", repo.Name()))
134 timerStop := r.logger.Time(fmt.Sprintf("Timer %s all plugins worktree", repo.Name()))
135 if err := r.worktree(i.ctx, repo, repoWriter, i.plugins, i.commands); err != nil {
136 r.logger.Error("start error", err)
137 }
138 timerStop()
139 }
140 repo.Close()
141 }
142}
143
144func (r *runtime) conf(ctx context.Context, plugin Plugin) ([]pluginLib.PluginRun, error) {
145 r.fsWorktree.Clear()
146 r.fsWebcontent.Clear()
147 r.fsCache.Clear()
148 r.repo = nil
149 r.plugin = plugin
150 r.command = nil
151 timerStop := r.logger.Time(fmt.Sprintf("Timer %s", plugin.Name))
152
153 r.logger.Debug("start plugin conf", logger.NewLoggerPair("name", plugin.Name))
154 m := r.wazRun.Module(plugin.Name)
155 if m == nil {
156 r.logger.Debug("instantiate plugin conf", logger.NewLoggerPair("name", plugin.Name))
157 config := wazero.NewModuleConfig().
158 WithStdout(os.Stdout).WithStderr(os.Stderr).
159 WithSysWalltime().
160 WithSysNanotime().
161 WithRandSource(rand.Reader).
162 WithFSConfig(
163 wazero.NewFSConfig().
164 WithFSMount(r.fsWorktree, "worktree").
165 WithFSMount(r.fsWebcontent, "webcontent").
166 WithFSMount(r.fsCache, "cache"))
167 mod, err := r.wazRun.InstantiateWithConfig(ctx, plugin.content, config.WithName(plugin.Name).WithStartFunctions("_initialize", "install"))
168 if err != nil {
169 if exitErr, ok := err.(*sys.ExitError); ok && exitErr.ExitCode() != 0 {
170 fmt.Fprintf(os.Stderr, "exit_code: %d\n", exitErr.ExitCode())
171 return nil, err
172 } else if !ok {
173 return nil, err
174 }
175 }
176 m = mod
177 } else {
178 r.logger.Debug("already exist conf", logger.NewLoggerPair("name", plugin.Name))
179 }
180 conf, err := r.callPluginForConf(ctx, m, r.logger.NewSubLogger(plugin.Name))
181 if err != nil {
182 r.logger.Error("finish plugin conf with error", err, logger.NewLoggerPair("name", plugin.Name))
183 }
184
185 r.logger.Debug("finish plugin conf", logger.NewLoggerPair("name", plugin.Name))
186 timerStop()
187 return conf, err
188}
189
190func (r *runtime) start(ctx context.Context, repo *repository.GitRootRepository, repoWriter *repository.GitRootRepositoryWrite, plugins []Plugin, commands []CommandForDiff) error {
191 r.fsWorktree.Update(repoWriter.ToFs(ctx))
192 r.fsWebcontent.Update(r.manager.conf.DataWeb(repo.Name()))
193
194 r.repo = repo
195 r.repoWriter = repoWriter
196 r.command = nil
197
198 r.commitHook = func(hash plumbing.Hash) {
199 command, err := repoWriter.GetLastCommit(hash)
200 if err != nil {
201 r.logger.Error("can't GetLastCommit in start runtime", err)
202 return
203 }
204 newCmd, err := CommandForDiffFromCommitCmd(ctx, repo, r.plugin, command, *r.command)
205 if err != nil {
206 r.logger.Error("can't CommandForDiffFromCommitCmd in start runtime", err)
207 return
208 }
209 commands = append(commands, newCmd)
210 }
211 defer func() {
212 r.commitHook = nil
213 }()
214
215 for _, plugin := range plugins {
216 r.fsCache.Update(r.manager.conf.Cache(repo.Name(), plugin.Name))
217 r.plugin = plugin
218 timerStop := r.logger.Time(fmt.Sprintf("Timer %s", plugin.Name))
219 r.logger.Debug("start plugin", logger.NewLoggerPair("name", plugin.Name))
220 m := r.wazRun.Module(plugin.Name)
221 if m == nil {
222 r.logger.Debug("instantiate plugin", logger.NewLoggerPair("name", plugin.Name))
223 config := wazero.NewModuleConfig().
224 WithSysWalltime().
225 WithSysNanotime().
226 WithRandSource(rand.Reader).
227 WithStdout(os.Stdout).WithStderr(os.Stderr).
228 WithFSConfig(wazero.NewFSConfig().
229 WithFSMount(r.fsWorktree, "worktree").
230 WithFSMount(r.fsWebcontent, "webcontent").
231 WithFSMount(r.fsCache, "cache"))
232 mod, err := r.wazRun.InstantiateWithConfig(ctx, plugin.content, config.WithName(plugin.Name).WithStartFunctions("_initialize", "install"))
233 if err != nil {
234 if exitErr, ok := err.(*sys.ExitError); ok && exitErr.ExitCode() != 0 {
235 fmt.Fprintf(os.Stderr, "exit_code: %d\n", exitErr.ExitCode())
236 return nil
237 } else if !ok {
238 return err
239 }
240 }
241 m = mod
242 } else {
243 r.logger.Debug("already exist", logger.NewLoggerPair("name", plugin.Name))
244 }
245 l := logger.NewLogger(logger.WASM)
246 l.Debug("memory before", logger.NewLoggerPair("size", m.Memory().Size()), logger.NewLoggerPair("plugin", plugin.Name))
247 cp := callPlugin{
248 manager: r.manager,
249 plugin: plugin,
250 repo: repo,
251 repoWriter: repoWriter,
252 module: m,
253 logger: r.logger.NewSubLogger(plugin.Name),
254 }
255 if err := cp.callPluginForDiff(ctx, r, commands); err != nil {
256 r.logger.Error("finish plugin with error", err, logger.NewLoggerPair("name", plugin.Name))
257 }
258 l.Debug("memory after", logger.NewLoggerPair("size", m.Memory().Size()), logger.NewLoggerPair("plugin", plugin.Name))
259 r.logger.Debug("finish plugin", logger.NewLoggerPair("name", plugin.Name))
260 timerStop()
261 }
262 return nil
263}
264
265func (r *runtime) ForgeConf(_ context.Context, m api.Module) uint64 {
266 // TODO need issues/a716-conf-external-addr.md to work
267 _, ptrSize, err := r.sendData(m, "{\"domain\": \"\"}") //TODO free ptr (first arg)
268 if err != nil {
269 r.logger.Error("ForgeConf can't send data", err)
270 return 0
271 }
272 return ptrSize
273}
274
275func (r *runtime) ModifyContentBuilder(forAS bool) interface{} {
276 f := func(filename string, content string) {
277 if ok, err := checkWrite(r.pluginRun.write.git, r.fsWorktree, filename); !ok {
278 r.logger.Error("plugin can't write in git", err, logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("path", filename))
279 return
280 }
281 r.logger.Debug("modifyContent", logger.NewLoggerPair("file", filename))
282 if err := r.repoWriter.Write(filename, []byte(content)); err != nil {
283 r.logger.Error("modifyContent can't open file", err, logger.NewLoggerPair("filepath", filename))
284 return
285 }
286 }
287
288 if !forAS {
289 return func(_ context.Context, m api.Module, filenamePtr, filenameSize, contentPtr, contentSize uint32) {
290 filename, err := r.readString(m, "ModifyContent filename", filenamePtr, filenameSize)
291 if err != nil {
292 return
293 }
294 content, err := r.readString(m, "ModifyContent content", contentPtr, contentSize)
295 if err != nil {
296 return
297 }
298 f(filename, content)
299 }
300 } else {
301 return func(_ context.Context, m api.Module, filenamePtr, contentPtr uint32) {
302 filename, err := r.readASString(m, "ModifyContent filename", filenamePtr)
303 if err != nil {
304 return
305 }
306 content, err := r.readASString(m, "ModifyContent content", contentPtr)
307 if err != nil {
308 return
309 }
310 f(filename, content)
311 }
312 }
313}
314
315func (r *runtime) ModifyWebContentBuilder(forAS bool) interface{} {
316 f := func(filename string, content string) {
317 if ok, err := checkWrite(r.pluginRun.write.web, r.fsWebcontent, filename); !ok {
318 r.logger.Error("plugin can't write in web", err, logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("path", filename))
319 return
320 }
321 fullPath := r.repo.PathDataWeb(filename)
322 dir, _ := filepath.Split(fullPath)
323 if err := os.MkdirAll(dir, os.ModePerm); err != nil {
324 r.logger.Error("modifyWebContent can't mkdirAll", err, logger.NewLoggerPair("filepath", filename))
325 return
326 }
327 if err := os.WriteFile(fullPath, []byte(content), 0666); err != nil {
328 r.logger.Error("modifyWebContent can't open file", err, logger.NewLoggerPair("filepath", filename))
329 return
330 }
331 r.logger.Debug("Write in web", logger.NewLoggerPair("fullPath", fullPath))
332 }
333
334 if !forAS {
335 return func(_ context.Context, m api.Module, filenamePtr, filenameSize, contentPtr, contentSize uint32) {
336 filename, err := r.readString(m, "ModifyWebContent filename", filenamePtr, filenameSize)
337 if err != nil {
338 return
339 }
340 content, err := r.readString(m, "ModifyWebContent content", contentPtr, contentSize)
341 if err != nil {
342 return
343 }
344 f(filename, content)
345 }
346 } else {
347 return func(_ context.Context, m api.Module, filenamePtr, contentPtr uint32) {
348 filename, err := r.readASString(m, "ModifyWebContent filename", filenamePtr)
349 if err != nil {
350 return
351 }
352 content, err := r.readASString(m, "ModifyWebContent content", contentPtr)
353 if err != nil {
354 return
355 }
356 f(filename, content)
357 }
358 }
359}
360
361func (r *runtime) ReplaceWebContentBuilder(forAS bool) interface{} {
362 f := func(filename string, oldContent string, content string) {
363 if ok, err := checkWrite(r.pluginRun.write.web, r.fsWebcontent, filename); !ok {
364 r.logger.Error("plugin can't write in web", err, logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("path", filename))
365 return
366 }
367 fullPath := r.repo.PathDataWeb(filename)
368 dir, _ := filepath.Split(fullPath)
369 if err := os.MkdirAll(dir, os.ModePerm); err != nil {
370 r.logger.Error("ReplaceWebContentBuilder can't mkdirAll", err, logger.NewLoggerPair("filepath", filename))
371 return
372 }
373 fileContent, err := os.ReadFile(fullPath)
374 if err != nil {
375 r.logger.Error("ReplaceWebContentBuilder can't ReadFile", err, logger.NewLoggerPair("filepath", filename))
376 return
377 }
378 if err := os.WriteFile(fullPath, bytes.Replace(fileContent, []byte(oldContent), []byte(content), 1), 0666); err != nil {
379 r.logger.Error("ReplaceWebContentBuilder can't open file", err, logger.NewLoggerPair("filepath", filename))
380 return
381 }
382 r.logger.Debug("Write in web", logger.NewLoggerPair("fullPath", fullPath))
383 }
384
385 if !forAS {
386 return func(_ context.Context, m api.Module, filenamePtr, filenameSize, oldContentPtr, oldContentSize, contentPtr, contentSize uint32) {
387 filename, err := r.readString(m, "ReplaceWebContentBuilder filename", filenamePtr, filenameSize)
388 if err != nil {
389 return
390 }
391 oldContent, err := r.readString(m, "ReplaceWebContentBuilder oldContent", oldContentPtr, oldContentSize)
392 if err != nil {
393 return
394 }
395 content, err := r.readString(m, "ReplaceWebContentBuilder content", contentPtr, contentSize)
396 if err != nil {
397 return
398 }
399 f(filename, oldContent, content)
400 }
401 } else {
402 return func(_ context.Context, m api.Module, filenamePtr, oldContentPtr, contentPtr uint32) {
403 filename, err := r.readASString(m, "ReplaceWebContentBuilder filename", filenamePtr)
404 if err != nil {
405 return
406 }
407 oldContent, err := r.readASString(m, "ReplaceWebContentBuilder oldContent", oldContentPtr)
408 if err != nil {
409 return
410 }
411 content, err := r.readASString(m, "ReplaceWebContentBuilder content", contentPtr)
412 if err != nil {
413 return
414 }
415 f(filename, oldContent, content)
416 }
417 }
418}
419
420func (r *runtime) ModifyCacheContentBuilder(forAS bool) interface{} {
421 f := func(filename string, content string) {
422 fullPath := filepath.Join(r.manager.conf.PathCache(), r.repo.Name(), r.plugin.Name, filename)
423 dir, _ := filepath.Split(fullPath)
424 if err := os.MkdirAll(dir, os.ModePerm); err != nil {
425 r.logger.Error("ModifyCacheContent can't mkdirAll", err, logger.NewLoggerPair("filepath", filename))
426 return
427 }
428 if err := os.WriteFile(fullPath, []byte(content), 0666); err != nil {
429 r.logger.Error("ModifyCacheContent can't open file", err, logger.NewLoggerPair("filepath", filename))
430 return
431 }
432 r.logger.Debug("Write in cache", logger.NewLoggerPair("fullPath", fullPath))
433 }
434 if !forAS {
435 return func(_ context.Context, m api.Module, filenamePtr, filenameSize, contentPtr, contentSize uint32) {
436 filename, err := r.readString(m, "ModifyCacheContent filename", filenamePtr, filenameSize)
437 if err != nil {
438 return
439 }
440 content, err := r.readString(m, "ModifyCacheContent content", contentPtr, contentSize)
441 if err != nil {
442 return
443 }
444 f(filename, content)
445 }
446 } else {
447 return func(_ context.Context, m api.Module, filenamePtr, contentPtr uint32) {
448 filename, err := r.readASString(m, "ModifyCacheContent filename", filenamePtr)
449 if err != nil {
450 return
451 }
452 content, err := r.readASString(m, "ModifyCacheContent content", contentPtr)
453 if err != nil {
454 return
455 }
456 f(filename, content)
457 }
458 }
459}
460
461func (r *runtime) CommitAllBuilder(forAS bool) interface{} {
462 f := func(msg string, err error) {
463 if err != nil {
464 return
465 }
466 if h, err := r.repoWriter.CommitAll(msg, r.plugin.commiter); err != nil {
467 r.logger.Error("commitAll can't commit", err)
468 return
469 } else {
470 r.logger.Debug("Commit", logger.NewLoggerPair("message", msg), logger.NewLoggerPair("hash", h.String()))
471 if r.commitHook != nil && !h.IsZero() {
472 r.commitHook(h)
473 }
474 }
475 }
476 if !forAS {
477 return func(_ context.Context, m api.Module, offset, byteCount uint32) {
478 f(r.readString(m, "commitAll", offset, byteCount))
479 }
480 } else {
481 return func(_ context.Context, m api.Module, offset uint32) {
482 f(r.readASString(m, "commitAll", offset))
483 }
484 }
485}
486
487func (r *runtime) DiffWithParentBuilder(forAS bool) interface{} {
488 f := func(m api.Module, filepath string, hash string) uint64 {
489 r.logger.Info("In diffWithParent before found ancestor")
490 diffStr, err := r.repoWriter.GetDiff(plumbing.NewHash(hash), filepath)
491 if err != nil {
492 r.logger.Error("GetDiff", err)
493 return 0
494 }
495 r.logger.Debug("In diffWithParent send diff", logger.NewLoggerPair("diff", diffStr))
496 _, ptrSize, err := r.sendData(m, diffStr) //TODO free ptr (first arg)
497 if err != nil {
498 r.logger.Error("can't sendData", err, logger.NewLoggerPair("message", diffStr))
499 return 0
500 }
501 return ptrSize
502 }
503 if !forAS {
504 return func(_ context.Context, m api.Module, filenamePtr, filenameSize, hashPtr, hashSize uint32) uint64 {
505 filename, err := r.readString(m, "DiffWithParent filename", filenamePtr, filenameSize)
506 if err != nil {
507 return 0
508 }
509 hash, err := r.readString(m, "DiffWithParent hash", hashPtr, hashSize)
510 if err != nil {
511 return 0
512 }
513 return f(m, filename, hash)
514 }
515 } else {
516 return func(_ context.Context, m api.Module, filenamePtr, hashPtr uint32) uint64 {
517 filename, err := r.readASString(m, "DiffWithParent filename", filenamePtr)
518 if err != nil {
519 return 0
520 }
521 hash, err := r.readASString(m, "DiffWithParent hash", hashPtr)
522 if err != nil {
523 return 0
524 }
525 return f(m, filename, hash)
526 }
527 }
528}
529
530func (r *runtime) LogBuilder(forAS bool) interface{} {
531 f := func(msg string, err error) {
532 if err != nil {
533 return
534 }
535 r.logger.Debug(msg, logger.NewLoggerPair("repo", r.repo.Name()), logger.NewLoggerPair("plugin", r.plugin.Name))
536 }
537 if !forAS {
538 return func(_ context.Context, m api.Module, offset, byteCount uint32) {
539 f(r.readString(m, "log", offset, byteCount))
540 }
541 } else {
542 return func(_ context.Context, m api.Module, offset uint32) {
543 f(r.readASString(m, "log", offset))
544 }
545 }
546}
547
548func (r *runtime) LogErrorBuilder(forAS bool) interface{} {
549 f := func(msg string, err string) {
550 r.logger.Error(msg, errors.New(err), logger.NewLoggerPair("repo", r.repo.Name()), logger.NewLoggerPair("plugin", r.plugin.Name))
551 }
552 if !forAS {
553 return func(_ context.Context, m api.Module, offset, byteCount, errPtr, errSize uint32) {
554 msg, err := r.readString(m, "LogError msg", offset, byteCount)
555 if err != nil {
556 return
557 }
558 errMsg, err := r.readString(m, "LogError err", errPtr, errSize)
559 if err != nil {
560 return
561 }
562 f(msg, errMsg)
563 }
564 } else {
565 return func(_ context.Context, m api.Module, offset, errPtr uint32) {
566 msg, err := r.readASString(m, "LogError msg", offset)
567 if err != nil {
568 return
569 }
570 errMsg, err := r.readASString(m, "LogError err", errPtr)
571 if err != nil {
572 return
573 }
574 f(msg, errMsg)
575 }
576 }
577}
578
579func (r *runtime) MergeBuilder(forAS bool) interface{} {
580 f := func(from string, to string) {
581 r.logger.Debug("try to merge", logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to))
582 if err := r.repoWriter.Merge(from, to, r.plugin.commiter, r.command.pusher); err != nil {
583 r.logger.Error("can't Merge", err, logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to))
584 }
585 }
586 if !forAS {
587 return func(_ context.Context, m api.Module, fromPtr, fromSize, toPtr, toSize uint32) {
588 from, err := r.readString(m, "Merge from", fromPtr, fromSize)
589 if err != nil {
590 return
591 }
592 to, err := r.readString(m, "Merge to", toPtr, toSize)
593 if err != nil {
594 return
595 }
596 f(from, to)
597 }
598 } else {
599 return func(_ context.Context, m api.Module, fromPtr, toPtr uint32) {
600 from, err := r.readASString(m, "Merge from", fromPtr)
601 if err != nil {
602 return
603 }
604 to, err := r.readASString(m, "Merge to", toPtr)
605 if err != nil {
606 return
607 }
608 f(from, to)
609 }
610 }
611}
612
613func (r *runtime) Close() error {
614 return r.wazRun.Close(r.ctx)
615}
616
617func (r *runtime) sendData(module api.Module, message string) (ptr uint32, ptrSize uint64, err error) {
618 malloc := module.ExportedFunction("malloc")
619
620 s := uint64(len(message))
621
622 results, err := malloc.Call(r.ctx, s)
623 if err != nil {
624 return 0, 0, oops.Wrapf(err, "can't malloc memory")
625 }
626 ptrA := results[0]
627
628 // The pointer is a linear memory offset, which is where we write the name.
629 if !module.Memory().WriteString(uint32(ptrA), message) {
630 return 0, 0, oops.Errorf("can't write memory")
631 }
632
633 return uint32(ptrA), (ptrA << uint64(32)) | s, nil
634}
635
636func (r *runtime) readString(m api.Module, errorMsg string, ptr uint32, size uint32) (string, error) {
637 txt, ok := m.Memory().Read(ptr, size)
638 if !ok {
639 err := errors.New("Memory.Read fail")
640 r.logger.Error(errorMsg, err, logger.NewLoggerPair("ptr", ptr), logger.NewLoggerPair("size", size))
641 return "", err
642 }
643 return string(txt), nil
644}
645
646func (r *runtime) readASString(m api.Module, errorMsg string, offset uint32) (string, error) {
647 // Length is four bytes before pointer.
648 byteCount, ok := m.Memory().ReadUint32Le(offset - 4)
649 if !ok || byteCount%2 != 0 {
650 err := errors.New("Memory.ReadUint32Le fail")
651 r.logger.Error(errorMsg, err, logger.NewLoggerPair("byteCount", byteCount), logger.NewLoggerPair("offset", offset))
652 return "", err
653 }
654 buf, ok := m.Memory().Read(offset, byteCount)
655 if !ok {
656 err := errors.New("Memory.Read fail")
657 r.logger.Error(errorMsg, err, logger.NewLoggerPair("offset", offset), logger.NewLoggerPair("byteCount", byteCount))
658 return "", err
659 }
660 u16s := make([]uint16, len(buf)/2)
661
662 lb := len(buf)
663 for i := 0; i < lb; i += 2 {
664 u16s[i/2] = uint16(buf[i]) + (uint16(buf[i+1]) << 8)
665 }
666 return string(utf16.Decode(u16s)), nil
667}