images.go 28.3 KB
Newer Older
1
2
3
package server

import (
4
	"archive/zip"
5
	"bytes"
6
	"context"
7
	"crypto/sha256"
Patrick Devine's avatar
Patrick Devine committed
8
	"encoding/hex"
9
10
11
12
13
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
14
	"log/slog"
15
	"net/http"
Michael Yang's avatar
Michael Yang committed
16
	"net/url"
17
18
	"os"
	"path/filepath"
Michael Yang's avatar
Michael Yang committed
19
	"runtime"
Michael Yang's avatar
Michael Yang committed
20
	"strconv"
21
	"strings"
Quinn Slack's avatar
Quinn Slack committed
22
	"text/template"
23

Michael Yang's avatar
Michael Yang committed
24
25
	"golang.org/x/exp/slices"

26
	"github.com/jmorganca/ollama/api"
27
	"github.com/jmorganca/ollama/convert"
28
	"github.com/jmorganca/ollama/llm"
29
	"github.com/jmorganca/ollama/parser"
Michael Yang's avatar
Michael Yang committed
30
	"github.com/jmorganca/ollama/version"
31
32
)

Michael Yang's avatar
Michael Yang committed
33
34
35
36
37
38
39
type registryOptions struct {
	Insecure bool
	Username string
	Password string
	Token    string
}

40
type Model struct {
Michael Yang's avatar
Michael Yang committed
41
	Name           string `json:"name"`
42
	Config         ConfigV2
Michael Yang's avatar
Michael Yang committed
43
44
	ShortName      string
	ModelPath      string
45
	ParentModel    string
Michael Yang's avatar
Michael Yang committed
46
47
48
49
50
51
	AdapterPaths   []string
	ProjectorPaths []string
	Template       string
	System         string
	License        []string
	Digest         string
Patrick Devine's avatar
Patrick Devine committed
52
	Size           int64
Michael Yang's avatar
Michael Yang committed
53
	Options        map[string]interface{}
54
55
56
	Messages       []Message
}

57
58
59
60
func (m *Model) IsEmbedding() bool {
	return slices.Contains(m.Config.ModelFamilies, "bert") || slices.Contains(m.Config.ModelFamilies, "nomic-bert")
}

61
62
63
type Message struct {
	Role    string `json:"role"`
	Content string `json:"content"`
64
65
66
67
68
}

type ManifestV2 struct {
	SchemaVersion int      `json:"schemaVersion"`
	MediaType     string   `json:"mediaType"`
Michael Yang's avatar
Michael Yang committed
69
	Config        *Layer   `json:"config"`
70
71
72
73
	Layers        []*Layer `json:"layers"`
}

type ConfigV2 struct {
74
75
76
77
78
79
	ModelFormat   string   `json:"model_format"`
	ModelFamily   string   `json:"model_family"`
	ModelFamilies []string `json:"model_families"`
	ModelType     string   `json:"model_type"`
	FileType      string   `json:"file_type"`

80
	// required by spec
81
82
	Architecture string `json:"architecture"`
	OS           string `json:"os"`
83
	RootFS       RootFS `json:"rootfs"`
84
85
}

Michael Yang's avatar
Michael Yang committed
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
func (c *ConfigV2) SetModelFormat(format string) {
	if c.ModelFormat == "" {
		c.ModelFormat = format
	}
}

func (c *ConfigV2) SetModelFamily(families ...string) {
	for _, family := range families {
		if c.ModelFamily == "" {
			c.ModelFamily = family
		}

		if !slices.Contains(c.ModelFamilies, family) {
			c.ModelFamilies = append(c.ModelFamilies, family)
		}
	}
}

func (c *ConfigV2) SetModelType(modelType string) {
	if c.ModelType == "" {
		c.ModelType = modelType
	}
}

func (c *ConfigV2) SetFileType(fileType string) {
	if c.FileType == "" {
		c.FileType = fileType
	}
}

116
117
118
119
120
type RootFS struct {
	Type    string   `json:"type"`
	DiffIDs []string `json:"diff_ids"`
}

Michael Yang's avatar
Michael Yang committed
121
func (m *ManifestV2) GetTotalSize() (total int64) {
Patrick Devine's avatar
Patrick Devine committed
122
123
124
	for _, layer := range m.Layers {
		total += layer.Size
	}
Michael Yang's avatar
Michael Yang committed
125

Patrick Devine's avatar
Patrick Devine committed
126
127
128
129
	total += m.Config.Size
	return total
}

Patrick Devine's avatar
Patrick Devine committed
130
func GetManifest(mp ModelPath) (*ManifestV2, string, error) {
131
	fp, err := mp.GetManifestPath()
132
	if err != nil {
Patrick Devine's avatar
Patrick Devine committed
133
		return nil, "", err
134
	}
135

136
	if _, err = os.Stat(fp); err != nil {
Patrick Devine's avatar
Patrick Devine committed
137
		return nil, "", err
138
139
140
141
	}

	var manifest *ManifestV2

142
	bts, err := os.ReadFile(fp)
143
	if err != nil {
Patrick Devine's avatar
Patrick Devine committed
144
		return nil, "", fmt.Errorf("couldn't open file '%s'", fp)
145
146
	}

Patrick Devine's avatar
Patrick Devine committed
147
148
149
	shaSum := sha256.Sum256(bts)
	shaStr := hex.EncodeToString(shaSum[:])

150
	if err := json.Unmarshal(bts, &manifest); err != nil {
Patrick Devine's avatar
Patrick Devine committed
151
		return nil, "", err
152
153
	}

Patrick Devine's avatar
Patrick Devine committed
154
	return manifest, shaStr, nil
155
156
157
}

