1package files
2
3import (
4	"fmt"
5	"io"
6	"os"
7	"path/filepath"
8	"strings"
9	"syscall"
10
11	"github.com/aptly-dev/aptly/aptly"
12	"github.com/aptly-dev/aptly/utils"
13)
14
15// PublishedStorage abstract file system with public dirs (published repos)
16type PublishedStorage struct {
17	rootPath     string
18	linkMethod   uint
19	verifyMethod uint
20}
21
22// Check interfaces
23var (
24	_ aptly.PublishedStorage           = (*PublishedStorage)(nil)
25	_ aptly.FileSystemPublishedStorage = (*PublishedStorage)(nil)
26)
27
28// Constants defining the type of creating links
29const (
30	LinkMethodHardLink uint = iota
31	LinkMethodSymLink
32	LinkMethodCopy
33)
34
35// Constants defining the type of file verification for LinkMethodCopy
36const (
37	VerificationMethodChecksum uint = iota
38	VerificationMethodFileSize
39)
40
41// NewPublishedStorage creates new instance of PublishedStorage which specified root
42func NewPublishedStorage(root string, linkMethod string, verifyMethod string) *PublishedStorage {
43	// Ensure linkMethod is one of 'hardlink', 'symlink', 'copy'
44	var verifiedLinkMethod uint
45
46	if strings.EqualFold(linkMethod, "copy") {
47		verifiedLinkMethod = LinkMethodCopy
48	} else if strings.EqualFold(linkMethod, "symlink") {
49		verifiedLinkMethod = LinkMethodSymLink
50	} else {
51		verifiedLinkMethod = LinkMethodHardLink
52	}
53
54	var verifiedVerifyMethod uint
55
56	if strings.EqualFold(verifyMethod, "size") {
57		verifiedVerifyMethod = VerificationMethodFileSize
58	} else {
59		verifiedVerifyMethod = VerificationMethodChecksum
60	}
61
62	return &PublishedStorage{rootPath: root, linkMethod: verifiedLinkMethod,
63		verifyMethod: verifiedVerifyMethod}
64}
65
66// PublicPath returns root of public part
67func (storage *PublishedStorage) PublicPath() string {
68	return storage.rootPath
69}
70
71// MkDir creates directory recursively under public path
72func (storage *PublishedStorage) MkDir(path string) error {
73	return os.MkdirAll(filepath.Join(storage.rootPath, path), 0777)
74}
75
76// PutFile puts file into published storage at specified path
77func (storage *PublishedStorage) PutFile(path string, sourceFilename string) error {
78	var (
79		source, f *os.File
80		err       error
81	)
82	source, err = os.Open(sourceFilename)
83	if err != nil {
84		return err
85	}
86	defer source.Close()
87
88	f, err = os.Create(filepath.Join(storage.rootPath, path))
89	if err != nil {
90		return err
91	}
92	defer f.Close()
93
94	_, err = io.Copy(f, source)
95	return err
96}
97
98// Remove removes single file under public path
99func (storage *PublishedStorage) Remove(path string) error {
100	if len(path) <= 0 {
101		panic("trying to remove empty path")
102	}
103	filepath := filepath.Join(storage.rootPath, path)
104	return os.Remove(filepath)
105}
106
107// RemoveDirs removes directory structure under public path
108func (storage *PublishedStorage) RemoveDirs(path string, progress aptly.Progress) error {
109	if len(path) <= 0 {
110		panic("trying to remove the root directory")
111	}
112	filepath := filepath.Join(storage.rootPath, path)
113	if progress != nil {
114		progress.Printf("Removing %s...\n", filepath)
115	}
116	return os.RemoveAll(filepath)
117}
118
119// LinkFromPool links package file from pool to dist's pool location
120//
121// publishedDirectory is desired location in pool (like prefix/pool/component/liba/libav/)
122// sourcePool is instance of aptly.PackagePool
123// sourcePath is a relative path to package file in package pool
124//
125// LinkFromPool returns relative path for the published file to be included in package index
126func (storage *PublishedStorage) LinkFromPool(publishedDirectory, fileName string, sourcePool aptly.PackagePool,
127	sourcePath string, sourceChecksums utils.ChecksumInfo, force bool) error {
128
129	baseName := filepath.Base(fileName)
130	poolPath := filepath.Join(storage.rootPath, publishedDirectory, filepath.Dir(fileName))
131
132	err := os.MkdirAll(poolPath, 0777)
133	if err != nil {
134		return err
135	}
136
137	var dstStat, srcStat os.FileInfo
138
139	dstStat, err = os.Stat(filepath.Join(poolPath, baseName))
140	if err == nil {
141		// already exists, check source file
142		srcStat, err = sourcePool.Stat(sourcePath)
143		if err != nil {
144			// source file doesn't exist? problem!
145			return err
146		}
147
148		if storage.linkMethod == LinkMethodCopy {
149			if storage.verifyMethod == VerificationMethodFileSize {
150				// if source and destination have the same size, no need to copy
151				if srcStat.Size() == dstStat.Size() {
152					return nil
153				}
154			} else {
155				// if source and destination have the same checksums, no need to copy
156				var dstMD5 string
157				dstMD5, err = utils.MD5ChecksumForFile(filepath.Join(poolPath, baseName))
158
159				if err != nil {
160					return err
161				}
162
163				if dstMD5 == sourceChecksums.MD5 {
164					return nil
165				}
166			}
167		} else {
168			srcSys := srcStat.Sys().(*syscall.Stat_t)
169			dstSys := dstStat.Sys().(*syscall.Stat_t)
170
171			// if source and destination inodes match, no need to link
172
173			// Symlink can point to different filesystem with identical inodes
174			// so we have to check the device as well.
175			if srcSys.Ino == dstSys.Ino && srcSys.Dev == dstSys.Dev {
176				return nil
177			}
178		}
179
180		// source and destination have different inodes, if !forced, this is fatal error
181		if !force {
182			return fmt.Errorf("error linking file to %s: file already exists and is different", filepath.Join(poolPath, baseName))
183		}
184
185		// forced, so remove destination
186		err = os.Remove(filepath.Join(poolPath, baseName))
187		if err != nil {
188			return err
189		}
190	}
191
192	// destination doesn't exist (or forced), create link or copy
193	if storage.linkMethod == LinkMethodCopy {
194		var r aptly.ReadSeekerCloser
195		r, err = sourcePool.Open(sourcePath)
196		if err != nil {
197			return err
198		}
199
200		var dst *os.File
201		dst, err = os.Create(filepath.Join(poolPath, baseName))
202		if err != nil {
203			r.Close()
204			return err
205		}
206
207		_, err = io.Copy(dst, r)
208		if err != nil {
209			r.Close()
210			dst.Close()
211			return err
212		}
213
214		err = r.Close()
215		if err != nil {
216			dst.Close()
217			return err
218		}
219
220		err = dst.Close()
221	} else if storage.linkMethod == LinkMethodSymLink {
222		err = sourcePool.(aptly.LocalPackagePool).Symlink(sourcePath, filepath.Join(poolPath, baseName))
223	} else {
224		err = sourcePool.(aptly.LocalPackagePool).Link(sourcePath, filepath.Join(poolPath, baseName))
225	}
226
227	return err
228}
229
230// Filelist returns list of files under prefix
231func (storage *PublishedStorage) Filelist(prefix string) ([]string, error) {
232	root := filepath.Join(storage.rootPath, prefix)
233	result := []string{}
234
235	err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
236		if err != nil {
237			return err
238		}
239		if !info.IsDir() {
240			result = append(result, path[len(root)+1:])
241		}
242		return nil
243	})
244
245	if err != nil && os.IsNotExist(err) {
246		// file path doesn't exist, consider it empty
247		return []string{}, nil
248	}
249
250	return result, err
251}
252
253// RenameFile renames (moves) file
254func (storage *PublishedStorage) RenameFile(oldName, newName string) error {
255	return os.Rename(filepath.Join(storage.rootPath, oldName), filepath.Join(storage.rootPath, newName))
256}
257
258// SymLink creates a symbolic link, which can be read with ReadLink
259func (storage *PublishedStorage) SymLink(src string, dst string) error {
260	return os.Symlink(filepath.Join(storage.rootPath, src), filepath.Join(storage.rootPath, dst))
261}
262
263// HardLink creates a hardlink of a file
264func (storage *PublishedStorage) HardLink(src string, dst string) error {
265	return os.Link(filepath.Join(storage.rootPath, src), filepath.Join(storage.rootPath, dst))
266}
267
268// FileExists returns true if path exists
269func (storage *PublishedStorage) FileExists(path string) (bool, error) {
270	if _, err := os.Lstat(filepath.Join(storage.rootPath, path)); os.IsNotExist(err) {
271		return false, nil
272	}
273
274	return true, nil
275}
276
277// ReadLink returns the symbolic link pointed to by path (relative to storage
278// root)
279func (storage *PublishedStorage) ReadLink(path string) (string, error) {
280	absPath, err := os.Readlink(filepath.Join(storage.rootPath, path))
281	if err != nil {
282		return absPath, err
283	}
284	return filepath.Rel(storage.rootPath, absPath)
285}
286