Merge lp:~chipaca/snappy/copyfile into lp:~snappy-dev/snappy/snappy-moved-to-github

Proposed by John Lenton
Status: Merged
Approved by: Sergio Schvezov
Approved revision: 440
Merged at revision: 438
Proposed branch: lp:~chipaca/snappy/copyfile
Merge into: lp:~snappy-dev/snappy/snappy-moved-to-github
Diff against target: 505 lines (+385/-50)
8 files modified
helpers/cp.go (+91/-0)
helpers/cp_linux.go (+46/-0)
helpers/cp_linux_test.go (+44/-0)
helpers/cp_other.go (+30/-0)
helpers/cp_test.go (+169/-0)
policy/policy.go (+3/-28)
policy/policy_test.go (+1/-1)
snappy/build.go (+1/-21)
To merge this branch: bzr merge lp:~chipaca/snappy/copyfile
Reviewer Review Type Date Requested Status
Sergio Schvezov Approve
John Lenton (community) Abstain
Review via email: mp+258078@code.launchpad.net

Commit message

Moved two implementations of Copy into helpers with the linux implementation being based on sendfile.

Description of the change

Moved two implementations of Copy into helpers.

Because I was having fun, also implemented copy using sendfile when in linux.

Using sendfile avoids at least two copies over read/write; more if copying multiple chunks of data (currently golang's io does chunks of 32k).

On the bbb, copying a 10M file using io.Copy takes .79s/.03s/.26s (real/user/sys); doing it using sendfile uses .64s/.01s/.13s (averaged over 10 runs).

This requires a kernel newer than 2.6.33, which should not be a problem.

To post a comment you must log in.
Revision history for this message
John Lenton (chipaca) wrote :

Moving to WIP, because I kinda forgot all about permissions.

Revision history for this message
John Lenton (chipaca) wrote :

All set now :)

Revision history for this message
Sergio Schvezov (sergiusens) wrote :

Just really quickly going to say THANK YOU.

The split of OS dependent implementations is also welcoming.

I'll do a proper review later today during layovers

Revision history for this message
Michael Vogt (mvo) wrote :

This looks very nice, thanks for doing this!

META: It seems this is a good time to branch trunk into trunk and 15.04. This branch should go to trunk and I think we want to do fixes for 15.04 as well (but this branch looks more like trunk/devel material to me).

Some comments/questions inline. I really like that this branch removes duplicated code and moves in into a single well tested (and speedy!) place.

Revision history for this message
John Lenton (chipaca) wrote :

And neither of you spotted that I was not returning the error from copyfile. heh.

review: Needs Fixing
lp:~chipaca/snappy/copyfile updated
439. By John Lenton

mock all the things. Then, test all the things.

Revision history for this message
John Lenton (chipaca) wrote :

Fixed :)

review: Abstain
Revision history for this message
John Lenton (chipaca) wrote :

Michael, I think this pretty much covers the test you wanted ;)

Revision history for this message
Sergio Schvezov (sergiusens) wrote :

one comment, looks good otherwise

review: Needs Fixing
lp:~chipaca/snappy/copyfile updated
440. By John Lenton

durr