func GetModel(name string) (*Model, error) {
158
	mp := ParseModelPath(name)
Patrick Devine's avatar
Patrick Devine committed
159
	manifest, digest, err := GetManifest(mp)
160
161
162
163
164
	if err != nil {
		return nil, err
	}

	model := &Model{
165
166
167
168
169
		Name:      mp.GetFullTagname(),
		ShortName: mp.GetShortTagname(),
		Digest:    digest,
		Template:  "{{ .Prompt }}",
		License:   []string{},
Patrick Devine's avatar
Patrick Devine committed
170
		Size:      manifest.GetTotalSize(),
171
172
	}

173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
	filename, err := GetBlobsPath(manifest.Config.Digest)
	if err != nil {
		return nil, err
	}

	configFile, err := os.Open(filename)
	if err != nil {
		return nil, err
	}
	defer configFile.Close()

	if err := json.NewDecoder(configFile).Decode(&model.Config); err != nil {
		return nil, err
	}

188
	for _, layer := range manifest.Layers {
Patrick Devine's avatar
Patrick Devine committed
189
		filename, err := GetBlobsPath(layer.Digest)
190
191
192
193
		if err != nil {
			return nil, err
		}

194
195
196
		switch layer.MediaType {
		case "application/vnd.ollama.image.model":
			model.ModelPath = filename
197
			model.ParentModel = layer.From
198
		case "application/vnd.ollama.image.embed":
199
200
			// Deprecated in versions  > 0.1.2
			// TODO: remove this warning in a future version
201
			slog.Info("WARNING: model contains embeddings, but embeddings in modelfiles have been deprecated and will be ignored.")
202
203
		case "application/vnd.ollama.image.adapter":
			model.AdapterPaths = append(model.AdapterPaths, filename)
Michael Yang's avatar
Michael Yang committed
204
205
		case "application/vnd.ollama.image.projector":
			model.ProjectorPaths = append(model.ProjectorPaths, filename)
206
207
208
209
210
211
212
213
214
		case "application/vnd.ollama.image.template":
			bts, err := os.ReadFile(filename)
			if err != nil {
				return nil, err
			}

			model.Template = string(bts)
		case "application/vnd.ollama.image.system":
			bts, err := os.ReadFile(filename)
215
216
217
			if err != nil {
				return nil, err
			}
218
219

			model.System = string(bts)
220
221
222
223
224
225
226
		case "application/vnd.ollama.image.prompt":
			bts, err := os.ReadFile(filename)
			if err != nil {
				return nil, err
			}

			model.Template = string(bts)
227
		case "application/vnd.ollama.image.params":
Michael Yang's avatar
Michael Yang committed
228
229
230
231
232
			params, err := os.Open(filename)
			if err != nil {
				return nil, err
			}
			defer params.Close()
233

234
			// parse model options parameters into a map so that we can see which fields have been specified explicitly
235
			if err = json.NewDecoder(params).Decode(&model.Options); err != nil {
236
237
				return nil, err
			}
238
239
240
241
242
243
244
245
246
247
		case "application/vnd.ollama.image.messages":
			msgs, err := os.Open(filename)
			if err != nil {
				return nil, err
			}
			defer msgs.Close()

			if err = json.NewDecoder(msgs).Decode(&model.Messages); err != nil {
				return nil, err
			}
Patrick Devine's avatar
Patrick Devine committed
248
249
250
251
252
253
		case "application/vnd.ollama.image.license":
			bts, err := os.ReadFile(filename)
			if err != nil {
				return nil, err
			}
			model.License = append(model.License, string(bts))
254
255
256
257
258
259
		}
	}

	return model, nil
}

260
261
func realpath(mfDir, from string) string {
	abspath, err := filepath.Abs(from)
Michael Yang's avatar
Michael Yang committed
262
	if err != nil {
263
		return from
264
265
	}

Michael Yang's avatar
Michael Yang committed
266
	home, err := os.UserHomeDir()
267
	if err != nil {
Michael Yang's avatar
Michael Yang committed
268
		return abspath
269
270
	}

271
	if from == "~" {
Michael Yang's avatar
Michael Yang committed
272
		return home
273
274
275
276
277
278
279
	} else if strings.HasPrefix(from, "~/") {
		return filepath.Join(home, from[2:])
	}

	if _, err := os.Stat(filepath.Join(mfDir, from)); err == nil {
		// this is a file relative to the Modelfile
		return filepath.Join(mfDir, from)
280
281
	}

Michael Yang's avatar
Michael Yang committed
282
283
284
	return abspath
}

