1// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
2// See LICENSE.txt for license information.
3
4package filestore
5
6import (
7	"bytes"
8	"io"
9	"io/ioutil"
10	"os"
11	"path/filepath"
12	"time"
13
14	"github.com/pkg/errors"
15
16	"github.com/mattermost/mattermost-server/v6/shared/mlog"
17)
18
19const (
20	TestFilePath = "/testfile"
21)
22
23type LocalFileBackend struct {
24	directory string
25}
26
27// copyFile will copy a file from src path to dst path.
28// Overwrites any existing files at dst.
29// Permissions are copied from file at src to the new file at dst.
30func copyFile(src, dst string) (err error) {
31	in, err := os.Open(src)
32	if err != nil {
33		return
34	}
35	defer in.Close()
36
37	if err = os.MkdirAll(filepath.Dir(dst), os.ModePerm); err != nil {
38		return
39	}
40	out, err := os.Create(dst)
41	if err != nil {
42		return
43	}
44	defer func() {
45		if e := out.Close(); e != nil {
46			err = e
47		}
48	}()
49
50	_, err = io.Copy(out, in)
51	if err != nil {
52		return
53	}
54
55	err = out.Sync()
56	if err != nil {
57		return
58	}
59
60	stat, err := os.Stat(src)
61	if err != nil {
62		return
63	}
64	err = os.Chmod(dst, stat.Mode())
65	if err != nil {
66		return
67	}
68
69	return
70}
71
72func (b *LocalFileBackend) TestConnection() error {
73	f := bytes.NewReader([]byte("testingwrite"))
74	if _, err := writeFileLocally(f, filepath.Join(b.directory, TestFilePath)); err != nil {
75		return errors.Wrap(err, "unable to write to the local filesystem storage")
76	}
77	os.Remove(filepath.Join(b.directory, TestFilePath))
78	mlog.Debug("Able to write files to local storage.")
79	return nil
80}
81
82func (b *LocalFileBackend) Reader(path string) (ReadCloseSeeker, error) {
83	f, err := os.Open(filepath.Join(b.directory, path))
84	if err != nil {
85		return nil, errors.Wrapf(err, "unable to open file %s", path)
86	}
87	return f, nil
88}
89
90func (b *LocalFileBackend) ReadFile(path string) ([]byte, error) {
91	f, err := ioutil.ReadFile(filepath.Join(b.directory, path))
92	if err != nil {
93		return nil, errors.Wrapf(err, "unable to read file %s", path)
94	}
95	return f, nil
96}
97
98func (b *LocalFileBackend) FileExists(path string) (bool, error) {
99	_, err := os.Stat(filepath.Join(b.directory, path))
100
101	if os.IsNotExist(err) {
102		return false, nil
103	}
104
105	if err != nil {
106		return false, errors.Wrapf(err, "unable to know if file %s exists", path)
107	}
108	return true, nil
109}
110
111func (b *LocalFileBackend) FileSize(path string) (int64, error) {
112	info, err := os.Stat(filepath.Join(b.directory, path))
113	if err != nil {
114		return 0, errors.Wrapf(err, "unable to get file size for %s", path)
115	}
116	return info.Size(), nil
117}
118
119func (b *LocalFileBackend) FileModTime(path string) (time.Time, error) {
120	info, err := os.Stat(filepath.Join(b.directory, path))
121	if err != nil {
122		return time.Time{}, errors.Wrapf(err, "unable to get modification time for file %s", path)
123	}
124	return info.ModTime(), nil
125}
126
127func (b *LocalFileBackend) CopyFile(oldPath, newPath string) error {
128	if err := copyFile(filepath.Join(b.directory, oldPath), filepath.Join(b.directory, newPath)); err != nil {
129		return errors.Wrapf(err, "unable to copy file from %s to %s", oldPath, newPath)
130	}
131	return nil
132}
133
134func (b *LocalFileBackend) MoveFile(oldPath, newPath string) error {
135	if err := os.MkdirAll(filepath.Dir(filepath.Join(b.directory, newPath)), 0750); err != nil {
136		return errors.Wrapf(err, "unable to create the new destination directory %s", filepath.Dir(newPath))
137	}
138
139	if err := os.Rename(filepath.Join(b.directory, oldPath), filepath.Join(b.directory, newPath)); err != nil {
140		return errors.Wrapf(err, "unable to move the file to %s to the destination directory", newPath)
141	}
142
143	return nil
144}
145
146func (b *LocalFileBackend) WriteFile(fr io.Reader, path string) (int64, error) {
147	return writeFileLocally(fr, filepath.Join(b.directory, path))
148}
149
150func writeFileLocally(fr io.Reader, path string) (int64, error) {
151	if err := os.MkdirAll(filepath.Dir(path), 0750); err != nil {
152		directory, _ := filepath.Abs(filepath.Dir(path))
153		return 0, errors.Wrapf(err, "unable to create the directory %s for the file %s", directory, path)
154	}
155	fw, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
156	if err != nil {
157		return 0, errors.Wrapf(err, "unable to open the file %s to write the data", path)
158	}
159	defer fw.Close()
160	written, err := io.Copy(fw, fr)
161	if err != nil {
162		return written, errors.Wrapf(err, "unable write the data in the file %s", path)
163	}
164	return written, nil
165}
166
167func (b *LocalFileBackend) AppendFile(fr io.Reader, path string) (int64, error) {
168	fp := filepath.Join(b.directory, path)
169	if _, err := os.Stat(fp); err != nil {
170		return 0, errors.Wrapf(err, "unable to find the file %s to append the data", path)
171	}
172	fw, err := os.OpenFile(fp, os.O_WRONLY|os.O_APPEND, 0600)
173	if err != nil {
174		return 0, errors.Wrapf(err, "unable to open the file %s to append the data", path)
175	}
176	defer fw.Close()
177	written, err := io.Copy(fw, fr)
178	if err != nil {
179		return written, errors.Wrapf(err, "unable append the data in the file %s", path)
180	}
181	return written, nil
182}
183
184func (b *LocalFileBackend) RemoveFile(path string) error {
185	if err := os.Remove(filepath.Join(b.directory, path)); err != nil {
186		return errors.Wrapf(err, "unable to remove the file %s", path)
187	}
188	return nil
189}
190
191func (b *LocalFileBackend) ListDirectory(path string) ([]string, error) {
192	var paths []string
193	fileInfos, err := ioutil.ReadDir(filepath.Join(b.directory, path))
194	if err != nil {
195		if os.IsNotExist(err) {
196			return paths, nil
197		}
198		return nil, errors.Wrapf(err, "unable to list the directory %s", path)
199	}
200	for _, fileInfo := range fileInfos {
201		paths = append(paths, filepath.Join(path, fileInfo.Name()))
202	}
203	return paths, nil
204}
205
206func (b *LocalFileBackend) RemoveDirectory(path string) error {
207	if err := os.RemoveAll(filepath.Join(b.directory, path)); err != nil {
208		return errors.Wrapf(err, "unable to remove the directory %s", path)
209	}
210	return nil
211}
212