Revision history for this message
Sergio Schvezov (sergiusens) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'helpers/cp.go'
2--- helpers/cp.go 1970-01-01 00:00:00 +0000
3+++ helpers/cp.go 2015-05-05 22:17:11 +0000
4@@ -0,0 +1,91 @@
5+/*
6+ * Copyright (C) 2014-2015 Canonical Ltd
7+ *
8+ * This program is free software: you can redistribute it and/or modify
9+ * it under the terms of the GNU General Public License version 3 as
10+ * published by the Free Software Foundation.
11+ *
12+ * This program is distributed in the hope that it will be useful,
13+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+ * GNU General Public License for more details.
16+ *
17+ * You should have received a copy of the GNU General Public License
18+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
19+ *
20+ */
21+
22+package helpers
23+
24+import (
25+ "fmt"
26+ "os"
27+)
28+
29+// CopyFlag is used to tweak the behaviour of CopyFile
30+type CopyFlag uint8
31+
32+const (
33+ // CopyFlagDefault is the default behaviour
34+ CopyFlagDefault CopyFlag = 0
35+ // CopyFlagSync does a sync after copying the files
36+ CopyFlagSync CopyFlag = 1 << iota
37+)
38+
39+var (
40+ openfile = doOpenFile
41+ copyfile = doCopyFile
42+)
43+
44+type fileish interface {
45+ Close() error
46+ Sync() error
47+ Fd() uintptr
48+ Stat() (os.FileInfo, error)
49+ Read([]byte) (int, error)
50+ Write([]byte) (int, error)
51+}
52+
53+func doOpenFile(name string, flag int, perm os.FileMode) (fileish, error) {
54+ return os.OpenFile(name, flag, perm)
55+}
56+
57+// CopyFile copies src to dst
58+func CopyFile(src, dst string, flags CopyFlag) (err error) {
59+ fin, err := openfile(src, os.O_RDONLY, 0)
60+ if err != nil {
61+ return fmt.Errorf("unable to open %s: %v", src, err)
62+ }
63+ defer func() {
64+ if cerr := fin.Close(); cerr != nil && err == nil {
65+ err = fmt.Errorf("when closing %s: %v", src, cerr)
66+ }
67+ }()
68+
69+ fi, err := fin.Stat()
70+ if err != nil {
71+ return fmt.Errorf("unable to stat %s: %v", src, err)
72+ }
73+
74+ fout, err := openfile(dst, os.O_WRONLY|os.O_CREATE|os.O_EXCL, fi.Mode()) //
75+ if err != nil {
76+ return fmt.Errorf("unable to create %s: %v", dst, err)
77+ }
78+ defer func() {
79+ if cerr := fout.Close(); cerr != nil && err == nil {
80+ err = fmt.Errorf("when closing %s: %v", dst, cerr)
81+ }
82+ }()
83+
84+ if err := copyfile(fin, fout, fi); err != nil {
85+ return fmt.Errorf("unable to copy %s to %s: %v", src, dst, err)
86+ }
87+
88+ if flags&CopyFlagSync != 0 {
89+ if err = fout.Sync(); err != nil {
90+ return fmt.Errorf("unable to sync %s: %v", dst, err)
91+ }
92+ }
93+
94+ return nil
95+}
96
97=== added file 'helpers/cp_linux.go'
98--- helpers/cp_linux.go 1970-01-01 00:00:00 +0000
99+++ helpers/cp_linux.go 2015-05-05 22:17:11 +0000
100@@ -0,0 +1,46 @@
101+/*
102+ * Copyright (C) 2014-2015 Canonical Ltd
103+ *
104+ * This program is free software: you can redistribute it and/or modify
105+ * it under the terms of the GNU General Public License version 3 as
106+ * published by the Free Software Foundation.
107+ *
108+ * This program is distributed in the hope that it will be useful,
109+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
110+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
111+ * GNU General Public License for more details.
112+ *
113+ * You should have received a copy of the GNU General Public License
114+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
115+ *
116+ */
117+
118+package helpers
119+
120+import (
121+ "os"
122+ "syscall"
123+)
124+
125+const maxint = int64(^uint(0) >> 1)
126+
127+var maxcp = maxint // overridden in testing
128+
129+func doCopyFile(fin, fout fileish, fi os.FileInfo) error {
130+ size := fi.Size()
131+ var offset int64
132+ for offset < size {
133+ // sendfile is funny; it only copies up to maxint
134+ // bytes at a time, but takes an int64 offset.
135+ count := size - offset
136+ if count > maxcp {
137+ count = maxcp
138+ }
139+
140+ if _, err := syscall.Sendfile(int(fout.Fd()), int(fin.Fd()), &offset, int(count)); err != nil {
141+ return err
142+ }
143+ }
144+
145+ return nil
146+}
147
148=== added file 'helpers/cp_linux_test.go'
149--- helpers/cp_linux_test.go 1970-01-01 00:00:00 +0000
150+++ helpers/cp_linux_test.go 2015-05-05 22:17:11 +0000
151@@ -0,0 +1,44 @@
152+/*
153+ * Copyright (C) 2014-2015 Canonical Ltd
154+ *
155+ * This program is free software: you can redistribute it and/or modify
156+ * it under the terms of the GNU General Public License version 3 as
157+ * published by the Free Software Foundation.
158+ *
159+ * This program is distributed in the hope that it will be useful,
160+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
161+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
162+ * GNU General Public License for more details.
163+ *
164+ * You should have received a copy of the GNU General Public License
165+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
166+ *
167+ */
168+
169+package helpers
170+
171+import (
172+ "io/ioutil"
173+ "os"
174+
175+ . "launchpad.net/gocheck"
176+)
177+
178+func (s *cpSuite) TestCpMulti(c *C) {
179+ maxcp = 2
180+ defer func() { maxcp = maxint }()
181+
182+ c.Check(CopyFile(s.f1, s.f2, CopyFlagDefault), IsNil)
183+ bs, err := ioutil.ReadFile(s.f2)
184+ c.Check(err, IsNil)
185+ c.Check(bs, DeepEquals, s.data)
186+}
187+
188+func (s *cpSuite) TestDoCpErr(c *C) {
189+ f1, err := os.Open(s.f1)
190+ c.Assert(err, IsNil)
191+ st, err := f1.Stat()
192+ c.Assert(err, IsNil)
193+ // force an error by asking it to write to a readonly stream
194+ c.Check(doCopyFile(f1, os.Stdin, st), NotNil)
195+}
196
197=== added file 'helpers/cp_other.go'
198--- helpers/cp_other.go 1970-01-01 00:00:00 +0000
199+++ helpers/cp_other.go 2015-05-05 22:17:11 +0000
200@@ -0,0 +1,30 @@
201+// +build !linux
202+
203+/*
204+ * Copyright (C) 2014-2015 Canonical Ltd
205+ *
206+ * This program is free software: you can redistribute it and/or modify
207+ * it under the terms of the GNU General Public License version 3 as
208+ * published by the Free Software Foundation.
209+ *
210+ * This program is distributed in the hope that it will be useful,
211+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
212+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
213+ * GNU General Public License for more details.
214+ *
215+ * You should have received a copy of the GNU General Public License
216+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
217+ *
218+ */
219+
220+package helpers
221+
222+import (
223+ "io"
224+ "os"
225+)
226+
227+func doCopyFile(fin, fout fileish, fi os.FileInfo) error {
228+ _, err := io.Copy(fout, fin)
229+ return err
230+}
231
232=== added file 'helpers/cp_test.go'
233--- helpers/cp_test.go 1970-01-01 00:00:00 +0000
234+++ helpers/cp_test.go 2015-05-05 22:17:11 +0000
235@@ -0,0 +1,169 @@
236+/*
237+ * Copyright (C) 2014-2015 Canonical Ltd
238+ *
239+ * This program is free software: you can redistribute it and/or modify
240+ * it under the terms of the GNU General Public License version 3 as
241+ * published by the Free Software Foundation.
242+ *
243+ * This program is distributed in the hope that it will be useful,
244+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
245+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
246+ * GNU General Public License for more details.
247+ *
248+ * You should have received a copy of the GNU General Public License
249+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
250+ *
251+ */
252+
253+package helpers
254+
255+import (
256+ "errors"
257+ "io/ioutil"
258+ "os"
259+ "path/filepath"
260+ "strings"
261+ "time"
262+
263+ . "launchpad.net/gocheck"
264+)
265+
266+type cpSuite struct {
267+ dir string
268+ f1 string
269+ f2 string
270+ data []byte
271+ log []string
272+ errs []error
273+ idx int
274+}
275+
276+var _ = Suite(&cpSuite{})
277+
278+func (s *cpSuite) mockCopyFile(fin, fout fileish, fi os.FileInfo) error {
279+ return s.µ("copyfile")
280+}
281+
282+func (s *cpSuite) mockOpenFile(name string, flag int, perm os.FileMode) (fileish, error) {
283+ return &mockfile{s}, s.µ("open")
284+}
285+
286+func (s *cpSuite) µ(msg string) (err error) {
287+ s.log = append(s.log, msg)
288+ if len(s.errs) > 0 {
289+ err = s.errs[0]
290+ if len(s.errs) > 1 {
291+ s.errs = s.errs[1:]
292+ }
293+ }
294+
295+ return
296+}
297+
298+func (s *cpSuite) SetUpTest(c *C) {
299+ s.errs = nil
300+ s.log = nil
301+ s.dir = c.MkDir()
302+ s.f1 = filepath.Join(s.dir, "f1")
303+ s.f2 = filepath.Join(s.dir, "f2")
304+ s.data = []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
305+ c.Assert(ioutil.WriteFile(s.f1, s.data, 0644), IsNil)
306+}
307+
308+func (s *cpSuite) mock() {
309+ copyfile = s.mockCopyFile
310+ openfile = s.mockOpenFile
311+}
312+
313+func (s *cpSuite) TearDownTest(c *C) {
314+ copyfile = doCopyFile
315+ openfile = doOpenFile
316+}
317+
318+func (s *cpSuite) TestCp(c *C) {
319+ c.Check(CopyFile(s.f1, s.f2, CopyFlagDefault), IsNil)
320+ bs, err := ioutil.ReadFile(s.f2)
321+ c.Check(err, IsNil)
322+ c.Check(bs, DeepEquals, s.data)
323+}
324+
325+func (s *cpSuite) TestCpSync(c *C) {
326+ s.mock()
327+ c.Check(CopyFile(s.f1, s.f2, CopyFlagDefault), IsNil)
328+ c.Check(strings.Join(s.log, ":"), Not(Matches), `.*:sync(:.*)?`)
329+
330+ s.log = nil
331+ c.Check(CopyFile(s.f1, s.f2, CopyFlagSync), IsNil)
332+ c.Check(strings.Join(s.log, ":"), Matches, `(.*:)?sync(:.*)?`)
333+}
334+
335+func (s *cpSuite) TestCpCantOpen(c *C) {
336+ s.mock()
337+ s.errs = []error{errors.New("xyzzy"), nil}
338+
339+ c.Check(CopyFile(s.f1, s.f2, CopyFlagSync), ErrorMatches, `unable to open \S+/f1: xyzzy`)
340+}
341+
342+func (s *cpSuite) TestCpCantStat(c *C) {
343+ s.mock()
344+ s.errs = []error{nil, errors.New("xyzzy"), nil}
345+
346+ c.Check(CopyFile(s.f1, s.f2, CopyFlagSync), ErrorMatches, `unable to stat \S+/f1: xyzzy`)
347+}
348+
349+func (s *cpSuite) TestCpCantCreate(c *C) {
350+ s.mock()
351+ s.errs = []error{nil, nil, errors.New("xyzzy"), nil}
352+
353+ c.Check(CopyFile(s.f1, s.f2, CopyFlagSync), ErrorMatches, `unable to create \S+/f2: xyzzy`)
354+}
355+
356+func (s *cpSuite) TestCpCantCopy(c *C) {
357+ s.mock()
358+ s.errs = []error{nil, nil, nil, errors.New("xyzzy"), nil}
359+
360+ c.Check(CopyFile(s.f1, s.f2, CopyFlagSync), ErrorMatches, `unable to copy \S+/f1 to \S+/f2: xyzzy`)
361+}
362+
363+func (s *cpSuite) TestCpCantSync(c *C) {
364+ s.mock()
365+ s.errs = []error{nil, nil, nil, nil, errors.New("xyzzy"), nil}
366+
367+ c.Check(CopyFile(s.f1, s.f2, CopyFlagSync), ErrorMatches, `unable to sync \S+/f2: xyzzy`)
368+}
369+
370+func (s *cpSuite) TestCpCantStop2(c *C) {
371+ s.mock()
372+ s.errs = []error{nil, nil, nil, nil, nil, errors.New("xyzzy"), nil}
373+
374+ c.Check(CopyFile(s.f1, s.f2, CopyFlagSync), ErrorMatches, `when closing \S+/f2: xyzzy`)
375+}
376+
377+func (s *cpSuite) TestCpCantStop1(c *C) {
378+ s.mock()
379+ s.errs = []error{nil, nil, nil, nil, nil, nil, errors.New("xyzzy"), nil}
380+
381+ c.Check(CopyFile(s.f1, s.f2, CopyFlagSync), ErrorMatches, `when closing \S+/f1: xyzzy`)
382+}
383+
384+type mockfile struct {
385+ s *cpSuite
386+}
387+
388+var mockst = mockstat{}
389+
390+func (f *mockfile) Close() error { return f.s.µ("close") }
391+func (f *mockfile) Sync() error { return f.s.µ("sync") }
392+func (f *mockfile) Fd() uintptr { f.s.µ("fd"); return 42 }
393+func (f *mockfile) Read([]byte) (int, error) { return 0, f.s.µ("read") }
394+func (f *mockfile) Write([]byte) (int, error) { return 0, f.s.µ("write") }
395+func (f *mockfile) Stat() (os.FileInfo, error) { return mockst, f.s.µ("stat") }
396+
397+type mockstat struct{}
398+
399+func (mockstat) Name() string { return "mockstat" }
400+func (mockstat) Size() int64 { return 42 }
401+func (mockstat) Mode() os.FileMode { return 0644 }
402+func (mockstat) ModTime() time.Time { return time.Now() }
403+func (mockstat) IsDir() bool { return false }
404+func (mockstat) Sys() interface{} { return nil }
405
406=== modified file 'policy/policy.go'
407--- policy/policy.go 2015-04-19 14:02:33 +0000
408+++ policy/policy.go 2015-05-05 22:17:11 +0000
409@@ -21,7 +21,6 @@
410
411 import (
412 "fmt"
413- "io"
414 "os"
415 "path/filepath"
416
417@@ -90,33 +89,9 @@
418 }
419 case install:
420 // do the copy
421- fin, err := os.Open(file)
422- if err != nil {
423- return fmt.Errorf("unable to read %v: %v", file, err)
424- }
425- defer func() {
426- if cerr := fin.Close(); cerr != nil && err == nil {
427- err = fmt.Errorf("when closing %v: %v", file, cerr)
428- }
429- }()
430-
431- fout, err := os.Create(targetFile)
432- if err != nil {
433- return fmt.Errorf("unable to create %v: %v", targetFile, err)
434- }
435- defer func() {
436- if cerr := fout.Close(); cerr != nil && err == nil {
437- err = fmt.Errorf("when closing %v: %v", targetFile, cerr)
438- }
439- }()
440-
441- if _, err = io.Copy(fout, fin); err != nil {
442- return fmt.Errorf("unable to copy %v to %v: %v", file, targetFile, err)
443- }
444- if err = fout.Sync(); err != nil {
445- return fmt.Errorf("when syncing %v: %v", targetFile, err)
446- }
447-
448+ if err := helpers.CopyFile(file, targetFile, helpers.CopyFlagSync); err != nil {
449+ return err
450+ }
451 default:
452 return fmt.Errorf("unknown operation %s", op)
453 }
454
455=== modified file 'policy/policy_test.go'
456--- policy/policy_test.go 2015-04-17 06:42:44 +0000
457+++ policy/policy_test.go 2015-05-05 22:17:11 +0000
458@@ -118,7 +118,7 @@
459 fn := filepath.Join(s.appg, "policygroups0")
460 c.Assert(os.Chmod(fn, 0), IsNil)
461 err := iterOp(install, filepath.Join(s.appg, "*"), s.dest, "foo_")
462- c.Check(err, ErrorMatches, ".*unable to read.*")
463+ c.Check(err, ErrorMatches, ".*unable to open.*")
464 }
465
466 func (s *policySuite) TestIterOpInstallBadTarget(c *C) {
467
468=== modified file 'snappy/build.go'
469--- snappy/build.go 2015-04-16 05:03:51 +0000
470+++ snappy/build.go 2015-05-05 22:17:11 +0000
471@@ -21,7 +21,6 @@
472 "bufio"
473 "encoding/json"
474 "fmt"
475- "io"
476 "io/ioutil"
477 "os"
478 "os/exec"
479@@ -394,26 +393,7 @@
480 return nil
481 }
482 // sigh. ok, copy it is.
483- in, err := os.Open(path)
484- if err != nil {
485- return err
486- }
487- defer in.Close()
488-
489- out, err := os.OpenFile(dest, os.O_WRONLY|os.O_CREATE|os.O_EXCL, info.Mode())
490- if err != nil {
491- return err
492- }
493- defer func() {
494- // XXX: write a test for this. I dare you. I double-dare you.
495- xerr := out.Close()
496- if err == nil {
497- err = xerr
498- }
499- }()
500- _, err = io.Copy(out, in)
501- // no need to sync, as it's a tempdir
502- return err
503+ return helpers.CopyFile(path, dest, helpers.CopyFlagDefault)
504 })
505 }
506

Subscribers

People subscribed via source and target branches