285
func CreateModel(ctx context.Context, name, modelFileDir string, commands []parser.Command, fn func(resp api.ProgressResponse)) error {
286
287
288
289
290
291
292
	deleteMap := make(map[string]struct{})
	if manifest, _, err := GetManifest(ParseModelPath(name)); err == nil {
		for _, layer := range append(manifest.Layers, manifest.Config) {
			deleteMap[layer.Digest] = struct{}{}
		}
	}

293
294
	config := ConfigV2{
		OS:           "linux",
Michael Yang's avatar
Michael Yang committed
295
		Architecture: "amd64",
Michael Yang's avatar
Michael Yang committed
296
297
298
		RootFS: RootFS{
			Type: "layers",
		},
299
300
	}

Michael Yang's avatar
Michael Yang committed
301
	var layers Layers
302
	messages := []string{}
Michael Yang's avatar
Michael Yang committed
303

304
	params := make(map[string][]string)
Michael Yang's avatar
Michael Yang committed
305
306
	fromParams := make(map[string]any)

307
	for _, c := range commands {
Michael Yang's avatar
Michael Yang committed
308
309
		mediatype := fmt.Sprintf("application/vnd.ollama.image.%s", c.Name)

310
311
		switch c.Name {
		case "model":
Michael Yang's avatar
Michael Yang committed
312
313
314
315
316
317
318
319
320
			if strings.HasPrefix(c.Args, "@") {
				blobPath, err := GetBlobsPath(strings.TrimPrefix(c.Args, "@"))
				if err != nil {
					return err
				}

				c.Args = blobPath
			}

321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
			pathName := realpath(modelFileDir, c.Args)

			ggufName, err := convertSafetensors(name, pathName)
			if err != nil {
				switch {
				case errors.Is(err, zip.ErrFormat):
					// it's not a safetensor archive
				default:
					return err
				}
			}

			if ggufName != "" {
				pathName = ggufName
				defer os.RemoveAll(ggufName)
			}

			bin, err := os.Open(pathName)
339
			if err != nil {
Michael Yang's avatar
Michael Yang committed
340
341
342
343
344
345
				// not a file on disk so must be a model reference
				modelpath := ParseModelPath(c.Args)
				manifest, _, err := GetManifest(modelpath)
				switch {
				case errors.Is(err, os.ErrNotExist):
					fn(api.ProgressResponse{Status: "pulling model"})
Michael Yang's avatar
Michael Yang committed
346
					if err := PullModel(ctx, c.Args, &registryOptions{}, fn); err != nil {
347
348
349
						return err
					}

Michael Yang's avatar
Michael Yang committed
350
					manifest, _, err = GetManifest(modelpath)
351
352
353
					if err != nil {
						return err
					}
Michael Yang's avatar
Michael Yang committed
354
355
				case err != nil:
					return err
356
				}
357

358
				fn(api.ProgressResponse{Status: "reading model metadata"})
Michael Yang's avatar
Michael Yang committed
359
				fromConfigPath, err := GetBlobsPath(manifest.Config.Digest)
Michael Yang's avatar
Michael Yang committed
360
361
362
363
				if err != nil {
					return err
				}

Michael Yang's avatar
Michael Yang committed
364
				fromConfigFile, err := os.Open(fromConfigPath)
Michael Yang's avatar
Michael Yang committed
365
366
367
				if err != nil {
					return err
				}
Michael Yang's avatar
Michael Yang committed
368
				defer fromConfigFile.Close()
Michael Yang's avatar
Michael Yang committed
369

Michael Yang's avatar
Michael Yang committed
370
371
				var fromConfig ConfigV2
				if err := json.NewDecoder(fromConfigFile).Decode(&fromConfig); err != nil {
Michael Yang's avatar
Michael Yang committed
372
373
374
					return err
				}

Bruce MacDonald's avatar
Bruce MacDonald committed
375
376
377
378
379
				// if the model is still not in gguf format, error out
				if fromConfig.ModelFormat != "gguf" {
					return fmt.Errorf("%s is not in gguf format, this base model is not compatible with this version of ollama", c.Args)
				}

Michael Yang's avatar
Michael Yang committed
380
381
382
383
				config.SetModelFormat(fromConfig.ModelFormat)
				config.SetModelFamily(append(fromConfig.ModelFamilies, fromConfig.ModelFamily)...)
				config.SetModelType(fromConfig.ModelType)
				config.SetFileType(fromConfig.FileType)
Michael Yang's avatar
Michael Yang committed
384

Michael Yang's avatar
Michael Yang committed
385
386
387
388
				for _, layer := range manifest.Layers {
					deleteMap[layer.Digest] = struct{}{}
					if layer.MediaType == "application/vnd.ollama.image.params" {
						fromParamsPath, err := GetBlobsPath(layer.Digest)
Michael Yang's avatar
Michael Yang committed
389
390
391
392
						if err != nil {
							return err
						}

Michael Yang's avatar
Michael Yang committed
393
						fromParamsFile, err := os.Open(fromParamsPath)
Michael Yang's avatar
Michael Yang committed
394
395
396
						if err != nil {
							return err
						}
Michael Yang's avatar
Michael Yang committed
397
						defer fromParamsFile.Close()
Michael Yang's avatar
Michael Yang committed
398

Michael Yang's avatar
Michael Yang committed
399
						if err := json.NewDecoder(fromParamsFile).Decode(&fromParams); err != nil {
Michael Yang's avatar
Michael Yang committed
400
401
402
403
							return err
						}
					}

Michael Yang's avatar
Michael Yang committed
404
					layer, err := NewLayerFromLayer(layer.Digest, layer.MediaType, modelpath.GetShortTagname())
405
406
407
					if err != nil {
						return err
					}
Michael Yang's avatar
Michael Yang committed
408

Michael Yang's avatar
Michael Yang committed
409
					layers.Add(layer)
410
				}
Michael Yang's avatar
Michael Yang committed
411
412
413

				deleteMap[manifest.Config.Digest] = struct{}{}
				continue
414
			}
Michael Yang's avatar
Michael Yang committed
415
			defer bin.Close()
416

417
			var offset int64
Bruce MacDonald's avatar
Bruce MacDonald committed
418
		CREATE:
419
420
			for {
				fn(api.ProgressResponse{Status: "creating model layer"})
421

422
423
				bin.Seek(offset, io.SeekStart)
				ggml, err := llm.DecodeGGML(bin)
Bruce MacDonald's avatar
Bruce MacDonald committed
424
425
426
427
428
429
430
431
432
				if err != nil {
					switch {
					case errors.Is(err, io.EOF):
						break CREATE
					case errors.Is(err, llm.ErrUnsupportedFormat):
						return fmt.Errorf("model binary specified in FROM field is not a valid gguf format model, %w", err)
					default:
						return err
					}
433
				}
Michael Yang's avatar
Michael Yang committed
434

Michael Yang's avatar
Michael Yang committed
435
436
437
438
				config.SetModelFormat(ggml.Name())
				config.SetModelFamily(ggml.ModelFamily())
				config.SetModelType(ggml.ModelType())
				config.SetFileType(ggml.FileType())
439

440
441
442
443
				mediatype := mediatype
				if ggml.ModelFamily() == "clip" {
					mediatype = "application/vnd.ollama.image.projector"
				}
444

445
446
447
448
449
450
451
452
453
454
				sr := io.NewSectionReader(bin, offset, ggml.Size)
				layer, err := NewLayer(sr, mediatype)
				if err != nil {
					return err
				}

				layers.Add(layer)

				offset += ggml.Size
			}
Michael Yang's avatar
Michael Yang committed
455
		case "adapter":
456
457
458
459
460
461
462
463
			if strings.HasPrefix(c.Args, "@") {
				blobPath, err := GetBlobsPath(strings.TrimPrefix(c.Args, "@"))
				if err != nil {
					return err
				}

				c.Args = blobPath
			}
Bruce MacDonald's avatar
Bruce MacDonald committed
464

Michael Yang's avatar
Michael Yang committed
465
			fn(api.ProgressResponse{Status: "creating adapter layer"})
466
			bin, err := os.Open(realpath(modelFileDir, c.Args))
467
			if err != nil {
Michael Yang's avatar
Michael Yang committed
468
				return err
469
			}
Michael Yang's avatar
Michael Yang committed
470
			defer bin.Close()
471

Michael Yang's avatar
Michael Yang committed
472
			layer, err := NewLayer(bin, mediatype)
473
			if err != nil {
Michael Yang's avatar
Michael Yang committed
474
				return err
475
			}
Bruce MacDonald's avatar
Bruce MacDonald committed
476

Michael Yang's avatar
Michael Yang committed
477
			layers.Add(layer)
Michael Yang's avatar
Michael Yang committed
478
479
		case "license":
			fn(api.ProgressResponse{Status: "creating license layer"})
Michael Yang's avatar
Michael Yang committed
480
481
482

			bin := strings.NewReader(c.Args)
			layer, err := NewLayer(bin, mediatype)
Bruce MacDonald's avatar
Bruce MacDonald committed
483
484
485
486
			if err != nil {
				return err
			}

Michael Yang's avatar
Michael Yang committed
487
			layers.Add(layer)
Michael Yang's avatar
Michael Yang committed
488
489
490
		case "template", "system":
			fn(api.ProgressResponse{Status: fmt.Sprintf("creating %s layer", c.Name)})

Michael Yang's avatar
Michael Yang committed
491
492
			bin := strings.NewReader(c.Args)
			layer, err := NewLayer(bin, mediatype)
493
			if err != nil {
494
				return err
495
			}
496

Michael Yang's avatar
Michael Yang committed
497
			layers.Replace(layer)
498
499
		case "message":
			messages = append(messages, c.Args)
500
		default:
501
			params[c.Name] = append(params[c.Name], c.Args)
502
503
504
		}
	}

505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
	if len(messages) > 0 {
		fn(api.ProgressResponse{Status: "creating parameters layer"})

		msgs := make([]api.Message, 0)

		for _, m := range messages {
			// todo: handle images
			msg := strings.SplitN(m, ": ", 2)
			msgs = append(msgs, api.Message{Role: msg[0], Content: msg[1]})
		}

		var b bytes.Buffer
		if err := json.NewEncoder(&b).Encode(msgs); err != nil {
			return err
		}

		layer, err := NewLayer(&b, "application/vnd.ollama.image.messages")
		if err != nil {
			return err
		}

		layers.Replace(layer)
	}

Michael Yang's avatar
Michael Yang committed
529
	if len(params) > 0 {
Michael Yang's avatar
Michael Yang committed
530
		fn(api.ProgressResponse{Status: "creating parameters layer"})
Michael Yang's avatar
Michael Yang committed
531

532
		formattedParams, err := api.FormatParams(params)
533
		if err != nil {
Michael Yang's avatar
Michael Yang committed
534
			return err
535
		}
536

Michael Yang's avatar
Michael Yang committed
537
		for k, v := range fromParams {
Michael Yang's avatar
Michael Yang committed
538
539
540
541
542
			if _, ok := formattedParams[k]; !ok {
				formattedParams[k] = v
			}
		}

Patrick Devine's avatar
Patrick Devine committed
543
		// xxx - can this be removed?
Michael Yang's avatar
Michael Yang committed
544
		if config.ModelType == "65B" {
Michael Yang's avatar
Michael Yang committed
545
			if gqa, ok := formattedParams["gqa"].(int); ok && gqa == 8 {
Michael Yang's avatar
Michael Yang committed
546
547
548
549
				config.ModelType = "70B"
			}
		}

Michael Yang's avatar
Michael Yang committed
550
551
		var b bytes.Buffer
		if err := json.NewEncoder(&b).Encode(formattedParams); err != nil {
552
553
554
			return err
		}

Michael Yang's avatar
Michael Yang committed
555
		fn(api.ProgressResponse{Status: "creating config layer"})
Michael Yang's avatar
Michael Yang committed
556
		layer, err := NewLayer(&b, "application/vnd.ollama.image.params")
557
		if err != nil {
Michael Yang's avatar
Michael Yang committed
558
			return err
559
		}
Michael Yang's avatar
Michael Yang committed
560

Michael Yang's avatar
Michael Yang committed
561
		layers.Replace(layer)
562
563
	}

Michael Yang's avatar
Michael Yang committed
564
565
566
	digests := make([]string, len(layers.items))
	for i, layer := range layers.items {
		digests[i] = layer.Digest
567
568
	}

Michael Yang's avatar
Michael Yang committed
569
	config.RootFS.DiffIDs = digests
Michael Yang's avatar
Michael Yang committed
570

Michael Yang's avatar
Michael Yang committed
571
572
	var b bytes.Buffer
	if err := json.NewEncoder(&b).Encode(config); err != nil {
573
574
575
		return err
	}

Michael Yang's avatar
Michael Yang committed
576
577
	configLayer, err := NewLayer(&b, "application/vnd.docker.container.image.v1+json")
	if err != nil {
578
579
580
		return err
	}

Michael Yang's avatar
Michael Yang committed
581
	delete(deleteMap, configLayer.Digest)
582

Michael Yang's avatar
Michael Yang committed
583
584
	for _, layer := range append(layers.items, configLayer) {
		committed, err := layer.Commit()
585
586
587
		if err != nil {
			return err
		}
588

Michael Yang's avatar
Michael Yang committed
589
590
591
		status := "writing layer"
		if !committed {
			status = "using already created layer"
592
593
		}

Michael Yang's avatar
Michael Yang committed
594
		fn(api.ProgressResponse{Status: fmt.Sprintf("%s %s", status, layer.Digest)})
595

Michael Yang's avatar
Michael Yang committed
596
		delete(deleteMap, layer.Digest)
597
598
	}

Michael Yang's avatar
Michael Yang committed
599
600
	fn(api.ProgressResponse{Status: "writing manifest"})
	if err := WriteManifest(name, configLayer, layers.items); err != nil {
601
602
		return err
	}
603

Michael Yang's avatar
Michael Yang committed
604
605
606
	if noprune := os.Getenv("OLLAMA_NOPRUNE"); noprune == "" {
		if err := deleteUnusedLayers(nil, deleteMap, false); err != nil {
			return err
607
608
609
		}
	}

Michael Yang's avatar
Michael Yang committed
610
611
	fn(api.ProgressResponse{Status: "success"})
	return nil
612
613
}

