Skip to content

Commit a21f237

Browse files
authored
Merge pull request #898 from docker/fix/tarball-forward-slash-paths
fix: use path.Join instead of filepath.Join for tar entry names
2 parents dda376b + e8e7675 commit a21f237

2 files changed

Lines changed: 55 additions & 9 deletions

File tree

pkg/distribution/tarball/target.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55
"context"
66
"fmt"
77
"io"
8-
"path/filepath"
8+
"path"
99

1010
"github.com/docker/model-runner/pkg/distribution/internal/progress"
1111
"github.com/docker/model-runner/pkg/distribution/oci"
@@ -68,7 +68,7 @@ func (t *Target) Write(ctx context.Context, mdl types.ModelArtifact, progressWri
6868
return err
6969
}
7070
if err = tw.WriteHeader(&tar.Header{
71-
Name: filepath.Join("blobs", cn.Algorithm, cn.Hex),
71+
Name: path.Join("blobs", cn.Algorithm, cn.Hex),
7272
Mode: 0666,
7373
Size: int64(len(rcf)),
7474
}); err != nil {
@@ -97,15 +97,15 @@ func (t *Target) addLayer(layer oci.Layer, tw *tar.Writer, progressWriter io.Wri
9797
if err != nil {
9898
return fmt.Errorf("get layer diffID: %w", err)
9999
}
100-
if err := t.ensureDir(filepath.Join("blobs", diffID.Algorithm), tw); err != nil {
100+
if err := t.ensureDir(path.Join("blobs", diffID.Algorithm), tw); err != nil {
101101
return err
102102
}
103103
sz, err := layer.Size()
104104
if err != nil {
105105
return fmt.Errorf("get layer size: %w", err)
106106
}
107107
if err = tw.WriteHeader(&tar.Header{
108-
Name: filepath.Join("blobs", diffID.Algorithm, diffID.Hex),
108+
Name: path.Join("blobs", diffID.Algorithm, diffID.Hex),
109109
Mode: 0666,
110110
Size: sz,
111111
}); err != nil {
@@ -138,15 +138,16 @@ func (t *Target) addLayer(layer oci.Layer, tw *tar.Writer, progressWriter io.Wri
138138
return nil
139139
}
140140

141-
func (t *Target) ensureDir(path string, tw *tar.Writer) error {
142-
if _, ok := t.dirs[path]; !ok {
141+
func (t *Target) ensureDir(p string, tw *tar.Writer) error {
142+
if _, ok := t.dirs[p]; !ok {
143143
if err := tw.WriteHeader(&tar.Header{
144-
Name: path,
144+
Name: p,
145145
Typeflag: tar.TypeDir,
146+
Mode: 0755,
146147
}); err != nil {
147-
return fmt.Errorf("add dir entry %q: %w", path, err)
148+
return fmt.Errorf("add dir entry %q: %w", p, err)
148149
}
149150
}
150-
t.dirs[path] = struct{}{}
151+
t.dirs[p] = struct{}{}
151152
return nil
152153
}

pkg/distribution/tarball/target_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"io"
77
"os"
88
"path/filepath"
9+
"strings"
910
"testing"
1011

1112
"github.com/docker/model-runner/pkg/distribution/builder"
@@ -70,6 +71,50 @@ func TestTarget(t *testing.T) {
7071
hasFile(t, tr, "manifest.json", manifestContents)
7172
}
7273

74+
// TestTargetEntryNamesUseForwardSlashes verifies that all tar entry names
75+
// produced by Target.Write use forward slashes, even for models with multiple
76+
// layers (e.g., GGUF + chat template).
77+
//
78+
// This is a regression test for https://github.com/docker/model-runner/issues/894
79+
// where filepath.Join on Windows produced backslash-separated entry names
80+
// (e.g., "blobs\sha256\hex"), causing the daemon reader to skip the blobs.
81+
func TestTargetEntryNamesUseForwardSlashes(t *testing.T) {
82+
b, err := builder.FromPath(filepath.Join("..", "assets", "dummy.gguf"))
83+
if err != nil {
84+
t.Fatalf("Failed to create builder from GGUF: %v", err)
85+
}
86+
b, err = b.WithChatTemplateFile(filepath.Join("..", "assets", "template.jinja"))
87+
if err != nil {
88+
t.Fatalf("Failed to add chat template: %v", err)
89+
}
90+
91+
var buf bytes.Buffer
92+
target, err := tarball.NewTarget(&buf)
93+
if err != nil {
94+
t.Fatalf("Failed to create target: %v", err)
95+
}
96+
if err := target.Write(t.Context(), b.Model(), nil); err != nil {
97+
t.Fatalf("Failed to write model: %v", err)
98+
}
99+
100+
// Read all tar entries and verify none contain backslashes.
101+
tr := tar.NewReader(&buf)
102+
for {
103+
hdr, err := tr.Next()
104+
if err == io.EOF {
105+
break
106+
}
107+
if err != nil {
108+
t.Fatalf("Failed to read tar entry: %v", err)
109+
}
110+
if strings.Contains(hdr.Name, "\\") {
111+
t.Errorf("Tar entry name contains backslash: %q — "+
112+
"tar entry names must use forward slashes for cross-platform compatibility "+
113+
"(see https://github.com/docker/model-runner/issues/894)", hdr.Name)
114+
}
115+
}
116+
}
117+
73118
func hasFile(t *testing.T, tr *tar.Reader, name string, contents []byte) {
74119
hdr, err := tr.Next()
75120
if err != nil {

0 commit comments

Comments
 (0)