1/*
2   Copyright The containerd Authors.
3
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7
8       http://www.apache.org/licenses/LICENSE-2.0
9
10   Unless required by applicable law or agreed to in writing, software
11   distributed under the License is distributed on an "AS IS" BASIS,
12   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   See the License for the specific language governing permissions and
14   limitations under the License.
15*/
16
17package fstest
18
19import (
20	"bytes"
21	"io"
22	"math/rand"
23	"os"
24	"path/filepath"
25	"syscall"
26	"time"
27)
28
29// Applier applies single file changes
30type Applier interface {
31	Apply(root string) error
32}
33
34type applyFn func(root string) error
35
36func (a applyFn) Apply(root string) error {
37	return a(root)
38}
39
40// CreateFile returns a file applier which creates a file as the
41// provided name with the given content and permission.
42func CreateFile(name string, content []byte, perm os.FileMode) Applier {
43	f := func() io.Reader {
44		return bytes.NewReader(content)
45	}
46	return writeFileStream(name, f, perm)
47}
48
49// CreateRandomFile returns a file applier which creates a file with random
50// content of the given size using the given seed and permission.
51func CreateRandomFile(name string, seed, size int64, perm os.FileMode) Applier {
52	f := func() io.Reader {
53		return io.LimitReader(rand.New(rand.NewSource(seed)), size)
54	}
55	return writeFileStream(name, f, perm)
56}
57
58// writeFileStream returns a file applier which creates a file as the
59// provided name with the given content from the provided i/o stream and permission.
60func writeFileStream(name string, stream func() io.Reader, perm os.FileMode) Applier {
61	return applyFn(func(root string) (retErr error) {
62		fullPath := filepath.Join(root, name)
63		f, err := os.OpenFile(fullPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
64		if err != nil {
65			return err
66		}
67		defer func() {
68			err := f.Close()
69			if err != nil && retErr == nil {
70				retErr = err
71			}
72		}()
73		_, err = io.Copy(f, stream())
74		if err != nil {
75			return err
76		}
77		return os.Chmod(fullPath, perm)
78	})
79}
80
81// Remove returns a file applier which removes the provided file name
82func Remove(name string) Applier {
83	return applyFn(func(root string) error {
84		return os.Remove(filepath.Join(root, name))
85	})
86}
87
88// RemoveAll returns a file applier which removes the provided file name
89// as in os.RemoveAll
90func RemoveAll(name string) Applier {
91	return applyFn(func(root string) error {
92		return os.RemoveAll(filepath.Join(root, name))
93	})
94}
95
96// CreateDir returns a file applier to create the directory with
97// the provided name and permission
98func CreateDir(name string, perm os.FileMode) Applier {
99	return applyFn(func(root string) error {
100		fullPath := filepath.Join(root, name)
101		if err := os.MkdirAll(fullPath, perm); err != nil {
102			return err
103		}
104		return os.Chmod(fullPath, perm)
105	})
106}
107
108// Rename returns a file applier which renames a file
109func Rename(old, new string) Applier {
110	return applyFn(func(root string) error {
111		return os.Rename(filepath.Join(root, old), filepath.Join(root, new))
112	})
113}
114
115// Chown returns a file applier which changes the ownership of a file
116func Chown(name string, uid, gid int) Applier {
117	return applyFn(func(root string) error {
118		return os.Chown(filepath.Join(root, name), uid, gid)
119	})
120}
121
122// Chtimes changes access and mod time of file.
123// Use Lchtimes for symbolic links.
124func Chtimes(name string, atime, mtime time.Time) Applier {
125	return applyFn(func(root string) error {
126		return os.Chtimes(filepath.Join(root, name), atime, mtime)
127	})
128}
129
130// Chmod returns a file applier which changes the file permission
131func Chmod(name string, perm os.FileMode) Applier {
132	return applyFn(func(root string) error {
133		return os.Chmod(filepath.Join(root, name), perm)
134	})
135}
136
137// Symlink returns a file applier which creates a symbolic link
138func Symlink(oldname, newname string) Applier {
139	return applyFn(func(root string) error {
140		return os.Symlink(oldname, filepath.Join(root, newname))
141	})
142}
143
144// Link returns a file applier which creates a hard link
145func Link(oldname, newname string) Applier {
146	return applyFn(func(root string) error {
147		return os.Link(filepath.Join(root, oldname), filepath.Join(root, newname))
148	})
149}
150
151// TODO: Make platform specific, windows applier is always no-op
152//func Mknod(name string, mode int32, dev int) Applier {
153//	return func(root string) error {
154//		return return syscall.Mknod(path, mode, dev)
155//	}
156//}
157
158func CreateSocket(name string, perm os.FileMode) Applier {
159	return applyFn(func(root string) error {
160		fullPath := filepath.Join(root, name)
161		fd, err := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
162		if err != nil {
163			return err
164		}
165		defer syscall.Close(fd)
166		sa := &syscall.SockaddrUnix{Name: fullPath}
167		if err := syscall.Bind(fd, sa); err != nil {
168			return err
169		}
170		return os.Chmod(fullPath, perm)
171	})
172}
173
174// Apply returns a new applier from the given appliers
175func Apply(appliers ...Applier) Applier {
176	return applyFn(func(root string) error {
177		for _, a := range appliers {
178			if err := a.Apply(root); err != nil {
179				return err
180			}
181		}
182		return nil
183	})
184}
185