614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
func convertSafetensors(name, fn string) (string, error) {
	r, err := zip.OpenReader(fn)
	if err != nil {
		return "", err
	}
	defer r.Close()

	tempDir, err := os.MkdirTemp("", "ollama-convert")
	if err != nil {
		return "", err
	}
	defer os.RemoveAll(tempDir)

	for _, f := range r.File {
		fpath := filepath.Join(tempDir, f.Name)
		outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
		if err != nil {
			return "", err
		}

		rc, err := f.Open()
		if err != nil {
			return "", err
		}

		_, err = io.Copy(outFile, rc)
		if err != nil {
			return "", err
		}

		outFile.Close()
		rc.Close()
	}

	params, err := convert.GetParams(tempDir)
	if err != nil {
		return "", err
	}

	SupportedArchs := []string{
		"MistralForCausalLM",
	}

	for _, arch := range params.Architectures {
		if !slices.Contains(SupportedArchs, arch) {
			return "", fmt.Errorf("this safetensors model is not yet supported")
		}
	}

	t, err := convert.GetSafeTensors(tempDir)
	if err != nil {
		return "", err
	}

	vocab, err := convert.LoadTokens(tempDir)
	if err != nil {
		return "", err
	}

	fn, err = convert.WriteGGUF(name, t, params, vocab)
	if err != nil {
		return "", err
	}

	return fn, nil
}

