Merge lp:~mandel/ciborium/remount-drives-take-2 into lp:ciborium

Proposed by Manuel de la Peña on 2015-02-05
Status: Merged
Approved by: Sergio Schvezov on 2015-02-11
Approved revision: 97
Merged at revision: 88
Proposed branch: lp:~mandel/ciborium/remount-drives-take-2
Merge into: lp:ciborium
Diff against target: 1170 lines (+962/-110)
8 files modified
cmd/ciborium/main.go (+2/-0)
udisks2/common_test.go (+28/-0)
udisks2/dispatcher.go (+174/-0)
udisks2/jobs.go (+147/-0)
udisks2/jobs_test.go (+192/-0)
udisks2/properties.go (+145/-0)
udisks2/properties_test.go (+210/-0)
udisks2/udisks2.go (+64/-110)
To merge this branch: bzr merge lp:~mandel/ciborium/remount-drives-take-2
Reviewer Review Type Date Requested Status
Sergio Schvezov 2015-02-05 Approve on 2015-02-11
PS Jenkins bot continuous-integration Approve on 2015-02-11
Review via email: mp+248783@code.launchpad.net

Commit Message

Ensure that we remount the drives after we format them. Remove deadlocks.

Description of the Change

The follwing MR does a number of changes to ensure that we can handle the dbus events in an asycn way to work around this go dbus bug https://bugs.launchpad.net/go-dbus/+bug/1416352

* In the code now jobs are handled by a job manager that keeps track of them to later fwd to a channel with their state.
* The udisks code listens to the diff channels and reacts according to them:
    * Additions: new interfaces are added and therefore a new drive is present.
    * Removals: Interfaces are done. A drive has gone.
    * Jobs: the diff jons performed on udisk that do not represent drives (Erase, Format)
* Udisks tracks the diff formatted drives and will mount them again when the Filesystem interface is re-added.
* Tests have been added to the changes.

To post a comment you must log in.
93. By Manuel de la Peña on 2015-02-05

Apply go fmt.

94. By Manuel de la Peña on 2015-02-05

Remove ugly log line.

Sergio Schvezov (sergiusens) wrote :

Thanks, the code looks good; just a couple of minor comments.

review: Needs Fixing
Manuel de la Peña (mandel) wrote :

Some answers inline, other changes will come in the next push.

95. By Manuel de la Peña on 2015-02-09

Update code according to review.

Sergio Schvezov (sergiusens) wrote :

Some comments on the fixes (redaction)

and some new copyright header comments.

review: Needs Fixing
96. By Manuel de la Peña on 2015-02-11

Update docs and license.

97. By Manuel de la Peña on 2015-02-11

More typos.

Sergio Schvezov (sergiusens) wrote :

