1// Copyright 2012 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package tar
6
7import (
8	"bytes"
9	"io/ioutil"
10	"os"
11	"path"
12	"reflect"
13	"strings"
14	"testing"
15	"time"
16)
17
18func TestFileInfoHeader(t *testing.T) {
19	fi, err := os.Stat("testdata/small.txt")
20	if err != nil {
21		t.Fatal(err)
22	}
23	h, err := FileInfoHeader(fi, "")
24	if err != nil {
25		t.Fatalf("FileInfoHeader: %v", err)
26	}
27	if g, e := h.Name, "small.txt"; g != e {
28		t.Errorf("Name = %q; want %q", g, e)
29	}
30	if g, e := h.Mode, int64(fi.Mode().Perm())|c_ISREG; g != e {
31		t.Errorf("Mode = %#o; want %#o", g, e)
32	}
33	if g, e := h.Size, int64(5); g != e {
34		t.Errorf("Size = %v; want %v", g, e)
35	}
36	if g, e := h.ModTime, fi.ModTime(); !g.Equal(e) {
37		t.Errorf("ModTime = %v; want %v", g, e)
38	}
39	// FileInfoHeader should error when passing nil FileInfo
40	if _, err := FileInfoHeader(nil, ""); err == nil {
41		t.Fatalf("Expected error when passing nil to FileInfoHeader")
42	}
43}
44
45func TestFileInfoHeaderDir(t *testing.T) {
46	fi, err := os.Stat("testdata")
47	if err != nil {
48		t.Fatal(err)
49	}
50	h, err := FileInfoHeader(fi, "")
51	if err != nil {
52		t.Fatalf("FileInfoHeader: %v", err)
53	}
54	if g, e := h.Name, "testdata/"; g != e {
55		t.Errorf("Name = %q; want %q", g, e)
56	}
57	// Ignoring c_ISGID for golang.org/issue/4867
58	if g, e := h.Mode&^c_ISGID, int64(fi.Mode().Perm())|c_ISDIR; g != e {
59		t.Errorf("Mode = %#o; want %#o", g, e)
60	}
61	if g, e := h.Size, int64(0); g != e {
62		t.Errorf("Size = %v; want %v", g, e)
63	}
64	if g, e := h.ModTime, fi.ModTime(); !g.Equal(e) {
65		t.Errorf("ModTime = %v; want %v", g, e)
66	}
67}
68
69func TestFileInfoHeaderSymlink(t *testing.T) {
70	h, err := FileInfoHeader(symlink{}, "some-target")
71	if err != nil {
72		t.Fatal(err)
73	}
74	if g, e := h.Name, "some-symlink"; g != e {
75		t.Errorf("Name = %q; want %q", g, e)
76	}
77	if g, e := h.Linkname, "some-target"; g != e {
78		t.Errorf("Linkname = %q; want %q", g, e)
79	}
80}
81
82type symlink struct{}
83
84func (symlink) Name() string       { return "some-symlink" }
85func (symlink) Size() int64        { return 0 }
86func (symlink) Mode() os.FileMode  { return os.ModeSymlink }
87func (symlink) ModTime() time.Time { return time.Time{} }
88func (symlink) IsDir() bool        { return false }
89func (symlink) Sys() interface{}   { return nil }
90
91func TestRoundTrip(t *testing.T) {
92	data := []byte("some file contents")
93
94	var b bytes.Buffer
95	tw := NewWriter(&b)
96	hdr := &Header{
97		Name:    "file.txt",
98		Uid:     1 << 21, // too big for 8 octal digits
99		Size:    int64(len(data)),
100		ModTime: time.Now(),
101	}
102	// tar only supports second precision.
103	hdr.ModTime = hdr.ModTime.Add(-time.Duration(hdr.ModTime.Nanosecond()) * time.Nanosecond)
104	if err := tw.WriteHeader(hdr); err != nil {
105		t.Fatalf("tw.WriteHeader: %v", err)
106	}
107	if _, err := tw.Write(data); err != nil {
108		t.Fatalf("tw.Write: %v", err)
109	}
110	if err := tw.Close(); err != nil {
111		t.Fatalf("tw.Close: %v", err)
112	}
113
114	// Read it back.
115	tr := NewReader(&b)
116	rHdr, err := tr.Next()
117	if err != nil {
118		t.Fatalf("tr.Next: %v", err)
119	}
120	if !reflect.DeepEqual(rHdr, hdr) {
121		t.Errorf("Header mismatch.\n got %+v\nwant %+v", rHdr, hdr)
122	}
123	rData, err := ioutil.ReadAll(tr)
124	if err != nil {
125		t.Fatalf("Read: %v", err)
126	}
127	if !bytes.Equal(rData, data) {
128		t.Errorf("Data mismatch.\n got %q\nwant %q", rData, data)
129	}
130}
131
132type headerRoundTripTest struct {
133	h  *Header
134	fm os.FileMode
135}
136
137func TestHeaderRoundTrip(t *testing.T) {
138	golden := []headerRoundTripTest{
139		// regular file.
140		{
141			h: &Header{
142				Name:     "test.txt",
143				Mode:     0644 | c_ISREG,
144				Size:     12,
145				ModTime:  time.Unix(1360600916, 0),
146				Typeflag: TypeReg,
147			},
148			fm: 0644,
149		},
150		// symbolic link.
151		{
152			h: &Header{
153				Name:     "link.txt",
154				Mode:     0777 | c_ISLNK,
155				Size:     0,
156				ModTime:  time.Unix(1360600852, 0),
157				Typeflag: TypeSymlink,
158			},
159			fm: 0777 | os.ModeSymlink,
160		},
161		// character device node.
162		{
163			h: &Header{
164				Name:     "dev/null",
165				Mode:     0666 | c_ISCHR,
166				Size:     0,
167				ModTime:  time.Unix(1360578951, 0),
168				Typeflag: TypeChar,
169			},
170			fm: 0666 | os.ModeDevice | os.ModeCharDevice,
171		},
172		// block device node.
173		{
174			h: &Header{
175				Name:     "dev/sda",
176				Mode:     0660 | c_ISBLK,
177				Size:     0,
178				ModTime:  time.Unix(1360578954, 0),
179				Typeflag: TypeBlock,
180			},
181			fm: 0660 | os.ModeDevice,
182		},
183		// directory.
184		{
185			h: &Header{
186				Name:     "dir/",
187				Mode:     0755 | c_ISDIR,
188				Size:     0,
189				ModTime:  time.Unix(1360601116, 0),
190				Typeflag: TypeDir,
191			},
192			fm: 0755 | os.ModeDir,
193		},
194		// fifo node.
195		{
196			h: &Header{
197				Name:     "dev/initctl",
198				Mode:     0600 | c_ISFIFO,
199				Size:     0,
200				ModTime:  time.Unix(1360578949, 0),
201				Typeflag: TypeFifo,
202			},
203			fm: 0600 | os.ModeNamedPipe,
204		},
205		// setuid.
206		{
207			h: &Header{
208				Name:     "bin/su",
209				Mode:     0755 | c_ISREG | c_ISUID,
210				Size:     23232,
211				ModTime:  time.Unix(1355405093, 0),
212				Typeflag: TypeReg,
213			},
214			fm: 0755 | os.ModeSetuid,
215		},
216		// setguid.
217		{
218			h: &Header{
219				Name:     "group.txt",
220				Mode:     0750 | c_ISREG | c_ISGID,
221				Size:     0,
222				ModTime:  time.Unix(1360602346, 0),
223				Typeflag: TypeReg,
224			},
225			fm: 0750 | os.ModeSetgid,
226		},
227		// sticky.
228		{
229			h: &Header{
230				Name:     "sticky.txt",
231				Mode:     0600 | c_ISREG | c_ISVTX,
232				Size:     7,
233				ModTime:  time.Unix(1360602540, 0),
234				Typeflag: TypeReg,
235			},
236			fm: 0600 | os.ModeSticky,
237		},
238		// hard link.
239		{
240			h: &Header{
241				Name:     "hard.txt",
242				Mode:     0644 | c_ISREG,
243				Size:     0,
244				Linkname: "file.txt",
245				ModTime:  time.Unix(1360600916, 0),
246				Typeflag: TypeLink,
247			},
248			fm: 0644,
249		},
250		// More information.
251		{
252			h: &Header{
253				Name:     "info.txt",
254				Mode:     0600 | c_ISREG,
255				Size:     0,
256				Uid:      1000,
257				Gid:      1000,
258				ModTime:  time.Unix(1360602540, 0),
259				Uname:    "slartibartfast",
260				Gname:    "users",
261				Typeflag: TypeReg,
262			},
263			fm: 0600,
264		},
265	}
266
267	for i, g := range golden {
268		fi := g.h.FileInfo()
269		h2, err := FileInfoHeader(fi, "")
270		if err != nil {
271			t.Error(err)
272			continue
273		}
274		if strings.Contains(fi.Name(), "/") {
275			t.Errorf("FileInfo of %q contains slash: %q", g.h.Name, fi.Name())
276		}
277		name := path.Base(g.h.Name)
278		if fi.IsDir() {
279			name += "/"
280		}
281		if got, want := h2.Name, name; got != want {
282			t.Errorf("i=%d: Name: got %v, want %v", i, got, want)
283		}
284		if got, want := h2.Size, g.h.Size; got != want {
285			t.Errorf("i=%d: Size: got %v, want %v", i, got, want)
286		}
287		if got, want := h2.Uid, g.h.Uid; got != want {
288			t.Errorf("i=%d: Uid: got %d, want %d", i, got, want)
289		}
290		if got, want := h2.Gid, g.h.Gid; got != want {
291			t.Errorf("i=%d: Gid: got %d, want %d", i, got, want)
292		}
293		if got, want := h2.Uname, g.h.Uname; got != want {
294			t.Errorf("i=%d: Uname: got %q, want %q", i, got, want)
295		}
296		if got, want := h2.Gname, g.h.Gname; got != want {
297			t.Errorf("i=%d: Gname: got %q, want %q", i, got, want)
298		}
299		if got, want := h2.Linkname, g.h.Linkname; got != want {
300			t.Errorf("i=%d: Linkname: got %v, want %v", i, got, want)
301		}
302		if got, want := h2.Typeflag, g.h.Typeflag; got != want {
303			t.Logf("%#v %#v", g.h, fi.Sys())
304			t.Errorf("i=%d: Typeflag: got %q, want %q", i, got, want)
305		}
306		if got, want := h2.Mode, g.h.Mode; got != want {
307			t.Errorf("i=%d: Mode: got %o, want %o", i, got, want)
308		}
309		if got, want := fi.Mode(), g.fm; got != want {
310			t.Errorf("i=%d: fi.Mode: got %o, want %o", i, got, want)
311		}
312		if got, want := h2.AccessTime, g.h.AccessTime; got != want {
313			t.Errorf("i=%d: AccessTime: got %v, want %v", i, got, want)
314		}
315		if got, want := h2.ChangeTime, g.h.ChangeTime; got != want {
316			t.Errorf("i=%d: ChangeTime: got %v, want %v", i, got, want)
317		}
318		if got, want := h2.ModTime, g.h.ModTime; got != want {
319			t.Errorf("i=%d: ModTime: got %v, want %v", i, got, want)
320		}
321		if sysh, ok := fi.Sys().(*Header); !ok || sysh != g.h {
322			t.Errorf("i=%d: Sys didn't return original *Header", i)
323		}
324	}
325}
326