Patrick Devine's avatar
Patrick Devine committed
681
func CopyModel(src, dest string) error {
682
	srcModelPath := ParseModelPath(src)
683
	srcPath, err := srcModelPath.GetManifestPath()
684
685
686
687
	if err != nil {
		return err
	}

688
	destModelPath := ParseModelPath(dest)
689
	destPath, err := destModelPath.GetManifestPath()
Patrick Devine's avatar
Patrick Devine committed
690
691
692
	if err != nil {
		return err
	}
693
694
695
	if err := os.MkdirAll(filepath.Dir(destPath), 0o755); err != nil {
		return err
	}
Patrick Devine's avatar
Patrick Devine committed
696
697

	// copy the file
Michael Yang's avatar
Michael Yang committed
698
	input, err := os.ReadFile(srcPath)
Patrick Devine's avatar
Patrick Devine committed
699
700
701
702
703
	if err != nil {
		fmt.Println("Error reading file:", err)
		return err
	}

Michael Yang's avatar
Michael Yang committed
704
	err = os.WriteFile(destPath, input, 0o644)
Patrick Devine's avatar
Patrick Devine committed
705
706
707
708
709
710
711
712
	if err != nil {
		fmt.Println("Error reading file:", err)
		return err
	}

	return nil
}

Michael Yang's avatar
Michael Yang committed
713
func deleteUnusedLayers(skipModelPath *ModelPath, deleteMap map[string]struct{}, dryRun bool) error {
714
715
716
717
	fp, err := GetManifestPath()
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
718
719
720
721

	walkFunc := func(path string, info os.FileInfo, _ error) error {
		if info.IsDir() {
			return nil
722
723
		}

Michael Yang's avatar
Michael Yang committed
724
725
726
727
		dir, file := filepath.Split(path)
		dir = strings.Trim(strings.TrimPrefix(dir, fp), string(os.PathSeparator))
		tag := strings.Join([]string{dir, file}, ":")
		fmp := ParseModelPath(tag)
728

Michael Yang's avatar
Michael Yang committed
729
		// skip the manifest we're trying to delete
730
		if skipModelPath != nil && skipModelPath.GetFullTagname() == fmp.GetFullTagname() {
Michael Yang's avatar
Michael Yang committed
731
			return nil
732
		}
Michael Yang's avatar
Michael Yang committed
733
734
735
736

		// save (i.e. delete from the deleteMap) any files used in other manifests
		manifest, _, err := GetManifest(fmp)
		if err != nil {
Michael Yang's avatar
Michael Yang committed
737
			// nolint: nilerr
Michael Yang's avatar
Michael Yang committed
738
739
740
741
742
743
744
745
			return nil
		}

		for _, layer := range manifest.Layers {
			delete(deleteMap, layer.Digest)
		}

		delete(deleteMap, manifest.Config.Digest)
746
		return nil
Michael Yang's avatar
Michael Yang committed
747
748
749
	}

	if err := filepath.Walk(fp, walkFunc); err != nil {
Michael Yang's avatar
Michael Yang committed
750
751
		return err
	}
752
753

	// only delete the files which are still in the deleteMap
Michael Yang's avatar
Michael Yang committed
754
755
756
	for k := range deleteMap {
		fp, err := GetBlobsPath(k)
		if err != nil {
757
			slog.Info(fmt.Sprintf("couldn't get file path for '%s': %v", k, err))
Michael Yang's avatar
Michael Yang committed
758
759
760
761
			continue
		}
		if !dryRun {
			if err := os.Remove(fp); err != nil {
762
				slog.Info(fmt.Sprintf("couldn't remove file '%s': %v", fp, err))
763
764
				continue
			}
Michael Yang's avatar
Michael Yang committed
765
		} else {
766
			slog.Info(fmt.Sprintf("wanted to remove: %s", fp))
767
768
769
		}
	}

770
771
772
773
	return nil
}

func PruneLayers() error {
Michael Yang's avatar
Michael Yang committed
774
	deleteMap := make(map[string]struct{})
775
776
777
778
779
780
781
	p, err := GetBlobsPath("")
	if err != nil {
		return err
	}

	blobs, err := os.ReadDir(p)
	if err != nil {
782
		slog.Info(fmt.Sprintf("couldn't read dir '%s': %v", p, err))
783
784
785
786
787
788
789
790
		return err
	}

	for _, blob := range blobs {
		name := blob.Name()
		if runtime.GOOS == "windows" {
			name = strings.ReplaceAll(name, "-", ":")
		}
Michael Yang's avatar
Michael Yang committed
791
792
793
		if strings.HasPrefix(name, "sha256:") {
			deleteMap[name] = struct{}{}
		}
794
795
	}

796
	slog.Info(fmt.Sprintf("total blobs: %d", len(deleteMap)))
797
798
799
800
801
802

	err = deleteUnusedLayers(nil, deleteMap, false)
	if err != nil {
		return err
	}

803
	slog.Info(fmt.Sprintf("total unused blobs removed: %d", len(deleteMap)))
804
805
806
807

	return nil
}