Tested, seems to work fine; there are some other corner cases that need fixing though (format when device is busy).

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'cmd/ciborium/main.go'
2--- cmd/ciborium/main.go 2014-11-30 23:42:50 +0000
3+++ cmd/ciborium/main.go 2015-02-11 15:31:38 +0000
4@@ -108,6 +108,8 @@
5 }
6
7 func main() {
8+ // set default logger flags to get more useful info
9+ log.SetFlags(log.LstdFlags | log.Lshortfile)
10
11 // Initialize i18n
12 gettext.SetLocale(gettext.LC_ALL, "")
13
14=== added file 'udisks2/common_test.go'
15--- udisks2/common_test.go 1970-01-01 00:00:00 +0000
16+++ udisks2/common_test.go 2015-02-11 15:31:38 +0000
17@@ -0,0 +1,28 @@
18+/*
19+ * Copyright 2015 Canonical Ltd.
20+ *
21+ * Authors:
22+ * Manuel de la Pena : manuel.delapena@cannical.com
23+ *
24+ * ciborium is free software; you can redistribute it and/or modify
25+ * it under the terms of the GNU General Public License as published by
26+ * the Free Software Foundation; version 3.
27+ *
28+ * ciborium is distributed in the hope that it will be useful,
29+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
30+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31+ * GNU General Public License for more details.
32+ *
33+ * You should have received a copy of the GNU General Public License
34+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
35+ */
36+
37+package udisks2
38+
39+import (
40+ "testing"
41+
42+ . "launchpad.net/gocheck"
43+)
44+
45+func Test(t *testing.T) { TestingT(t) }
46
47=== added file 'udisks2/dispatcher.go'
48--- udisks2/dispatcher.go 1970-01-01 00:00:00 +0000
49+++ udisks2/dispatcher.go 2015-02-11 15:31:38 +0000
50@@ -0,0 +1,174 @@
51+/*
52+ * Copyright 2015 Canonical Ltd.
53+ *
54+ * Authors:
55+ * Manuel de la Pena : manuel.delapena@cannical.com
56+ *
57+ * ciborium is free software; you can redistribute it and/or modify
58+ * it under the terms of the GNU General Public License as published by
59+ * the Free Software Foundation; version 3.
60+ *
61+ * ciborium is distributed in the hope that it will be useful,
62+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
63+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
64+ * GNU General Public License for more details.
65+ *
66+ * You should have received a copy of the GNU General Public License
67+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
68+ */
69+
70+package udisks2
71+
72+import (
73+ "log"
74+ "runtime"
75+ "sort"
76+ "strings"
77+
78+ "launchpad.net/go-dbus/v1"
79+)
80+
81+const (
82+ jobPrefixPath = "/org/freedesktop/UDisks2/jobs/"
83+ blockDevicesPath = "/org/freedesktop/UDisks2/block_devices/"
84+)
85+
86+type Interfaces []string
87+
88+type Event struct {
89+ Path dbus.ObjectPath
90+ Props InterfacesAndProperties
91+ Interfaces Interfaces
92+}
93+
94+// isRemovalEvent returns if an event represents an InterfacesRemoved signal from the dbus ObjectManager
95+// dbus interface. An event is a removal event when it carries a set of the interfaces that have been lost
96+// in a dbus object path.
97+func (e *Event) isRemovalEvent() bool {
98+ return len(e.Interfaces) != 0
99+}
100+
101+type dispatcher struct {
102+ conn *dbus.Connection
103+ additionsWatch *dbus.SignalWatch
104+ removalsWatch *dbus.SignalWatch
105+ Jobs chan Event
106+ Additions chan Event
107+ Removals chan Event
108+}
109+
110+func connectToSignal(conn *dbus.Connection, path dbus.ObjectPath, inter, member string) (*dbus.SignalWatch, error) {
111+ log.Println("Connecting to signal", path, inter, member)
112+ w, err := conn.WatchSignal(&dbus.MatchRule{
113+ Type: dbus.TypeSignal,
114+ Sender: dbusName,
115+ Interface: dbusObjectManagerInterface,
116+ Member: member,
117+ Path: path})
118+ return w, err
119+}
120+
121+// newDispatcher tries to return a dispatcher instance that is connected to the dbus signal that must be listened
122+// in order to interact with UDisk. If the connection with the signals could not be performed an error is returned.
123+func newDispatcher(conn *dbus.Connection) (*dispatcher, error) {
124+ log.Print("Creating new dispatcher.")
125+ add_w, err := connectToSignal(conn, dbusObject, dbusObjectManagerInterface, dbusAddedSignal)
126+ if err != nil {
127+ return nil, err
128+ }
129+
130+ remove_w, err := connectToSignal(conn, dbusObject, dbusObjectManagerInterface, dbusRemovedSignal)
131+ if err != nil {
132+ return nil, err
133+ }
134+
135+ jobs_ch := make(chan Event)
136+ additions_ch := make(chan Event)
137+ remove_ch := make(chan Event)
138+
139+ d := &dispatcher{conn, add_w, remove_w, jobs_ch, additions_ch, remove_ch}
140+ runtime.SetFinalizer(d, cleanDispatcherData)
141+
142+ // create the go routines used to grab the events and dispatch them accordingly
143+ return d, nil
144+}
145+
146+func (d *dispatcher) Init() {
147+ log.Print("Init the dispatcher.")
148+ go func() {
149+ for msg := range d.additionsWatch.C {
150+ var event Event
151+ if err := msg.Args(&event.Path, &event.Props); err != nil {
152+ log.Print(err)
153+ continue
154+ }
155+ log.Print("New addition event for path ", event.Path, event.Props)
156+ d.processAddition(event)
157+ }
158+ }()
159+
160+ go func() {
161+ for msg := range d.removalsWatch.C {
162+ log.Print("New removal event for path.")
163+ var event Event
164+ if err := msg.Args(&event.Path, &event.Interfaces); err != nil {
165+ log.Print(err)
166+ continue
167+ }
168+ sort.Strings(event.Interfaces)
169+ log.Print("Removal event is ", event.Path, " Interfaces: ", event.Interfaces)
170+ d.processRemoval(event)
171+ }
172+ }()
173+}
174+
175+func (d *dispatcher) free() {
176+ log.Print("Cleaning dispatcher resources.")
177+ // cancel all watches so that goroutines are done and close the
178+ // channels
179+ d.additionsWatch.Cancel()
180+ d.removalsWatch.Cancel()
181+ close(d.Jobs)
182+ close(d.Additions)
183+ close(d.Removals)
184+}
185+
186+func (d *dispatcher) processAddition(event Event) {
187+ log.Print("Processing an add event from path ", event.Path)
188+ // according to the object path we know if the even was a job one or not
189+ if strings.HasPrefix(string(event.Path), jobPrefixPath) {
190+ log.Print("Sending a new job event.")
191+ select {
192+ case d.Jobs <- event:
193+ log.Print("Sent event ", event.Path)
194+ }
195+ } else {
196+ log.Print("Sending a new general add event.")
197+ select {
198+ case d.Additions <- event:
199+ log.Print("Sent event ", event.Path)
200+ }
201+ }
202+}
203+
204+func (d *dispatcher) processRemoval(event Event) {
205+ log.Print("Processing a remove event from path ", event.Path)
206+ // according to the object path we know if the even was a job one or not
207+ if strings.HasPrefix(string(event.Path), jobPrefixPath) {
208+ log.Print("Sending a new remove job event.")
209+ select {
210+ case d.Jobs <- event:
211+ log.Println("Sent event", event.Path)
212+ }
213+ } else {
214+ log.Print("Sending a new general remove event.")
215+ select {
216+ case d.Removals <- event:
217+ log.Println("Sent event", event.Path)
218+ }
219+ }
220+}
221+
222+func cleanDispatcherData(d *dispatcher) {
223+ d.free()
224+}
225
226=== added file 'udisks2/jobs.go'
227--- udisks2/jobs.go 1970-01-01 00:00:00 +0000
228+++ udisks2/jobs.go 2015-02-11 15:31:38 +0000
229@@ -0,0 +1,147 @@
230+/*
231+ * Copyright 2015 Canonical Ltd.
232+ *
233+ * Authors:
234+ * Manuel de la Pena : manuel.delapena@cannical.com
235+ *
236+ * ciborium is free software; you can redistribute it and/or modify
237+ * it under the terms of the GNU General Public License as published by
238+ * the Free Software Foundation; version 3.
239+ *
240+ * ciborium is distributed in the hope that it will be useful,
241+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
242+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
243+ * GNU General Public License for more details.
244+ *
245+ * You should have received a copy of the GNU General Public License
246+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
247+ */
248+
249+package udisks2
250+
251+import (
252+ "log"
253+ "runtime"
254+ "sort"
255+
256+ "launchpad.net/go-dbus/v1"
257+)
258+
259+type job struct {
260+ Event Event
261+ Operation string
262+ Paths []string
263+ WasCompleted bool
264+}
265+
266+type jobManager struct {
267+ onGoingJobs map[dbus.ObjectPath]job
268+ FormatEraseJobs chan job
269+ FormatMkfsJobs chan job
270+}
271+
272+func newJobManager(d *dispatcher) *jobManager {
273+ // listen to the diff job events and ensure that they are dealt with in the correct channel
274+ ongoing := make(map[dbus.ObjectPath]job)
275+ eraseChan := make(chan job)
276+ mkfsChan := make(chan job)
277+ m := &jobManager{ongoing, eraseChan, mkfsChan}
278+ runtime.SetFinalizer(m, cleanJobData)
279+
280+ // create a go routine that will filter the diff jobs
281+ go func() {
282+ for {
283+ select {
284+ case e := <-d.Jobs:
285+ log.Println("New event", e.Path, "Properties:", e.Props, "Interfaces:", e.Interfaces)
286+ if e.isRemovalEvent() {
287+ log.Print("Is removal event")
288+ m.processRemovalEvent(e)
289+ } else {
290+ m.processAdditionEvent(e)
291+ }
292+ }
293+ }
294+ log.Print("Job manager routine done")
295+ }()
296+ return m
297+}
298+
299+func (m *jobManager) processRemovalEvent(e Event) {
300+ log.Println("Deal with job event removal", e.Path, e.Interfaces)
301+ if job, ok := m.onGoingJobs[e.Path]; ok {
302+ // assert that we did loose the jobs interface, the dispatcher does sort the interfaces
303+ i := sort.SearchStrings(e.Interfaces, dbusJobInterface)
304+ if i != len(e.Interfaces) {
305+ log.Print("Job completed.")
306+ // complete event found
307+ job.WasCompleted = true
308+
309+ if job.Operation == formatErase {
310+ log.Print("Sending completed erase job")
311+ m.FormatEraseJobs <- job
312+ }
313+
314+ if job.Operation == formateMkfs {
315+ log.Print("Sending completed mkfs job")
316+ m.FormatMkfsJobs <- job
317+ }
318+
319+ log.Print("Removed ongoing job for path", e.Path)
320+ delete(m.onGoingJobs, e.Path)
321+ return
322+ } else {
323+ log.Println("Ignoring event for path", e.Path, "because the job interface was not lost")
324+ return
325+ }
326+ } else {
327+ log.Println("Ignoring event for path", e.Path)
328+ return
329+ }
330+}
331+
332+func (m *jobManager) processAdditionEvent(e Event) {
333+ j, ok := m.onGoingJobs[e.Path]
334+ if !ok {
335+ log.Println("Creating job for new path", e.Path)
336+ log.Println("New job operation", e.Props.jobOperation())
337+ operation := e.Props.jobOperation()
338+ var paths []string
339+ if e.Props.isMkfsFormatJob() {
340+ log.Print("Get paths from formatMkfs event.")
341+ paths = e.Props.getFormattedPaths()
342+ }
343+
344+ j = job{e, operation, paths, false}
345+ m.onGoingJobs[e.Path] = j
346+ } else {
347+ log.Print("Updating job for path ", e.Path)
348+ j.Event = e
349+ if e.Props.isEraseFormatJob() {
350+ j.Operation = formatErase
351+ }
352+ if e.Props.isMkfsFormatJob() {
353+ j.Operation = formateMkfs
354+ j.Paths = e.Props.getFormattedPaths()
355+ }
356+ }
357+
358+ if j.Operation == formatErase {
359+ log.Print("Sending rease job from addition.")
360+ m.FormatEraseJobs <- j
361+ } else if j.Operation == formateMkfs {
362+ log.Print("Sending format job from addition.")
363+ m.FormatMkfsJobs <- j
364+ } else {
365+ log.Println("Ignoring job event with operation", j.Operation)
366+ }
367+}
368+
369+func (m *jobManager) free() {
370+ close(m.FormatEraseJobs)
371+ close(m.FormatMkfsJobs)
372+}
373+
374+func cleanJobData(m *jobManager) {
375+ m.free()
376+}
377
378=== added file 'udisks2/jobs_test.go'
379--- udisks2/jobs_test.go 1970-01-01 00:00:00 +0000
380+++ udisks2/jobs_test.go 2015-02-11 15:31:38 +0000
381@@ -0,0 +1,192 @@
382+/*
383+ * Copyright 2015 Canonical Ltd.
384+ *
385+ * Authors:
386+ * Manuel de la Pena : manuel.delapena@cannical.com
387+ *
388+ * ciborium is free software; you can redistribute it and/or modify
389+ * it under the terms of the GNU General Public License as published by
390+ * the Free Software Foundation; version 3.
391+ *
392+ * ciborium is distributed in the hope that it will be useful,
393+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
394+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
395+ * GNU General Public License for more details.
396+ *
397+ * You should have received a copy of the GNU General Public License
398+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
399+ */
400+
401+package udisks2
402+
403+import (
404+ "launchpad.net/go-dbus/v1"
405+ . "launchpad.net/gocheck"
406+)
407+
408+type JobManagerTestSuite struct {
409+ ongoing map[dbus.ObjectPath]job
410+ events chan Event
411+ manager *jobManager
412+ completed chan bool
413+}
414+
415+var _ = Suite(&JobManagerTestSuite{})
416+
417+func (s *JobManagerTestSuite) SetUpTest(c *C) {
418+ s.ongoing = make(map[dbus.ObjectPath]job)
419+ eraseChan := make(chan job)
420+ mkfsChan := make(chan job)
421+
422+ s.manager = &jobManager{s.ongoing, eraseChan, mkfsChan}
423+ s.completed = make(chan bool)
424+}
425+
426+func (s *JobManagerTestSuite) TearDownTest(c *C) {
427+ close(s.manager.FormatEraseJobs)
428+ close(s.manager.FormatMkfsJobs)
429+ close(s.completed)
430+}
431+
432+func (s *JobManagerTestSuite) TestProcessAddEventNewErase(c *C) {
433+ path := dbus.ObjectPath("/org/freedesktop/UDisks2/jobs/3")
434+
435+ go func() {
436+ for j := range s.manager.FormatEraseJobs {
437+ c.Assert(j.Operation, Equals, formatErase)
438+ c.Assert(j.WasCompleted, Equals, false)
439+ // assert that the job is present in the ongoing map
440+ _, ok := s.ongoing[path]
441+ c.Assert(ok, Equals, true)
442+ s.completed <- true
443+ }
444+ }()
445+
446+ interfaces := make([]string, 0, 0)
447+
448+ props := make(map[string]VariantMap)
449+ props[dbusJobInterface] = make(map[string]dbus.Variant)
450+ props[dbusJobInterface][operationProperty] = dbus.Variant{formatErase}
451+ objsPaths := make([]string, 1, 1)
452+ objsPaths[0] = "/path/to/erased/fs"
453+ props[dbusJobInterface][objectsProperty] = dbus.Variant{objsPaths}
454+
455+ event := Event{path, props, interfaces}
456+ s.manager.processAdditionEvent(event)
457+ <-s.completed
458+}
459+
460+func (s *JobManagerTestSuite) TestProcessAddEventNewFormat(c *C) {
461+ path := dbus.ObjectPath("/org/freedesktop/UDisks2/jobs/1")
462+
463+ go func() {
464+ for j := range s.manager.FormatMkfsJobs {
465+ c.Assert(j.Operation, Equals, formateMkfs)
466+ c.Assert(j.WasCompleted, Equals, false)
467+ // assert that the job is present in the ongoing map
468+ _, ok := s.ongoing[path]
469+ c.Assert(ok, Equals, true)
470+ s.completed <- true
471+ }
472+ }()
473+
474+ interfaces := make([]string, 0, 0)
475+
476+ props := make(map[string]VariantMap)
477+ props[dbusJobInterface] = make(map[string]dbus.Variant)
478+ props[dbusJobInterface][operationProperty] = dbus.Variant{formateMkfs}
479+ objsPaths := make([]interface{}, 1, 1)
480+ objsPaths[0] = "/path/to/new/fs"
481+ props[dbusJobInterface][objectsProperty] = dbus.Variant{objsPaths}
482+
483+ event := Event{path, props, interfaces}
484+ s.manager.processAdditionEvent(event)
485+ <-s.completed
486+}
487+
488+func (s *JobManagerTestSuite) TestProcessAddEventPresent(c *C) {
489+ path := dbus.ObjectPath("/org/freedesktop/UDisks2/jobs/1")
490+
491+ // add a ongoing job for the given path
492+ s.ongoing[path] = job{}
493+
494+ go func() {
495+ for j := range s.manager.FormatMkfsJobs {
496+ c.Assert(j.Operation, Equals, formateMkfs)
497+ c.Assert(j.WasCompleted, Equals, false)
498+ // assert that the job is present in the ongoing map
499+ _, ok := s.ongoing[path]
500+ c.Assert(ok, Equals, true)
501+ s.completed <- true
502+ }
503+ }()
504+
505+ interfaces := make([]string, 0, 0)
506+
507+ props := make(map[string]VariantMap)
508+ props[dbusJobInterface] = make(map[string]dbus.Variant)
509+ props[dbusJobInterface][operationProperty] = dbus.Variant{formateMkfs}
510+ objsPaths := make([]interface{}, 1, 1)
511+ objsPaths[0] = "/path/to/new/fs"
512+ props[dbusJobInterface][objectsProperty] = dbus.Variant{objsPaths}
513+
514+ event := Event{path, props, interfaces}
515+ s.manager.processAdditionEvent(event)
516+ <-s.completed
517+}
518+
519+func (s *JobManagerTestSuite) TestProcessRemovalEventMissing(c *C) {
520+ path := dbus.ObjectPath("/org/freedesktop/UDisks2/jobs/1")
521+ interfaces := make([]string, 1, 1)
522+ interfaces[0] = dbusJobInterface
523+ props := make(map[string]VariantMap)
524+
525+ event := Event{path, props, interfaces}
526+ // nothing bad should happen
527+ s.manager.processRemovalEvent(event)
528+
529+}
530+
531+func (s *JobManagerTestSuite) TestProcessRemovalEventInterfaceMissing(c *C) {
532+ path := dbus.ObjectPath("/org/freedesktop/UDisks2/jobs/1")
533+ interfaces := make([]string, 1, 1)
534+ interfaces[0] = "com.test.Random"
535+ props := make(map[string]VariantMap)
536+
537+ event := Event{path, props, interfaces}
538+
539+ s.ongoing[path] = job{}
540+
541+ // nothing bad should happen
542+ s.manager.processRemovalEvent(event)
543+}
544+
545+func (s *JobManagerTestSuite) TestProcessRemovalEventMkfs(c *C) {
546+ path := dbus.ObjectPath("/org/freedesktop/UDisks2/jobs/1")
547+
548+ // create an erase job and add it to the ongoing map, check that the job
549+ // is fwd to the channel as completed and removed from the map
550+ formattedPaths := make([]string, 1, 1)
551+ formattedPaths[0] = "/one/path/to/a/fmormatted/fs/1"
552+ presentJob := job{Event{}, formateMkfs, formattedPaths, false}
553+
554+ s.ongoing[path] = presentJob
555+
556+ go func() {
557+ for j := range s.manager.FormatMkfsJobs {
558+ c.Assert(j.Operation, Equals, formateMkfs)
559+ c.Assert(j.WasCompleted, Equals, true)
560+ c.Assert(len(j.Paths), Equals, 1)
561+ s.completed <- true
562+ }
563+ }()
564+
565+ interfaces := make([]string, 1, 1)
566+ interfaces[0] = dbusJobInterface
567+ props := make(map[string]VariantMap)
568+
569+ event := Event{path, props, interfaces}
570+
571+ s.manager.processRemovalEvent(event)
572+ <-s.completed
573+}
574
575=== added file 'udisks2/properties.go'
576--- udisks2/properties.go 1970-01-01 00:00:00 +0000
577+++ udisks2/properties.go 2015-02-11 15:31:38 +0000
578@@ -0,0 +1,145 @@
579+/*
580+ * Copyright 2015 Canonical Ltd.
581+ *
582+ * Authors:
583+ * Sergio Schvezov: sergio.schvezov@cannical.com
584+ * Manuel de la Pena: manuel.delapena@canonical.com
585+ *
586+ * ciborium is free software; you can redistribute it and/or modify
587+ * it under the terms of the GNU General Public License as published by
588+ * the Free Software Foundation; version 3.
589+ *
590+ * ciborium is distributed in the hope that it will be useful,
591+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
592+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
593+ * GNU General Public License for more details.
594+ *
595+ * You should have received a copy of the GNU General Public License
596+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
597+ */
598+
599+package udisks2
600+
601+import (
602+ "reflect"
603+
604+ "launchpad.net/go-dbus/v1"
605+)
606+
607+const (
608+ formatErase = "format-erase"
609+ formateMkfs = "format-mkfs"
610+ mountPointsProperty = "MountPoints"
611+ uuidProperty = "UUID"
612+ tableProperty = "Table"
613+ partitionableProperty = "HintPartitionable"
614+ operationProperty = "Operation"
615+ objectsProperty = "Objects"
616+)
617+
618+type VariantMap map[string]dbus.Variant
619+type InterfacesAndProperties map[string]VariantMap
620+
621+func (i InterfacesAndProperties) isMounted() bool {
622+ propFS, ok := i[dbusFilesystemInterface]
623+ if !ok {
624+ return false
625+ }
626+ mountpointsVariant, ok := propFS[mountPointsProperty]
627+ if !ok {
628+ return false
629+ }
630+ if reflect.TypeOf(mountpointsVariant.Value).Kind() != reflect.Slice {
631+ return false
632+ }
633+ mountpoints := reflect.ValueOf(mountpointsVariant.Value).Len()
634+
635+ return mountpoints > 0
636+}
637+
638+func (i InterfacesAndProperties) hasPartition() bool {
639+ prop, ok := i[dbusPartitionInterface]
640+ if !ok {
641+ return false
642+ }
643+ // check if a couple of properties exist
644+ if _, ok := prop[uuidProperty]; !ok {
645+ return false
646+ }
647+ if _, ok := prop[tableProperty]; !ok {
648+ return false
649+ }
650+ return true
651+}
652+
653+func (i InterfacesAndProperties) isPartitionable() bool {
654+ prop, ok := i[dbusBlockInterface]
655+ if !ok {
656+ return false
657+ }
658+ partitionableHintVariant, ok := prop[partitionableProperty]
659+ if !ok {
660+ return false
661+ }
662+ if reflect.TypeOf(partitionableHintVariant.Value).Kind() != reflect.Bool {
663+ return false
664+ }
665+ return reflect.ValueOf(partitionableHintVariant.Value).Bool()
666+}
667+
668+func (i InterfacesAndProperties) jobOperation() string {
669+ prop, ok := i[dbusJobInterface]
670+ if !ok {
671+ return ""
672+ }
673+ operationVariant, ok := prop[operationProperty]
674+ if !ok {
675+ return ""
676+ }
677+ if reflect.TypeOf(operationVariant.Value).Kind() != reflect.String {
678+ return ""
679+ }
680+ return reflect.ValueOf(operationVariant.Value).String()
681+}
682+
683+func (i InterfacesAndProperties) isEraseFormatJob() bool {
684+ return i.jobOperation() == formatErase
685+
686+}
687+
688+func (i InterfacesAndProperties) isMkfsFormatJob() bool {
689+ return i.jobOperation() == formateMkfs
690+}
691+
692+func (i InterfacesAndProperties) getFormattedPaths() []string {
693+ var objectPaths []string
694+ prop, ok := i[dbusJobInterface]
695+ if !ok {
696+ return objectPaths
697+ }
698+ operationVariant, ok := prop[operationProperty]
699+ if !ok {
700+ return objectPaths
701+ }
702+
703+ operationStr := reflect.ValueOf(operationVariant.Value).String()
704+ if operationStr == formateMkfs {
705+ objs, ok := prop[objectsProperty]
706+ if ok {
707+ objsVal := reflect.ValueOf(objs.Value)
708+ length := objsVal.Len()
709+ objectPaths = make([]string, length, length)
710+ for i := 0; i < length; i++ {
711+ objectPaths[i] = objsVal.Index(i).Elem().String()
712+ }
713+ return objectPaths
714+ }
715+ }
716+
717+ return objectPaths
718+}
719+
720+func (i InterfacesAndProperties) isFilesystem() bool {
721+ _, ok := i[dbusFilesystemInterface]
722+ return ok
723+}
724
725=== added file 'udisks2/properties_test.go'
726--- udisks2/properties_test.go 1970-01-01 00:00:00 +0000
727+++ udisks2/properties_test.go 2015-02-11 15:31:38 +0000
728@@ -0,0 +1,210 @@
729+/*
730+ * Copyright 2015 Canonical Ltd.
731+ *
732+ * Authors:
733+ * Manuel de la Pena : manuel.delapena@cannical.com
734+ *
735+ * ciborium is free software; you can redistribute it and/or modify
736+ * it under the terms of the GNU General Public License as published by
737+ * the Free Software Foundation; version 3.
738+ *
739+ * ciborium is distributed in the hope that it will be useful,
740+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
741+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
742+ * GNU General Public License for more details.
743+ *
744+ * You should have received a copy of the GNU General Public License
745+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
746+ */
747+
748+package udisks2
749+
750+import (
751+ "sort"
752+
753+ "launchpad.net/go-dbus/v1"
754+ . "launchpad.net/gocheck"
755+)
756+
757+type InterfacesAndPropertiesTestSuite struct {
758+ properties InterfacesAndProperties
759+}
760+
761+var _ = Suite(&InterfacesAndPropertiesTestSuite{})
762+
763+func (s *InterfacesAndPropertiesTestSuite) SetUpTest(c *C) {
764+ s.properties = make(map[string]VariantMap)
765+}
766+
767+func (s *InterfacesAndPropertiesTestSuite) TestIsMountedMissingInterface(c *C) {
768+ // empty properties means that the interface is missing
769+ c.Assert(s.properties.isMounted(), Equals, false)
770+}
771+
772+func (s *InterfacesAndPropertiesTestSuite) TestIsMountedMissingMountPoints(c *C) {
773+ // add the expected interface but without the mount points property
774+ s.properties[dbusFilesystemInterface] = make(map[string]dbus.Variant)
775+ c.Assert(s.properties.isMounted(), Equals, false)
776+}
777+
778+func (s *InterfacesAndPropertiesTestSuite) TestIsMountedNotSlize(c *C) {
779+ s.properties[dbusFilesystemInterface] = make(map[string]dbus.Variant)
780+ s.properties[dbusFilesystemInterface]["MountPoints"] = dbus.Variant{5}
781+ c.Assert(s.properties.isMounted(), Equals, false)
782+}
783+
784+func (s *InterfacesAndPropertiesTestSuite) TestIsMountedZeroMountPoints(c *C) {
785+ mount_points := make([]string, 0, 0)
786+ s.properties[dbusFilesystemInterface] = make(map[string]dbus.Variant)
787+ s.properties[dbusFilesystemInterface]["MountPoints"] = dbus.Variant{mount_points}
788+ c.Assert(s.properties.isMounted(), Equals, false)
789+}
790+
791+func (s *InterfacesAndPropertiesTestSuite) TestIsMountedSeveralMountPoints(c *C) {
792+ mount_points := make([]string, 1, 1)
793+ mount_points[0] = "/random/mount/point"
794+ s.properties[dbusFilesystemInterface] = make(map[string]dbus.Variant)
795+ s.properties[dbusFilesystemInterface]["MountPoints"] = dbus.Variant{mount_points}
796+ c.Assert(s.properties.isMounted(), Equals, true)
797+}
798+
799+func (s *InterfacesAndPropertiesTestSuite) TestHasPartitionMissingInterface(c *C) {
800+ // an empty map should result in false
801+ c.Assert(s.properties.hasPartition(), Equals, false)
802+}
803+
804+func (s *InterfacesAndPropertiesTestSuite) TestHasPartitionMissinUUID(c *C) {
805+ // add the interface with no properties
806+ s.properties[dbusPartitionInterface] = make(map[string]dbus.Variant)
807+ c.Assert(s.properties.hasPartition(), Equals, false)
808+}
809+
810+func (s *InterfacesAndPropertiesTestSuite) TestHasParitionMissingTable(c *C) {
811+ s.properties[dbusPartitionInterface] = make(map[string]dbus.Variant)
812+ s.properties[dbusPartitionInterface]["UUID"] = dbus.Variant{"A UUID"}
813+ c.Assert(s.properties.hasPartition(), Equals, false)
814+}
815+
816+func (s *InterfacesAndPropertiesTestSuite) TestHasParitionPresent(c *C) {
817+ s.properties[dbusPartitionInterface] = make(map[string]dbus.Variant)
818+ s.properties[dbusPartitionInterface]["UUID"] = dbus.Variant{"A UUID"}
819+ s.properties[dbusPartitionInterface]["Table"] = dbus.Variant{"A Table"}
820+ c.Assert(s.properties.hasPartition(), Equals, true)
821+}
822+
823+func (s *InterfacesAndPropertiesTestSuite) TestIsPartitionableMissingInterface(c *C) {
824+ // an empty map should result in false
825+ c.Assert(s.properties.isPartitionable(), Equals, false)
826+}
827+
828+func (s *InterfacesAndPropertiesTestSuite) TestIsParitionableMissingHint(c *C) {
829+ s.properties[dbusBlockInterface] = make(map[string]dbus.Variant)
830+ c.Assert(s.properties.isPartitionable(), Equals, false)
831+}
832+
833+func (s *InterfacesAndPropertiesTestSuite) TestIsParitionableHintNotBool(c *C) {
834+ s.properties[dbusBlockInterface] = make(map[string]dbus.Variant)
835+ s.properties[dbusBlockInterface]["HintPartitionable"] = dbus.Variant{"A String"}
836+ c.Assert(s.properties.isPartitionable(), Equals, false)
837+}
838+
839+func (s *InterfacesAndPropertiesTestSuite) TestIsPartitionable(c *C) {
840+ s.properties[dbusBlockInterface] = make(map[string]dbus.Variant)
841+ s.properties[dbusBlockInterface]["HintPartitionable"] = dbus.Variant{true}
842+ c.Assert(s.properties.isPartitionable(), Equals, true)
843+}
844+
845+func (s *InterfacesAndPropertiesTestSuite) TestIsNotPartitionable(c *C) {
846+ s.properties[dbusBlockInterface] = make(map[string]dbus.Variant)
847+ s.properties[dbusBlockInterface]["HintPartitionable"] = dbus.Variant{false}
848+ c.Assert(s.properties.isPartitionable(), Equals, false)
849+}
850+
851+func (s *InterfacesAndPropertiesTestSuite) TestIsEraseFormatJobMissingInterface(c *C) {
852+ // an empty map should result in false
853+ c.Assert(s.properties.isEraseFormatJob(), Equals, false)
854+}
855+
856+func (s *InterfacesAndPropertiesTestSuite) TestIsEraseFormatJobMissingOperation(c *C) {
857+ s.properties[dbusJobInterface] = make(map[string]dbus.Variant)
858+ c.Assert(s.properties.isEraseFormatJob(), Equals, false)
859+}
860+
861+func (s *InterfacesAndPropertiesTestSuite) TestIsEraseFormatJobWrongType(c *C) {
862+ s.properties[dbusJobInterface] = make(map[string]dbus.Variant)
863+ s.properties[dbusJobInterface]["Operation"] = dbus.Variant{false}
864+ c.Assert(s.properties.isEraseFormatJob(), Equals, false)
865+}
866+
867+func (s *InterfacesAndPropertiesTestSuite) TestIsEraseFormatJobWrongOperation(c *C) {
868+ s.properties[dbusJobInterface] = make(map[string]dbus.Variant)
869+ s.properties[dbusJobInterface]["Operation"] = dbus.Variant{"false"}
870+ c.Assert(s.properties.isEraseFormatJob(), Equals, false)
871+}
872+
873+func (s *InterfacesAndPropertiesTestSuite) TestIsEraseFormatJob(c *C) {
874+ s.properties[dbusJobInterface] = make(map[string]dbus.Variant)
875+ s.properties[dbusJobInterface]["Operation"] = dbus.Variant{"format-erase"}
876+ c.Assert(s.properties.isEraseFormatJob(), Equals, true)
877+}
878+
879+func (s *InterfacesAndPropertiesTestSuite) TestIsMkfsFormatJobMissingInterface(c *C) {
880+ c.Assert(s.properties.isMkfsFormatJob(), Equals, false)
881+}
882+
883+func (s *InterfacesAndPropertiesTestSuite) TestIsMkfsFormatJobMissingOperation(c *C) {
884+ s.properties[dbusJobInterface] = make(map[string]dbus.Variant)
885+ c.Assert(s.properties.isMkfsFormatJob(), Equals, false)
886+}
887+
888+func (s *InterfacesAndPropertiesTestSuite) TestIsMkfsFormatJobWrongType(c *C) {
889+ s.properties[dbusJobInterface] = make(map[string]dbus.Variant)
890+ s.properties[dbusJobInterface]["Operation"] = dbus.Variant{true}
891+ c.Assert(s.properties.isMkfsFormatJob(), Equals, false)
892+}
893+
894+func (s *InterfacesAndPropertiesTestSuite) TestIsMkfsFormatJobWrongOperation(c *C) {
895+ s.properties[dbusJobInterface] = make(map[string]dbus.Variant)
896+ s.properties[dbusJobInterface]["Operation"] = dbus.Variant{"false"}
897+ c.Assert(s.properties.isMkfsFormatJob(), Equals, false)
898+}
899+
900+func (s *InterfacesAndPropertiesTestSuite) TestIsMkfsFormatJob(c *C) {
901+ s.properties[dbusJobInterface] = make(map[string]dbus.Variant)
902+ s.properties[dbusJobInterface]["Operation"] = dbus.Variant{"format-mkfs"}
903+ c.Assert(s.properties.isMkfsFormatJob(), Equals, true)
904+}
905+
906+func (s *InterfacesAndPropertiesTestSuite) TestGetFormattedPathsMissingInterface(c *C) {
907+ paths := s.properties.getFormattedPaths()
908+ c.Assert(len(paths), Equals, 0)
909+}
910+
911+func (s *InterfacesAndPropertiesTestSuite) TestGetFormattedPathsMissingProperty(c *C) {
912+ s.properties[dbusJobInterface] = make(map[string]dbus.Variant)
913+ paths := s.properties.getFormattedPaths()
914+ c.Assert(len(paths), Equals, 0)
915+}
916+
917+func (s *InterfacesAndPropertiesTestSuite) TestGetFormattedPaths(c *C) {
918+ firstPath := "/path/to/new/fs/1"
919+ secondPath := "/path/to/new/fs/2"
920+ thirdPath := "/path/to/new/fs/3"
921+
922+ objsPaths := make([]interface{}, 3, 3)
923+ objsPaths[0] = firstPath
924+ objsPaths[1] = secondPath
925+ objsPaths[2] = thirdPath
926+
927+ s.properties[dbusJobInterface] = make(map[string]dbus.Variant)
928+ s.properties[dbusJobInterface]["Operation"] = dbus.Variant{"format-mkfs"}
929+ s.properties[dbusJobInterface]["Objects"] = dbus.Variant{objsPaths}
930+
931+ paths := s.properties.getFormattedPaths()
932+ //sort.Strings(paths)
933+
934+ c.Assert(len(paths), Equals, len(objsPaths))
935+ c.Assert(sort.SearchStrings(paths, firstPath), Not(Equals), len(paths))
936+ c.Assert(sort.SearchStrings(paths, secondPath), Not(Equals), len(paths))
937+ c.Assert(sort.SearchStrings(paths, thirdPath), Not(Equals), len(paths))
938+}
939
940=== modified file 'udisks2/udisks2.go'
941--- udisks2/udisks2.go 2014-09-24 23:02:18 +0000
942+++ udisks2/udisks2.go 2015-02-11 15:31:38 +0000
943@@ -43,16 +43,13 @@
944 dbusFilesystemInterface = "org.freedesktop.UDisks2.Filesystem"
945 dbusPartitionInterface = "org.freedesktop.UDisks2.Partition"
946 dbusPartitionTableInterface = "org.freedesktop.UDisks2.PartitionTable"
947+ dbusJobInterface = "org.freedesktop.UDisks2.Job"
948 dbusAddedSignal = "InterfacesAdded"
949 dbusRemovedSignal = "InterfacesRemoved"
950 )
951
952 var ErrUnhandledFileSystem = errors.New("unhandled filesystem")
953
954-type VariantMap map[string]dbus.Variant
955-type InterfacesAndProperties map[string]VariantMap
956-type Interfaces []string
957-
958 type Drive struct {
959 path dbus.ObjectPath
960 blockDevices map[dbus.ObjectPath]InterfacesAndProperties
961@@ -61,34 +58,33 @@
962
963 type driveMap map[dbus.ObjectPath]*Drive
964
965-type Event struct {
966- Path dbus.ObjectPath
967- Props InterfacesAndProperties
968-}
969-
970 type mountpointMap map[dbus.ObjectPath]string
971
972 type UDisks2 struct {
973- conn *dbus.Connection
974- validFS sort.StringSlice
975- blockAdded chan *Event
976- driveAdded *dbus.SignalWatch
977- mountRemoved chan string
978- blockError chan error
979- driveRemoved *dbus.SignalWatch
980- blockDevice chan bool
981- drives driveMap
982- mountpoints mountpointMap
983- mapLock sync.Mutex
984- startLock sync.Mutex
985+ conn *dbus.Connection
986+ validFS sort.StringSlice
987+ blockAdded chan *Event
988+ driveAdded *dbus.SignalWatch
989+ mountRemoved chan string
990+ blockError chan error
991+ driveRemoved *dbus.SignalWatch
992+ blockDevice chan bool
993+ drives driveMap
994+ mountpoints mountpointMap
995+ mapLock sync.Mutex
996+ startLock sync.Mutex
997+ dispatcher *dispatcher
998+ jobs *jobManager
999+ pendingMounts []string
1000 }
1001
1002 func NewStorageWatcher(conn *dbus.Connection, filesystems ...string) (u *UDisks2) {
1003 u = &UDisks2{
1004- conn: conn,
1005- validFS: sort.StringSlice(filesystems),
1006- drives: make(driveMap),
1007- mountpoints: make(mountpointMap),
1008+ conn: conn,
1009+ validFS: sort.StringSlice(filesystems),
1010+ drives: make(driveMap),
1011+ mountpoints: make(mountpointMap),
1012+ pendingMounts: make([]string, 0, 0),
1013 }
1014 runtime.SetFinalizer(u, cleanDriveWatch)
1015 return u
1016@@ -212,45 +208,43 @@
1017 }
1018
1019 func (u *UDisks2) Init() (err error) {
1020- if u.driveAdded, err = u.connectToSignalInterfacesAdded(); err != nil {
1021- return err
1022- }
1023- if u.driveRemoved, err = u.connectToSignalInterfacesRemoved(); err != nil {
1024- return err
1025- }
1026- u.initInterfacesWatchChan()
1027- return nil
1028-}
1029-
1030-func (u *UDisks2) initInterfacesWatchChan() {
1031- go func() {
1032- for {
1033- select {
1034- case msg := <-u.driveAdded.C:
1035- var event Event
1036- if err := msg.Args(&event.Path, &event.Props); err != nil {
1037- log.Print(err)
1038- continue
1039- }
1040- if err := u.processAddEvent(&event); err != nil {
1041- log.Print("Issues while processing ", event.Path, ": ", err)
1042- }
1043- case msg := <-u.driveRemoved.C:
1044- var objectPath dbus.ObjectPath
1045- var interfaces Interfaces
1046- if err := msg.Args(&objectPath, &interfaces); err != nil {
1047- log.Print(err)
1048- continue
1049- }
1050- if err := u.processRemoveEvent(objectPath, interfaces); err != nil {
1051- log.Println("Issues while processing remove event:", err)
1052+ d, err := newDispatcher(u.conn)
1053+ if err == nil {
1054+ u.dispatcher = d
1055+ u.jobs = newJobManager(d)
1056+ go func() {
1057+ for {
1058+ select {
1059+ case e := <-u.dispatcher.Additions:
1060+ if err := u.processAddEvent(&e); err != nil {
1061+ log.Print("Issues while processing ", e.Path, ": ", err)
1062+ }
1063+ case e := <-u.dispatcher.Removals:
1064+ if err := u.processRemoveEvent(e.Path, e.Interfaces); err != nil {
1065+ log.Println("Issues while processing remove event:", err)
1066+ }
1067+ case j := <-u.jobs.FormatEraseJobs:
1068+ if j.WasCompleted {
1069+ log.Print("Erase job completed.")
1070+ } else {
1071+ log.Print("Erase job started.")
1072+ }
1073+ case j := <-u.jobs.FormatMkfsJobs:
1074+ if j.WasCompleted {
1075+ log.Println("Format job done for", j.Event.Path)
1076+ u.pendingMounts = append(u.pendingMounts, j.Paths...)
1077+ sort.Strings(u.pendingMounts)
1078+ } else {
1079+ log.Print("Format job started.")
1080+ }
1081 }
1082 }
1083- }
1084- log.Print("Shutting down InterfacesAdded channel")
1085- }()
1086-
1087- u.emitExistingDevices()
1088+ }()
1089+ d.Init()
1090+ u.emitExistingDevices()
1091+ return nil
1092+ }
1093+ return err
1094 }
1095
1096 func (u *UDisks2) connectToSignal(path dbus.ObjectPath, inter, member string) (*dbus.SignalWatch, error) {
1097@@ -288,7 +282,7 @@
1098 var blocks, drives []*Event
1099 // separate drives from blocks to avoid aliasing
1100 for objectPath, props := range allDevices {
1101- s := &Event{objectPath, props}
1102+ s := &Event{objectPath, props, make([]string, 0, 0)}
1103 switch objectPathType(objectPath) {
1104 case deviceTypeDrive:
1105 drives = append(drives, s)
1106@@ -313,6 +307,13 @@
1107 func (u *UDisks2) processAddEvent(s *Event) error {
1108 u.mapLock.Lock()
1109 defer u.mapLock.Unlock()
1110+ pos := sort.SearchStrings(u.pendingMounts, string(s.Path))
1111+ if pos != len(u.pendingMounts) && s.Props.isFilesystem() {
1112+ log.Println("Mount path", s.Path)
1113+ _, err := u.Mount(s)
1114+ u.pendingMounts = append(u.pendingMounts[:pos], u.pendingMounts[pos+1:]...)
1115+ return err
1116+ }
1117 if isBlockDevice, err := u.drives.addInterface(s); err != nil {
1118 return err
1119 } else if isBlockDevice {
1120@@ -524,50 +525,3 @@
1121
1122 return blockDevice, nil
1123 }
1124-
1125-func (i InterfacesAndProperties) isMounted() bool {
1126- propFS, ok := i[dbusFilesystemInterface]
1127- if !ok {
1128- return false
1129- }
1130- mountpointsVariant, ok := propFS["MountPoints"]
1131- if !ok {
1132- return false
1133- }
1134- if reflect.TypeOf(mountpointsVariant.Value).Kind() != reflect.Slice {
1135- return false
1136- }
1137- mountpoints := reflect.ValueOf(mountpointsVariant.Value).Len()
1138-
1139- return mountpoints > 0
1140-}
1141-
1142-func (i InterfacesAndProperties) hasPartition() bool {
1143- prop, ok := i[dbusPartitionInterface]
1144- if !ok {
1145- return false
1146- }
1147- // check if a couple of properties exist
1148- if _, ok := prop["UUID"]; !ok {
1149- return false
1150- }
1151- if _, ok := prop["Table"]; !ok {
1152- return false
1153- }
1154- return true
1155-}
1156-
1157-func (i InterfacesAndProperties) isPartitionable() bool {
1158- prop, ok := i[dbusBlockInterface]
1159- if !ok {
1160- return false
1161- }
1162- partitionableHintVariant, ok := prop["HintPartitionable"]
1163- if !ok {
1164- return false
1165- }
1166- if reflect.TypeOf(partitionableHintVariant.Value).Kind() != reflect.Bool {
1167- return false
1168- }
1169- return reflect.ValueOf(partitionableHintVariant.Value).Bool()
1170-}

Subscribers

People subscribed via source and target branches