Michael Yang's avatar
Michael Yang committed
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
func PruneDirectory(path string) error {
	info, err := os.Lstat(path)
	if err != nil {
		return err
	}

	if info.IsDir() && info.Mode()&os.ModeSymlink == 0 {
		entries, err := os.ReadDir(path)
		if err != nil {
			return err
		}

		for _, entry := range entries {
			if err := PruneDirectory(filepath.Join(path, entry.Name())); err != nil {
				return err
			}
		}

		entries, err = os.ReadDir(path)
		if err != nil {
			return err
		}

		if len(entries) > 0 {
			return nil
		}

		return os.Remove(path)
	}

	return nil
}

841
842
843
844
845
846
847
func DeleteModel(name string) error {
	mp := ParseModelPath(name)
	manifest, _, err := GetManifest(mp)
	if err != nil {
		return err
	}

Michael Yang's avatar
Michael Yang committed
848
	deleteMap := make(map[string]struct{})
849
	for _, layer := range manifest.Layers {
Michael Yang's avatar
Michael Yang committed
850
		deleteMap[layer.Digest] = struct{}{}
851
	}
Michael Yang's avatar
Michael Yang committed
852
	deleteMap[manifest.Config.Digest] = struct{}{}
853
854
855
856
857
858

	err = deleteUnusedLayers(&mp, deleteMap, false)
	if err != nil {
		return err
	}

859
	fp, err := mp.GetManifestPath()
860
861
862
863
864
	if err != nil {
		return err
	}
	err = os.Remove(fp)
	if err != nil {
865
		slog.Info(fmt.Sprintf("couldn't remove manifest file '%s': %v", fp, err))
866
867
868
869
870
871
		return err
	}

	return nil
}

Patrick Devine's avatar
Patrick Devine committed
872
func ShowModelfile(model *Model) (string, error) {
Michael Yang's avatar
Michael Yang committed
873
	var mt struct {
Patrick Devine's avatar
Patrick Devine committed
874
		*Model
Michael Yang's avatar
Michael Yang committed
875
		From       string
Michael Yang's avatar
Michael Yang committed
876
		Parameters map[string][]any
Patrick Devine's avatar
Patrick Devine committed
877
878
	}

Michael Yang's avatar
Michael Yang committed
879
	mt.Parameters = make(map[string][]any)
Patrick Devine's avatar
Patrick Devine committed
880
	for k, v := range model.Options {
Michael Yang's avatar
Michael Yang committed
881
882
883
		if s, ok := v.([]any); ok {
			mt.Parameters[k] = s
			continue
Patrick Devine's avatar
Patrick Devine committed
884
885
		}

Michael Yang's avatar
Michael Yang committed
886
		mt.Parameters[k] = []any{v}
Patrick Devine's avatar
Patrick Devine committed
887
888
	}

Michael Yang's avatar
Michael Yang committed
889
890
	mt.Model = model
	mt.From = model.ModelPath
Patrick Devine's avatar
Patrick Devine committed
891

892
893
	if model.ParentModel != "" {
		mt.From = model.ParentModel
Patrick Devine's avatar
Patrick Devine committed
894
895
896
897
898
899
900
901
	}

	modelFile := `# Modelfile generated by "ollama show"
# To build a new Modelfile based on this one, replace the FROM line with:
# FROM {{ .ShortName }}

FROM {{ .From }}
TEMPLATE """{{ .Template }}"""
902
903

{{- if .System }}
Patrick Devine's avatar
Patrick Devine committed
904
SYSTEM """{{ .System }}"""
905
{{- end }}
906
907
908
909

{{- range $adapter := .AdapterPaths }}
ADAPTER {{ $adapter }}
{{- end }}
Michael Yang's avatar
Michael Yang committed
910

Michael Yang's avatar
Michael Yang committed
911
912
913
914
{{- range $k, $v := .Parameters }}
{{- range $parameter := $v }}
PARAMETER {{ $k }} {{ printf "%#v" $parameter }}
{{- end }}
Michael Yang's avatar
Michael Yang committed
915
{{- end }}`
Patrick Devine's avatar
Patrick Devine committed
916
917
918

	tmpl, err := template.New("").Parse(modelFile)
	if err != nil {
919
		slog.Info(fmt.Sprintf("error parsing template: %q", err))
Patrick Devine's avatar
Patrick Devine committed
920
921
922
923
924
925
		return "", err
	}

	var buf bytes.Buffer

	if err = tmpl.Execute(&buf, mt); err != nil {
926
		slog.Info(fmt.Sprintf("error executing template: %q", err))
Patrick Devine's avatar
Patrick Devine committed
927
928
929
930
931
932
		return "", err
	}

	return buf.String(), nil
}

Michael Yang's avatar
Michael Yang committed
933
func PushModel(ctx context.Context, name string, regOpts *registryOptions, fn func(api.ProgressResponse)) error {
934
	mp := ParseModelPath(name)
935
936
	fn(api.ProgressResponse{Status: "retrieving manifest"})

937
938
939
940
	if mp.ProtocolScheme == "http" && !regOpts.Insecure {
		return fmt.Errorf("insecure protocol http")
	}

Patrick Devine's avatar
Patrick Devine committed
941
	manifest, _, err := GetManifest(mp)
942
	if err != nil {
943
		fn(api.ProgressResponse{Status: "couldn't retrieve manifest"})
944
945
946
947
		return err
	}

	var layers []*Layer
Jeffrey Morgan's avatar
Jeffrey Morgan committed
948
	layers = append(layers, manifest.Layers...)
Michael Yang's avatar
Michael Yang committed
949
	layers = append(layers, manifest.Config)
950
951

	for _, layer := range layers {
Michael Yang's avatar
Michael Yang committed
952
		if err := uploadBlob(ctx, mp, layer, regOpts, fn); err != nil {
953
			slog.Info(fmt.Sprintf("error uploading blob: %v", err))
954
955
956
			if errors.Is(err, errUnauthorized) {
				return fmt.Errorf("unable to push %s, make sure this namespace exists and you are authorized to push to it", ParseModelPath(name).GetNamespaceRepository())
			}
957
958
			return err
		}
959
960
	}

961
	fn(api.ProgressResponse{Status: "pushing manifest"})
Michael Yang's avatar
Michael Yang committed
962
963
	requestURL := mp.BaseURL()
	requestURL = requestURL.JoinPath("v2", mp.GetNamespaceRepository(), "manifests", mp.Tag)
964
965
966
967
968
969

	manifestJSON, err := json.Marshal(manifest)
	if err != nil {
		return err
	}

Michael Yang's avatar
Michael Yang committed
970
971
	headers := make(http.Header)
	headers.Set("Content-Type", "application/vnd.docker.distribution.manifest.v2+json")
Michael Yang's avatar
Michael Yang committed
972
	resp, err := makeRequestWithRetry(ctx, http.MethodPut, requestURL, headers, bytes.NewReader(manifestJSON), regOpts)
973
974
975
976
977
	if err != nil {
		return err
	}
	defer resp.Body.Close()

978
	fn(api.ProgressResponse{Status: "success"})
979
980
981
982

	return nil
}

Michael Yang's avatar
Michael Yang committed
983
func PullModel(ctx context.Context, name string, regOpts *registryOptions, fn func(api.ProgressResponse)) error {
984
985
	mp := ParseModelPath(name)

986
987
988
989
990
	var manifest *ManifestV2
	var err error
	var noprune string

	// build deleteMap to prune unused layers
Michael Yang's avatar
Michael Yang committed
991
	deleteMap := make(map[string]struct{})
992
993
994
995
996
997
998
999
1000

	if noprune = os.Getenv("OLLAMA_NOPRUNE"); noprune == "" {
		manifest, _, err = GetManifest(mp)
		if err != nil && !errors.Is(err, os.ErrNotExist) {
			return err
		}

		if manifest != nil {
			for _, l := range manifest.Layers {
Michael Yang's avatar
Michael Yang committed
1001
				deleteMap[l.Digest] = struct{}{}
1002
			}
Michael Yang's avatar
Michael Yang committed
1003
			deleteMap[manifest.Config.Digest] = struct{}{}
1004
1005
1006
		}
	}

1007
1008
	if mp.ProtocolScheme == "http" && !regOpts.Insecure {
		return fmt.Errorf("insecure protocol http")
1009
	}
1010

1011
	fn(api.ProgressResponse{Status: "pulling manifest"})
1012

1013
	manifest, err = pullModelManifest(ctx, mp, regOpts)
1014
	if err != nil {
1015
		return fmt.Errorf("pull model manifest: %s", err)
1016
1017
1018
	}

	var layers []*Layer
Bruce MacDonald's avatar
Bruce MacDonald committed
1019
	layers = append(layers, manifest.Layers...)
Michael Yang's avatar
Michael Yang committed
1020
	layers = append(layers, manifest.Config)
1021
1022

	for _, layer := range layers {
1023
1024
1025
1026
1027
1028
1029
1030
		if err := downloadBlob(
			ctx,
			downloadOpts{
				mp:      mp,
				digest:  layer.Digest,
				regOpts: regOpts,
				fn:      fn,
			}); err != nil {
1031
1032
			return err
		}
1033
		delete(deleteMap, layer.Digest)
1034
	}
1035
	delete(deleteMap, manifest.Config.Digest)
1036

Michael Yang's avatar
Michael Yang committed
1037
1038
1039
	fn(api.ProgressResponse{Status: "verifying sha256 digest"})
	for _, layer := range layers {
		if err := verifyBlob(layer.Digest); err != nil {
1040
1041
1042
1043
1044
1045
1046
1047
			if errors.Is(err, errDigestMismatch) {
				// something went wrong, delete the blob
				fp, err := GetBlobsPath(layer.Digest)
				if err != nil {
					return err
				}
				if err := os.Remove(fp); err != nil {
					// log this, but return the original error
1048
					slog.Info(fmt.Sprintf("couldn't remove file with digest mismatch '%s': %v", fp, err))
1049
1050
				}
			}
Michael Yang's avatar
Michael Yang committed
1051
1052
1053
1054
			return err
		}
	}

1055
	fn(api.ProgressResponse{Status: "writing manifest"})
1056

1057
	manifestJSON, err := json.Marshal(manifest)
1058
1059
1060
1061
	if err != nil {
		return err
	}

1062
	fp, err := mp.GetManifestPath()
1063
1064
1065
	if err != nil {
		return err
	}
1066
1067
1068
	if err := os.MkdirAll(filepath.Dir(fp), 0o755); err != nil {
		return err
	}
1069

Bruce MacDonald's avatar
Bruce MacDonald committed
1070
	err = os.WriteFile(fp, manifestJSON, 0o644)
1071
	if err != nil {
1072
		slog.Info(fmt.Sprintf("couldn't write to %s", fp))
1073
1074
1075
		return err
	}

1076
1077
1078
1079
1080
1081
1082
1083
	if noprune == "" {
		fn(api.ProgressResponse{Status: "removing any unused layers"})
		err = deleteUnusedLayers(nil, deleteMap, false)
		if err != nil {
			return err
		}
	}

1084
	fn(api.ProgressResponse{Status: "success"})
1085
1086
1087
1088

	return nil
}

Michael Yang's avatar
Michael Yang committed
1089
func pullModelManifest(ctx context.Context, mp ModelPath, regOpts *registryOptions) (*ManifestV2, error) {
Michael Yang's avatar
Michael Yang committed
1090
	requestURL := mp.BaseURL().JoinPath("v2", mp.GetNamespaceRepository(), "manifests", mp.Tag)
1091

Michael Yang's avatar
Michael Yang committed
1092
1093
	headers := make(http.Header)
	headers.Set("Accept", "application/vnd.docker.distribution.manifest.v2+json")
Michael Yang's avatar
Michael Yang committed
1094
	resp, err := makeRequestWithRetry(ctx, http.MethodGet, requestURL, headers, nil, regOpts)
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	var m *ManifestV2
	if err := json.NewDecoder(resp.Body).Decode(&m); err != nil {
		return nil, err
	}

	return m, err
}

// GetSHA256Digest returns the SHA256 hash of a given buffer and returns it, and the size of buffer
Michael Yang's avatar
Michael Yang committed
1109
func GetSHA256Digest(r io.Reader) (string, int64) {
Michael Yang's avatar
Michael Yang committed
1110
1111
1112
1113
1114
1115
	h := sha256.New()
	n, err := io.Copy(h, r)
	if err != nil {
		log.Fatal(err)
	}

Michael Yang's avatar
Michael Yang committed
1116
	return fmt.Sprintf("sha256:%x", h.Sum(nil)), n
1117
1118
}

1119
1120
var errUnauthorized = fmt.Errorf("unauthorized")

Michael Yang's avatar
Michael Yang committed
1121
func makeRequestWithRetry(ctx context.Context, method string, requestURL *url.URL, headers http.Header, body io.ReadSeeker, regOpts *registryOptions) (*http.Response, error) {
Michael Yang's avatar
Michael Yang committed
1122
	for i := 0; i < 2; i++ {
Michael Yang's avatar
Michael Yang committed
1123
		resp, err := makeRequest(ctx, method, requestURL, headers, body, regOpts)
Michael Yang's avatar
Michael Yang committed
1124
		if err != nil {
Michael Yang's avatar
Michael Yang committed
1125
			if !errors.Is(err, context.Canceled) {
1126
				slog.Info(fmt.Sprintf("request failed: %v", err))
Michael Yang's avatar
Michael Yang committed
1127
1128
			}

Michael Yang's avatar
Michael Yang committed
1129
1130
			return nil, err
		}
Michael Yang's avatar
Michael Yang committed
1131
1132
1133
1134

		switch {
		case resp.StatusCode == http.StatusUnauthorized:
			// Handle authentication error with one retry
Michael Yang's avatar
Michael Yang committed
1135
1136
			challenge := parseRegistryChallenge(resp.Header.Get("www-authenticate"))
			token, err := getAuthorizationToken(ctx, challenge)
Michael Yang's avatar
Michael Yang committed
1137
1138
1139
			if err != nil {
				return nil, err
			}
Michael Yang's avatar
Michael Yang committed
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
			regOpts.Token = token
			if body != nil {
				_, err = body.Seek(0, io.SeekStart)
				if err != nil {
					return nil, err
				}
			}
		case resp.StatusCode == http.StatusNotFound:
			return nil, os.ErrNotExist
		case resp.StatusCode >= http.StatusBadRequest:
			responseBody, err := io.ReadAll(resp.Body)
			if err != nil {
				return nil, fmt.Errorf("%d: %s", resp.StatusCode, err)
			}
			return nil, fmt.Errorf("%d: %s", resp.StatusCode, responseBody)
		default:
			return resp, nil
Michael Yang's avatar
Michael Yang committed
1157
1158
1159
		}
	}

Michael Yang's avatar
Michael Yang committed
1160
	return nil, errUnauthorized
Michael Yang's avatar
Michael Yang committed
1161
1162
}

Michael Yang's avatar
Michael Yang committed
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
func makeRequest(ctx context.Context, method string, requestURL *url.URL, headers http.Header, body io.Reader, regOpts *registryOptions) (*http.Response, error) {
	if requestURL.Scheme != "http" && regOpts != nil && regOpts.Insecure {
		requestURL.Scheme = "http"
	}

	req, err := http.NewRequestWithContext(ctx, method, requestURL.String(), body)
	if err != nil {
		return nil, err
	}

	if headers != nil {
		req.Header = headers
	}

	if regOpts != nil {
		if regOpts.Token != "" {
			req.Header.Set("Authorization", "Bearer "+regOpts.Token)
		} else if regOpts.Username != "" && regOpts.Password != "" {
			req.SetBasicAuth(regOpts.Username, regOpts.Password)
		}
	}

	req.Header.Set("User-Agent", fmt.Sprintf("ollama/%s (%s %s) Go/%s", version.Version, runtime.GOARCH, runtime.GOOS, runtime.Version()))

	if s := req.Header.Get("Content-Length"); s != "" {
		contentLength, err := strconv.ParseInt(s, 10, 64)
		if err != nil {
			return nil, err
		}

		req.ContentLength = contentLength
	}

Michael Yang's avatar
Michael Yang committed
1196
	resp, err := http.DefaultClient.Do(req)
Michael Yang's avatar
Michael Yang committed
1197
1198
1199
1200
1201
1202
1203
	if err != nil {
		return nil, err
	}

	return resp, nil
}

Patrick Devine's avatar
Patrick Devine committed
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
func getValue(header, key string) string {
	startIdx := strings.Index(header, key+"=")
	if startIdx == -1 {
		return ""
	}

	// Move the index to the starting quote after the key.
	startIdx += len(key) + 2
	endIdx := startIdx

	for endIdx < len(header) {
		if header[endIdx] == '"' {
			if endIdx+1 < len(header) && header[endIdx+1] != ',' { // If the next character isn't a comma, continue
				endIdx++
				continue
			}
			break
		}
		endIdx++
	}
	return header[startIdx:endIdx]
}

Michael Yang's avatar
Michael Yang committed
1227
func parseRegistryChallenge(authStr string) registryChallenge {
Patrick Devine's avatar
Patrick Devine committed
1228
1229
	authStr = strings.TrimPrefix(authStr, "Bearer ")

Michael Yang's avatar
Michael Yang committed
1230
	return registryChallenge{
Patrick Devine's avatar
Patrick Devine committed
1231
1232
1233
1234
1235
1236
		Realm:   getValue(authStr, "realm"),
		Service: getValue(authStr, "service"),
		Scope:   getValue(authStr, "scope"),
	}
}

1237
1238
var errDigestMismatch = fmt.Errorf("digest mismatch, file must be downloaded again")

Michael Yang's avatar
Michael Yang committed
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
func verifyBlob(digest string) error {
	fp, err := GetBlobsPath(digest)
	if err != nil {
		return err
	}

	f, err := os.Open(fp)
	if err != nil {
		return err
	}
	defer f.Close()

	fileDigest, _ := GetSHA256Digest(f)
	if digest != fileDigest {
1253
		return fmt.Errorf("%w: want %s, got %s", errDigestMismatch, digest, fileDigest)
Michael Yang's avatar
Michael Yang committed
1254
1255
1256
1257
	}

	return nil
}