This separates out the DeviceSet logic a bit better from the raw
device mapper operations.
devicemapper: Serialize addess to the devicemapper deviceset
This code is not safe to run in multiple threads at the same time,
and neither is libdevmapper.
DeviceMapper: Move deactivate into UnmountDevice
This way the deactivate is atomic wrt othe device mapper operations
and will not fail with EBUSY if someone else starts a devicemapper
operation inbetween unmount and deactivate.
devmapper: Fix loopback mounting regression
Some changes were added to attach_loop_device which added
a perror() in a place that caused it to override errno so that
a later errno != EBUSY failed. This fixes that and cleans up
the error reporting a bit.
devmapper: Build on old kernels without LOOP_CTL_GET_FREE define
... | ... |
@@ -6,7 +6,7 @@ type DeviceSet interface { |
6 | 6 |
DeactivateDevice(hash string) error |
7 | 7 |
RemoveDevice(hash string) error |
8 | 8 |
MountDevice(hash, path string) error |
9 |
- UnmountDevice(hash, path string) error |
|
9 |
+ UnmountDevice(hash, path string, deactivate bool) error |
|
10 | 10 |
HasDevice(hash string) bool |
11 | 11 |
HasInitializedDevice(hash string) bool |
12 | 12 |
HasActivatedDevice(hash string) bool |
... | ... |
@@ -13,6 +13,7 @@ import ( |
13 | 13 |
"strconv" |
14 | 14 |
"strings" |
15 | 15 |
"syscall" |
16 |
+ "sync" |
|
16 | 17 |
) |
17 | 18 |
|
18 | 19 |
const ( |
... | ... |
@@ -36,6 +37,7 @@ type MetaData struct { |
36 | 36 |
|
37 | 37 |
type DeviceSetDM struct { |
38 | 38 |
MetaData |
39 |
+ sync.Mutex |
|
39 | 40 |
initialized bool |
40 | 41 |
root string |
41 | 42 |
devicePrefix string |
... | ... |
@@ -77,74 +79,6 @@ func (devices *DeviceSetDM) getPoolDevName() string { |
77 | 77 |
return getDevName(devices.getPoolName()) |
78 | 78 |
} |
79 | 79 |
|
80 |
-func (devices *DeviceSetDM) createTask(t TaskType, name string) (*Task, error) { |
|
81 |
- task := TaskCreate(t) |
|
82 |
- if task == nil { |
|
83 |
- return nil, fmt.Errorf("Can't create task of type %d", int(t)) |
|
84 |
- } |
|
85 |
- if err := task.SetName(name); err != nil { |
|
86 |
- return nil, fmt.Errorf("Can't set task name %s", name) |
|
87 |
- } |
|
88 |
- return task, nil |
|
89 |
-} |
|
90 |
- |
|
91 |
-func (devices *DeviceSetDM) getInfo(name string) (*Info, error) { |
|
92 |
- task, err := devices.createTask(DeviceInfo, name) |
|
93 |
- if task == nil { |
|
94 |
- return nil, err |
|
95 |
- } |
|
96 |
- if err := task.Run(); err != nil { |
|
97 |
- return nil, err |
|
98 |
- } |
|
99 |
- return task.GetInfo() |
|
100 |
-} |
|
101 |
- |
|
102 |
-func (devices *DeviceSetDM) getStatus(name string) (uint64, uint64, string, string, error) { |
|
103 |
- task, err := devices.createTask(DeviceStatus, name) |
|
104 |
- if task == nil { |
|
105 |
- utils.Debugf("getStatus: Error createTask: %s", err) |
|
106 |
- return 0, 0, "", "", err |
|
107 |
- } |
|
108 |
- if err := task.Run(); err != nil { |
|
109 |
- utils.Debugf("getStatus: Error Run: %s", err) |
|
110 |
- return 0, 0, "", "", err |
|
111 |
- } |
|
112 |
- |
|
113 |
- devinfo, err := task.GetInfo() |
|
114 |
- if err != nil { |
|
115 |
- utils.Debugf("getStatus: Error GetInfo: %s", err) |
|
116 |
- return 0, 0, "", "", err |
|
117 |
- } |
|
118 |
- if devinfo.Exists == 0 { |
|
119 |
- utils.Debugf("getStatus: Non existing device %s", name) |
|
120 |
- return 0, 0, "", "", fmt.Errorf("Non existing device %s", name) |
|
121 |
- } |
|
122 |
- |
|
123 |
- _, start, length, target_type, params := task.GetNextTarget(0) |
|
124 |
- return start, length, target_type, params, nil |
|
125 |
-} |
|
126 |
- |
|
127 |
-func (devices *DeviceSetDM) setTransactionId(oldId uint64, newId uint64) error { |
|
128 |
- task, err := devices.createTask(DeviceTargetMsg, devices.getPoolDevName()) |
|
129 |
- if task == nil { |
|
130 |
- utils.Debugf("\n--->Err: %s\n", err) |
|
131 |
- return err |
|
132 |
- } |
|
133 |
- |
|
134 |
- if err := task.SetSector(0); err != nil { |
|
135 |
- return fmt.Errorf("Can't set sector") |
|
136 |
- } |
|
137 |
- |
|
138 |
- if err := task.SetMessage(fmt.Sprintf("set_transaction_id %d %d", oldId, newId)); err != nil { |
|
139 |
- return fmt.Errorf("Can't set message") |
|
140 |
- } |
|
141 |
- |
|
142 |
- if err := task.Run(); err != nil { |
|
143 |
- return fmt.Errorf("Error running setTransactionId") |
|
144 |
- } |
|
145 |
- return nil |
|
146 |
-} |
|
147 |
- |
|
148 | 80 |
func (devices *DeviceSetDM) hasImage(name string) bool { |
149 | 81 |
dirname := devices.loopbackDir() |
150 | 82 |
filename := path.Join(dirname, name) |
... | ... |
@@ -178,194 +112,6 @@ func (devices *DeviceSetDM) ensureImage(name string, size int64) (string, error) |
178 | 178 |
return filename, nil |
179 | 179 |
} |
180 | 180 |
|
181 |
-func (devices *DeviceSetDM) createPool(dataFile *os.File, metadataFile *os.File) error { |
|
182 |
- utils.Debugf("Activating device-mapper pool %s", devices.getPoolName()) |
|
183 |
- task, err := devices.createTask(DeviceCreate, devices.getPoolName()) |
|
184 |
- if task == nil { |
|
185 |
- utils.Debugf("\n--->Err: %s\n", err) |
|
186 |
- return err |
|
187 |
- } |
|
188 |
- |
|
189 |
- size, err := GetBlockDeviceSize(dataFile) |
|
190 |
- if err != nil { |
|
191 |
- return fmt.Errorf("Can't get data size") |
|
192 |
- } |
|
193 |
- |
|
194 |
- params := metadataFile.Name() + " " + dataFile.Name() + " 512 8192" |
|
195 |
- if err := task.AddTarget(0, size/512, "thin-pool", params); err != nil { |
|
196 |
- return fmt.Errorf("Can't add target") |
|
197 |
- } |
|
198 |
- |
|
199 |
- var cookie uint32 = 0 |
|
200 |
- if err := task.SetCookie(&cookie, 0); err != nil { |
|
201 |
- return fmt.Errorf("Can't set cookie") |
|
202 |
- } |
|
203 |
- |
|
204 |
- if err := task.Run(); err != nil { |
|
205 |
- return fmt.Errorf("Error running DeviceCreate") |
|
206 |
- } |
|
207 |
- |
|
208 |
- UdevWait(cookie) |
|
209 |
- |
|
210 |
- return nil |
|
211 |
-} |
|
212 |
- |
|
213 |
-func (devices *DeviceSetDM) suspendDevice(info *DevInfo) error { |
|
214 |
- task, err := devices.createTask(DeviceSuspend, info.Name()) |
|
215 |
- if task == nil { |
|
216 |
- utils.Debugf("\n--->Err: %s\n", err) |
|
217 |
- return err |
|
218 |
- } |
|
219 |
- if err := task.Run(); err != nil { |
|
220 |
- return fmt.Errorf("Error running DeviceSuspend") |
|
221 |
- } |
|
222 |
- return nil |
|
223 |
-} |
|
224 |
- |
|
225 |
-func (devices *DeviceSetDM) resumeDevice(info *DevInfo) error { |
|
226 |
- task, err := devices.createTask(DeviceResume, info.Name()) |
|
227 |
- if task == nil { |
|
228 |
- utils.Debugf("\n--->Err: %s\n", err) |
|
229 |
- return err |
|
230 |
- } |
|
231 |
- |
|
232 |
- var cookie uint32 = 0 |
|
233 |
- if err := task.SetCookie(&cookie, 0); err != nil { |
|
234 |
- return fmt.Errorf("Can't set cookie") |
|
235 |
- } |
|
236 |
- |
|
237 |
- if err := task.Run(); err != nil { |
|
238 |
- return fmt.Errorf("Error running DeviceSuspend") |
|
239 |
- } |
|
240 |
- |
|
241 |
- UdevWait(cookie) |
|
242 |
- |
|
243 |
- return nil |
|
244 |
-} |
|
245 |
- |
|
246 |
-func (devices *DeviceSetDM) createDevice(deviceId int) error { |
|
247 |
- task, err := devices.createTask(DeviceTargetMsg, devices.getPoolDevName()) |
|
248 |
- if task == nil { |
|
249 |
- utils.Debugf("\n--->Err: %s\n", err) |
|
250 |
- return err |
|
251 |
- } |
|
252 |
- |
|
253 |
- if err := task.SetSector(0); err != nil { |
|
254 |
- return fmt.Errorf("Can't set sector") |
|
255 |
- } |
|
256 |
- |
|
257 |
- if err := task.SetMessage(fmt.Sprintf("create_thin %d", deviceId)); err != nil { |
|
258 |
- return fmt.Errorf("Can't set message") |
|
259 |
- } |
|
260 |
- |
|
261 |
- if err := task.Run(); err != nil { |
|
262 |
- return fmt.Errorf("Error running createDevice") |
|
263 |
- } |
|
264 |
- return nil |
|
265 |
-} |
|
266 |
- |
|
267 |
-func (devices *DeviceSetDM) createSnapDevice(deviceId int, baseInfo *DevInfo) error { |
|
268 |
- devinfo, _ := devices.getInfo(baseInfo.Name()) |
|
269 |
- doSuspend := devinfo != nil && devinfo.Exists != 0 |
|
270 |
- |
|
271 |
- if doSuspend { |
|
272 |
- if err := devices.suspendDevice(baseInfo); err != nil { |
|
273 |
- utils.Debugf("\n--->Err: %s\n", err) |
|
274 |
- return err |
|
275 |
- } |
|
276 |
- } |
|
277 |
- |
|
278 |
- task, err := devices.createTask(DeviceTargetMsg, devices.getPoolDevName()) |
|
279 |
- if task == nil { |
|
280 |
- devices.resumeDevice(baseInfo) |
|
281 |
- utils.Debugf("\n--->Err: %s\n", err) |
|
282 |
- return err |
|
283 |
- } |
|
284 |
- |
|
285 |
- if err := task.SetSector(0); err != nil { |
|
286 |
- devices.resumeDevice(baseInfo) |
|
287 |
- return fmt.Errorf("Can't set sector") |
|
288 |
- } |
|
289 |
- |
|
290 |
- if err := task.SetMessage(fmt.Sprintf("create_snap %d %d", deviceId, baseInfo.DeviceId)); err != nil { |
|
291 |
- devices.resumeDevice(baseInfo) |
|
292 |
- return fmt.Errorf("Can't set message") |
|
293 |
- } |
|
294 |
- |
|
295 |
- if err := task.Run(); err != nil { |
|
296 |
- devices.resumeDevice(baseInfo) |
|
297 |
- return fmt.Errorf("Error running DeviceCreate") |
|
298 |
- } |
|
299 |
- |
|
300 |
- if doSuspend { |
|
301 |
- if err := devices.resumeDevice(baseInfo); err != nil { |
|
302 |
- utils.Debugf("\n--->Err: %s\n", err) |
|
303 |
- return err |
|
304 |
- } |
|
305 |
- } |
|
306 |
- |
|
307 |
- return nil |
|
308 |
-} |
|
309 |
- |
|
310 |
-func (devices *DeviceSetDM) deleteDevice(deviceId int) error { |
|
311 |
- task, err := devices.createTask(DeviceTargetMsg, devices.getPoolDevName()) |
|
312 |
- if task == nil { |
|
313 |
- utils.Debugf("\n--->Err: %s\n", err) |
|
314 |
- return err |
|
315 |
- } |
|
316 |
- |
|
317 |
- if err := task.SetSector(0); err != nil { |
|
318 |
- return fmt.Errorf("Can't set sector") |
|
319 |
- } |
|
320 |
- |
|
321 |
- if err := task.SetMessage(fmt.Sprintf("delete %d", deviceId)); err != nil { |
|
322 |
- return fmt.Errorf("Can't set message") |
|
323 |
- } |
|
324 |
- |
|
325 |
- if err := task.Run(); err != nil { |
|
326 |
- return fmt.Errorf("Error running deleteDevice") |
|
327 |
- } |
|
328 |
- return nil |
|
329 |
-} |
|
330 |
- |
|
331 |
-func (devices *DeviceSetDM) removeDevice(name string) error { |
|
332 |
- task, err := devices.createTask(DeviceRemove, name) |
|
333 |
- if task == nil { |
|
334 |
- utils.Debugf("\n--->Err: %s\n", err) |
|
335 |
- return err |
|
336 |
- } |
|
337 |
- if err = task.Run(); err != nil { |
|
338 |
- return fmt.Errorf("Error running removeDevice") |
|
339 |
- } |
|
340 |
- return nil |
|
341 |
-} |
|
342 |
- |
|
343 |
-func (devices *DeviceSetDM) activateDevice(info *DevInfo) error { |
|
344 |
- task, err := devices.createTask(DeviceCreate, info.Name()) |
|
345 |
- if task == nil { |
|
346 |
- utils.Debugf("\n--->Err: %s\n", err) |
|
347 |
- return err |
|
348 |
- } |
|
349 |
- |
|
350 |
- params := fmt.Sprintf("%s %d", devices.getPoolDevName(), info.DeviceId) |
|
351 |
- if err := task.AddTarget(0, info.Size/512, "thin", params); err != nil { |
|
352 |
- return fmt.Errorf("Can't add target") |
|
353 |
- } |
|
354 |
- |
|
355 |
- var cookie uint32 = 0 |
|
356 |
- if err := task.SetCookie(&cookie, 0); err != nil { |
|
357 |
- return fmt.Errorf("Can't set cookie") |
|
358 |
- } |
|
359 |
- |
|
360 |
- if err := task.Run(); err != nil { |
|
361 |
- return fmt.Errorf("Error running DeviceCreate") |
|
362 |
- } |
|
363 |
- |
|
364 |
- UdevWait(cookie) |
|
365 |
- |
|
366 |
- return nil |
|
367 |
-} |
|
368 |
- |
|
369 | 181 |
func (devices *DeviceSetDM) allocateDeviceId() int { |
370 | 182 |
// TODO: Add smarter reuse of deleted devices |
371 | 183 |
id := devices.nextFreeDevice |
... | ... |
@@ -412,7 +158,7 @@ func (devices *DeviceSetDM) saveMetadata() error { |
412 | 412 |
} |
413 | 413 |
|
414 | 414 |
if devices.NewTransactionId != devices.TransactionId { |
415 |
- if err = devices.setTransactionId(devices.TransactionId, devices.NewTransactionId); err != nil { |
|
415 |
+ if err = setTransactionId(devices.getPoolDevName(), devices.TransactionId, devices.NewTransactionId); err != nil { |
|
416 | 416 |
utils.Debugf("\n--->Err: %s\n", err) |
417 | 417 |
return err |
418 | 418 |
} |
... | ... |
@@ -448,11 +194,11 @@ func (devices *DeviceSetDM) activateDeviceIfNeeded(hash string) error { |
448 | 448 |
return fmt.Errorf("Unknown device %s", hash) |
449 | 449 |
} |
450 | 450 |
|
451 |
- if devinfo, _ := devices.getInfo(info.Name()); devinfo != nil && devinfo.Exists != 0 { |
|
451 |
+ if devinfo, _ := getInfo(info.Name()); devinfo != nil && devinfo.Exists != 0 { |
|
452 | 452 |
return nil |
453 | 453 |
} |
454 | 454 |
|
455 |
- return devices.activateDevice(info) |
|
455 |
+ return activateDevice(devices.getPoolDevName(), info.Name(), info.DeviceId, info.Size) |
|
456 | 456 |
} |
457 | 457 |
|
458 | 458 |
func (devices *DeviceSetDM) createFilesystem(info *DevInfo) error { |
... | ... |
@@ -470,7 +216,7 @@ func (devices *DeviceSetDM) createFilesystem(info *DevInfo) error { |
470 | 470 |
} |
471 | 471 |
|
472 | 472 |
func (devices *DeviceSetDM) loadMetaData() error { |
473 |
- _, _, _, params, err := devices.getStatus(devices.getPoolName()) |
|
473 |
+ _, _, _, params, err := getStatus(devices.getPoolName()) |
|
474 | 474 |
if err != nil { |
475 | 475 |
utils.Debugf("\n--->Err: %s\n", err) |
476 | 476 |
return err |
... | ... |
@@ -521,7 +267,7 @@ func (devices *DeviceSetDM) setupBaseImage() error { |
521 | 521 |
|
522 | 522 |
if oldInfo != nil && !oldInfo.Initialized { |
523 | 523 |
utils.Debugf("Removing uninitialized base image") |
524 |
- if err := devices.RemoveDevice(""); err != nil { |
|
524 |
+ if err := devices.removeDevice(""); err != nil { |
|
525 | 525 |
utils.Debugf("\n--->Err: %s\n", err) |
526 | 526 |
return err |
527 | 527 |
} |
... | ... |
@@ -532,14 +278,14 @@ func (devices *DeviceSetDM) setupBaseImage() error { |
532 | 532 |
id := devices.allocateDeviceId() |
533 | 533 |
|
534 | 534 |
// Create initial device |
535 |
- if err := devices.createDevice(id); err != nil { |
|
535 |
+ if err := createDevice(devices.getPoolDevName(), id); err != nil { |
|
536 | 536 |
utils.Debugf("\n--->Err: %s\n", err) |
537 | 537 |
return err |
538 | 538 |
} |
539 | 539 |
|
540 | 540 |
info, err := devices.registerDevice(id, "", defaultBaseFsSize) |
541 | 541 |
if err != nil { |
542 |
- _ = devices.deleteDevice(id) |
|
542 |
+ _ = deleteDevice(devices.getPoolDevName(), id) |
|
543 | 543 |
utils.Debugf("\n--->Err: %s\n", err) |
544 | 544 |
return err |
545 | 545 |
} |
... | ... |
@@ -582,7 +328,7 @@ func setCloseOnExec(name string) { |
582 | 582 |
} |
583 | 583 |
|
584 | 584 |
func (devices *DeviceSetDM) initDevmapper() error { |
585 |
- info, err := devices.getInfo(devices.getPoolName()) |
|
585 |
+ info, err := getInfo(devices.getPoolName()) |
|
586 | 586 |
if info == nil { |
587 | 587 |
utils.Debugf("Error device getInfo: %s", err) |
588 | 588 |
return err |
... | ... |
@@ -636,7 +382,7 @@ func (devices *DeviceSetDM) initDevmapper() error { |
636 | 636 |
} |
637 | 637 |
defer metadataFile.Close() |
638 | 638 |
|
639 |
- if err := devices.createPool(dataFile, metadataFile); err != nil { |
|
639 |
+ if err := createPool(devices.getPoolName(), dataFile, metadataFile); err != nil { |
|
640 | 640 |
utils.Debugf("\n--->Err: %s\n", err) |
641 | 641 |
return err |
642 | 642 |
} |
... | ... |
@@ -657,6 +403,9 @@ func (devices *DeviceSetDM) initDevmapper() error { |
657 | 657 |
} |
658 | 658 |
|
659 | 659 |
func (devices *DeviceSetDM) AddDevice(hash, baseHash string) error { |
660 |
+ devices.Lock() |
|
661 |
+ defer devices.Unlock() |
|
662 |
+ |
|
660 | 663 |
if err := devices.ensureInit(); err != nil { |
661 | 664 |
utils.Debugf("Error init: %s\n", err) |
662 | 665 |
return err |
... | ... |
@@ -674,33 +423,28 @@ func (devices *DeviceSetDM) AddDevice(hash, baseHash string) error { |
674 | 674 |
|
675 | 675 |
deviceId := devices.allocateDeviceId() |
676 | 676 |
|
677 |
- if err := devices.createSnapDevice(deviceId, baseInfo); err != nil { |
|
677 |
+ if err := devices.createSnapDevice(devices.getPoolDevName(), deviceId, baseInfo.Name(), baseInfo.DeviceId); err != nil { |
|
678 | 678 |
utils.Debugf("Error creating snap device: %s\n", err) |
679 | 679 |
return err |
680 | 680 |
} |
681 | 681 |
|
682 | 682 |
if _, err := devices.registerDevice(deviceId, hash, baseInfo.Size); err != nil { |
683 |
- devices.deleteDevice(deviceId) |
|
683 |
+ deleteDevice(devices.getPoolDevName(), deviceId) |
|
684 | 684 |
utils.Debugf("Error registering device: %s\n", err) |
685 | 685 |
return err |
686 | 686 |
} |
687 | 687 |
return nil |
688 | 688 |
} |
689 | 689 |
|
690 |
-func (devices *DeviceSetDM) RemoveDevice(hash string) error { |
|
691 |
- if err := devices.ensureInit(); err != nil { |
|
692 |
- utils.Debugf("\n--->Err: %s\n", err) |
|
693 |
- return err |
|
694 |
- } |
|
695 |
- |
|
690 |
+func (devices *DeviceSetDM) removeDevice(hash string) error { |
|
696 | 691 |
info := devices.Devices[hash] |
697 | 692 |
if info == nil { |
698 | 693 |
return fmt.Errorf("hash %s doesn't exists", hash) |
699 | 694 |
} |
700 | 695 |
|
701 |
- devinfo, _ := devices.getInfo(info.Name()) |
|
696 |
+ devinfo, _ := getInfo(info.Name()) |
|
702 | 697 |
if devinfo != nil && devinfo.Exists != 0 { |
703 |
- if err := devices.removeDevice(info.Name()); err != nil { |
|
698 |
+ if err := removeDevice(info.Name()); err != nil { |
|
704 | 699 |
utils.Debugf("Error removing device: %s\n", err) |
705 | 700 |
return err |
706 | 701 |
} |
... | ... |
@@ -714,7 +458,7 @@ func (devices *DeviceSetDM) RemoveDevice(hash string) error { |
714 | 714 |
} |
715 | 715 |
} |
716 | 716 |
|
717 |
- if err := devices.deleteDevice(info.DeviceId); err != nil { |
|
717 |
+ if err := deleteDevice(devices.getPoolDevName(), info.DeviceId); err != nil { |
|
718 | 718 |
utils.Debugf("Error deleting device: %s\n", err) |
719 | 719 |
return err |
720 | 720 |
} |
... | ... |
@@ -731,24 +475,32 @@ func (devices *DeviceSetDM) RemoveDevice(hash string) error { |
731 | 731 |
return nil |
732 | 732 |
} |
733 | 733 |
|
734 |
-func (devices *DeviceSetDM) DeactivateDevice(hash string) error { |
|
734 |
+func (devices *DeviceSetDM) RemoveDevice(hash string) error { |
|
735 |
+ devices.Lock() |
|
736 |
+ defer devices.Unlock() |
|
737 |
+ |
|
735 | 738 |
if err := devices.ensureInit(); err != nil { |
736 | 739 |
utils.Debugf("\n--->Err: %s\n", err) |
737 | 740 |
return err |
738 | 741 |
} |
739 | 742 |
|
743 |
+ |
|
744 |
+ return devices.removeDevice(hash) |
|
745 |
+} |
|
746 |
+ |
|
747 |
+func (devices *DeviceSetDM) deactivateDevice(hash string) error { |
|
740 | 748 |
info := devices.Devices[hash] |
741 | 749 |
if info == nil { |
742 | 750 |
return fmt.Errorf("hash %s doesn't exists", hash) |
743 | 751 |
} |
744 | 752 |
|
745 |
- devinfo, err := devices.getInfo(info.Name()) |
|
753 |
+ devinfo, err := getInfo(info.Name()) |
|
746 | 754 |
if err != nil { |
747 | 755 |
utils.Debugf("\n--->Err: %s\n", err) |
748 | 756 |
return err |
749 | 757 |
} |
750 | 758 |
if devinfo.Exists != 0 { |
751 |
- if err := devices.removeDevice(info.Name()); err != nil { |
|
759 |
+ if err := removeDevice(info.Name()); err != nil { |
|
752 | 760 |
utils.Debugf("\n--->Err: %s\n", err) |
753 | 761 |
return err |
754 | 762 |
} |
... | ... |
@@ -757,7 +509,24 @@ func (devices *DeviceSetDM) DeactivateDevice(hash string) error { |
757 | 757 |
return nil |
758 | 758 |
} |
759 | 759 |
|
760 |
+func (devices *DeviceSetDM) DeactivateDevice(hash string) error { |
|
761 |
+ devices.Lock() |
|
762 |
+ defer devices.Unlock() |
|
763 |
+ |
|
764 |
+ if err := devices.ensureInit(); err != nil { |
|
765 |
+ utils.Debugf("\n--->Err: %s\n", err) |
|
766 |
+ return err |
|
767 |
+ } |
|
768 |
+ |
|
769 |
+ utils.Debugf("DeactivateDevice %s", hash) |
|
770 |
+ return devices.deactivateDevice(hash); |
|
771 |
+} |
|
772 |
+ |
|
773 |
+ |
|
760 | 774 |
func (devices *DeviceSetDM) Shutdown() error { |
775 |
+ devices.Lock() |
|
776 |
+ defer devices.Unlock() |
|
777 |
+ |
|
761 | 778 |
if !devices.initialized { |
762 | 779 |
return nil |
763 | 780 |
} |
... | ... |
@@ -772,14 +541,14 @@ func (devices *DeviceSetDM) Shutdown() error { |
772 | 772 |
} |
773 | 773 |
|
774 | 774 |
for _, d := range devices.Devices { |
775 |
- if err := devices.DeactivateDevice(d.Hash); err != nil { |
|
775 |
+ if err := devices.deactivateDevice(d.Hash); err != nil { |
|
776 | 776 |
utils.Debugf("Shutdown deactivate %s , error: %s\n", d.Hash, err) |
777 | 777 |
} |
778 | 778 |
} |
779 | 779 |
|
780 | 780 |
pool := devices.getPoolDevName() |
781 |
- if devinfo, err := devices.getInfo(pool); err == nil && devinfo.Exists != 0 { |
|
782 |
- if err := devices.removeDevice(pool); err != nil { |
|
781 |
+ if devinfo, err := getInfo(pool); err == nil && devinfo.Exists != 0 { |
|
782 |
+ if err := removeDevice(pool); err != nil { |
|
783 | 783 |
utils.Debugf("Shutdown deactivate %s , error: %s\n", pool, err) |
784 | 784 |
} |
785 | 785 |
} |
... | ... |
@@ -788,6 +557,9 @@ func (devices *DeviceSetDM) Shutdown() error { |
788 | 788 |
} |
789 | 789 |
|
790 | 790 |
func (devices *DeviceSetDM) MountDevice(hash, path string) error { |
791 |
+ devices.Lock() |
|
792 |
+ defer devices.Unlock() |
|
793 |
+ |
|
791 | 794 |
if err := devices.ensureInit(); err != nil { |
792 | 795 |
utils.Debugf("\n--->Err: %s\n", err) |
793 | 796 |
return err |
... | ... |
@@ -815,7 +587,10 @@ func (devices *DeviceSetDM) MountDevice(hash, path string) error { |
815 | 815 |
return nil |
816 | 816 |
} |
817 | 817 |
|
818 |
-func (devices *DeviceSetDM) UnmountDevice(hash, path string) error { |
|
818 |
+func (devices *DeviceSetDM) UnmountDevice(hash, path string, deactivate bool) error { |
|
819 |
+ devices.Lock() |
|
820 |
+ defer devices.Unlock() |
|
821 |
+ |
|
819 | 822 |
if err := syscall.Unmount(path, 0); err != nil { |
820 | 823 |
utils.Debugf("\n--->Err: %s\n", err) |
821 | 824 |
return err |
... | ... |
@@ -827,10 +602,17 @@ func (devices *DeviceSetDM) UnmountDevice(hash, path string) error { |
827 | 827 |
delete(devices.activeMounts, path) |
828 | 828 |
} |
829 | 829 |
|
830 |
+ if deactivate { |
|
831 |
+ devices.deactivateDevice(hash) |
|
832 |
+ } |
|
833 |
+ |
|
830 | 834 |
return nil |
831 | 835 |
} |
832 | 836 |
|
833 | 837 |
func (devices *DeviceSetDM) HasDevice(hash string) bool { |
838 |
+ devices.Lock() |
|
839 |
+ defer devices.Unlock() |
|
840 |
+ |
|
834 | 841 |
if err := devices.ensureInit(); err != nil { |
835 | 842 |
return false |
836 | 843 |
} |
... | ... |
@@ -838,6 +620,9 @@ func (devices *DeviceSetDM) HasDevice(hash string) bool { |
838 | 838 |
} |
839 | 839 |
|
840 | 840 |
func (devices *DeviceSetDM) HasInitializedDevice(hash string) bool { |
841 |
+ devices.Lock() |
|
842 |
+ defer devices.Unlock() |
|
843 |
+ |
|
841 | 844 |
if err := devices.ensureInit(); err != nil { |
842 | 845 |
return false |
843 | 846 |
} |
... | ... |
@@ -847,6 +632,9 @@ func (devices *DeviceSetDM) HasInitializedDevice(hash string) bool { |
847 | 847 |
} |
848 | 848 |
|
849 | 849 |
func (devices *DeviceSetDM) HasActivatedDevice(hash string) bool { |
850 |
+ devices.Lock() |
|
851 |
+ defer devices.Unlock() |
|
852 |
+ |
|
850 | 853 |
if err := devices.ensureInit(); err != nil { |
851 | 854 |
return false |
852 | 855 |
} |
... | ... |
@@ -855,11 +643,14 @@ func (devices *DeviceSetDM) HasActivatedDevice(hash string) bool { |
855 | 855 |
if info == nil { |
856 | 856 |
return false |
857 | 857 |
} |
858 |
- devinfo, _ := devices.getInfo(info.Name()) |
|
858 |
+ devinfo, _ := getInfo(info.Name()) |
|
859 | 859 |
return devinfo != nil && devinfo.Exists != 0 |
860 | 860 |
} |
861 | 861 |
|
862 | 862 |
func (devices *DeviceSetDM) SetInitialized(hash string) error { |
863 |
+ devices.Lock() |
|
864 |
+ defer devices.Unlock() |
|
865 |
+ |
|
863 | 866 |
if err := devices.ensureInit(); err != nil { |
864 | 867 |
utils.Debugf("\n--->Err: %s\n", err) |
865 | 868 |
return err |
... | ... |
@@ -14,6 +14,10 @@ package devmapper |
14 | 14 |
#include <linux/fs.h> |
15 | 15 |
#include <errno.h> |
16 | 16 |
|
17 |
+#ifndef LOOP_CTL_GET_FREE |
|
18 |
+#define LOOP_CTL_GET_FREE 0x4C82 |
|
19 |
+#endif |
|
20 |
+ |
|
17 | 21 |
char* attach_loop_device(const char *filename, int *loop_fd_out) |
18 | 22 |
{ |
19 | 23 |
struct loop_info64 loopinfo = {0}; |
... | ... |
@@ -56,19 +60,18 @@ char* attach_loop_device(const char *filename, int *loop_fd_out) |
56 | 56 |
loop_fd = open(buf, O_RDWR); |
57 | 57 |
if (loop_fd < 0 && errno == ENOENT) { |
58 | 58 |
close(fd); |
59 |
- perror("open"); |
|
60 | 59 |
fprintf (stderr, "no available loopback device!"); |
61 | 60 |
return NULL; |
62 | 61 |
} else if (loop_fd < 0) |
63 | 62 |
continue; |
64 | 63 |
|
65 | 64 |
if (ioctl (loop_fd, LOOP_SET_FD, (void *)(size_t)fd) < 0) { |
66 |
- perror("ioctl"); |
|
65 |
+ int errsv = errno; |
|
67 | 66 |
close(loop_fd); |
68 | 67 |
loop_fd = -1; |
69 |
- if (errno != EBUSY) { |
|
68 |
+ if (errsv != EBUSY) { |
|
70 | 69 |
close (fd); |
71 |
- fprintf (stderr, "cannot set up loopback device %s", buf); |
|
70 |
+ fprintf (stderr, "cannot set up loopback device %s: %s", buf, strerror(errsv)); |
|
72 | 71 |
return NULL; |
73 | 72 |
} |
74 | 73 |
continue; |
... | ... |
@@ -388,3 +391,255 @@ func RemoveDevice(name string) error { |
388 | 388 |
func free(p *C.char) { |
389 | 389 |
C.free(unsafe.Pointer(p)) |
390 | 390 |
} |
391 |
+ |
|
392 |
+func createPool(poolName string, dataFile *os.File, metadataFile *os.File) error { |
|
393 |
+ task, err := createTask(DeviceCreate, poolName) |
|
394 |
+ if task == nil { |
|
395 |
+ return err |
|
396 |
+ } |
|
397 |
+ |
|
398 |
+ size, err := GetBlockDeviceSize(dataFile) |
|
399 |
+ if err != nil { |
|
400 |
+ return fmt.Errorf("Can't get data size") |
|
401 |
+ } |
|
402 |
+ |
|
403 |
+ params := metadataFile.Name() + " " + dataFile.Name() + " 512 8192" |
|
404 |
+ if err := task.AddTarget(0, size/512, "thin-pool", params); err != nil { |
|
405 |
+ return fmt.Errorf("Can't add target") |
|
406 |
+ } |
|
407 |
+ |
|
408 |
+ var cookie uint32 = 0 |
|
409 |
+ if err := task.SetCookie(&cookie, 0); err != nil { |
|
410 |
+ return fmt.Errorf("Can't set cookie") |
|
411 |
+ } |
|
412 |
+ |
|
413 |
+ if err := task.Run(); err != nil { |
|
414 |
+ return fmt.Errorf("Error running DeviceCreate") |
|
415 |
+ } |
|
416 |
+ |
|
417 |
+ UdevWait(cookie) |
|
418 |
+ |
|
419 |
+ return nil |
|
420 |
+} |
|
421 |
+ |
|
422 |
+func createTask(t TaskType, name string) (*Task, error) { |
|
423 |
+ task := TaskCreate(t) |
|
424 |
+ if task == nil { |
|
425 |
+ return nil, fmt.Errorf("Can't create task of type %d", int(t)) |
|
426 |
+ } |
|
427 |
+ if err := task.SetName(name); err != nil { |
|
428 |
+ return nil, fmt.Errorf("Can't set task name %s", name) |
|
429 |
+ } |
|
430 |
+ return task, nil |
|
431 |
+} |
|
432 |
+ |
|
433 |
+func getInfo(name string) (*Info, error) { |
|
434 |
+ task, err := createTask(DeviceInfo, name) |
|
435 |
+ if task == nil { |
|
436 |
+ return nil, err |
|
437 |
+ } |
|
438 |
+ if err := task.Run(); err != nil { |
|
439 |
+ return nil, err |
|
440 |
+ } |
|
441 |
+ return task.GetInfo() |
|
442 |
+} |
|
443 |
+ |
|
444 |
+func getStatus(name string) (uint64, uint64, string, string, error) { |
|
445 |
+ task, err := createTask(DeviceStatus, name) |
|
446 |
+ if task == nil { |
|
447 |
+ utils.Debugf("getStatus: Error createTask: %s", err) |
|
448 |
+ return 0, 0, "", "", err |
|
449 |
+ } |
|
450 |
+ if err := task.Run(); err != nil { |
|
451 |
+ utils.Debugf("getStatus: Error Run: %s", err) |
|
452 |
+ return 0, 0, "", "", err |
|
453 |
+ } |
|
454 |
+ |
|
455 |
+ devinfo, err := task.GetInfo() |
|
456 |
+ if err != nil { |
|
457 |
+ utils.Debugf("getStatus: Error GetInfo: %s", err) |
|
458 |
+ return 0, 0, "", "", err |
|
459 |
+ } |
|
460 |
+ if devinfo.Exists == 0 { |
|
461 |
+ utils.Debugf("getStatus: Non existing device %s", name) |
|
462 |
+ return 0, 0, "", "", fmt.Errorf("Non existing device %s", name) |
|
463 |
+ } |
|
464 |
+ |
|
465 |
+ _, start, length, target_type, params := task.GetNextTarget(0) |
|
466 |
+ return start, length, target_type, params, nil |
|
467 |
+} |
|
468 |
+ |
|
469 |
+func setTransactionId(poolName string, oldId uint64, newId uint64) error { |
|
470 |
+ task, err := createTask(DeviceTargetMsg, poolName) |
|
471 |
+ if task == nil { |
|
472 |
+ return err |
|
473 |
+ } |
|
474 |
+ |
|
475 |
+ if err := task.SetSector(0); err != nil { |
|
476 |
+ return fmt.Errorf("Can't set sector") |
|
477 |
+ } |
|
478 |
+ |
|
479 |
+ if err := task.SetMessage(fmt.Sprintf("set_transaction_id %d %d", oldId, newId)); err != nil { |
|
480 |
+ return fmt.Errorf("Can't set message") |
|
481 |
+ } |
|
482 |
+ |
|
483 |
+ if err := task.Run(); err != nil { |
|
484 |
+ return fmt.Errorf("Error running setTransactionId") |
|
485 |
+ } |
|
486 |
+ return nil |
|
487 |
+} |
|
488 |
+ |
|
489 |
+func suspendDevice(name string) error { |
|
490 |
+ task, err := createTask(DeviceSuspend, name) |
|
491 |
+ if task == nil { |
|
492 |
+ return err |
|
493 |
+ } |
|
494 |
+ if err := task.Run(); err != nil { |
|
495 |
+ return fmt.Errorf("Error running DeviceSuspend") |
|
496 |
+ } |
|
497 |
+ return nil |
|
498 |
+} |
|
499 |
+ |
|
500 |
+func resumeDevice(name string) error { |
|
501 |
+ task, err := createTask(DeviceResume, name) |
|
502 |
+ if task == nil { |
|
503 |
+ return err |
|
504 |
+ } |
|
505 |
+ |
|
506 |
+ var cookie uint32 = 0 |
|
507 |
+ if err := task.SetCookie(&cookie, 0); err != nil { |
|
508 |
+ return fmt.Errorf("Can't set cookie") |
|
509 |
+ } |
|
510 |
+ |
|
511 |
+ if err := task.Run(); err != nil { |
|
512 |
+ return fmt.Errorf("Error running DeviceSuspend") |
|
513 |
+ } |
|
514 |
+ |
|
515 |
+ UdevWait(cookie) |
|
516 |
+ |
|
517 |
+ return nil |
|
518 |
+} |
|
519 |
+ |
|
520 |
+func createDevice(poolName string, deviceId int) error { |
|
521 |
+ task, err := createTask(DeviceTargetMsg, poolName) |
|
522 |
+ if task == nil { |
|
523 |
+ return err |
|
524 |
+ } |
|
525 |
+ |
|
526 |
+ if err := task.SetSector(0); err != nil { |
|
527 |
+ return fmt.Errorf("Can't set sector") |
|
528 |
+ } |
|
529 |
+ |
|
530 |
+ if err := task.SetMessage(fmt.Sprintf("create_thin %d", deviceId)); err != nil { |
|
531 |
+ return fmt.Errorf("Can't set message") |
|
532 |
+ } |
|
533 |
+ |
|
534 |
+ if err := task.Run(); err != nil { |
|
535 |
+ return fmt.Errorf("Error running createDevice") |
|
536 |
+ } |
|
537 |
+ return nil |
|
538 |
+} |
|
539 |
+ |
|
540 |
+func deleteDevice(poolName string, deviceId int) error { |
|
541 |
+ task, err := createTask(DeviceTargetMsg, poolName) |
|
542 |
+ if task == nil { |
|
543 |
+ return err |
|
544 |
+ } |
|
545 |
+ |
|
546 |
+ if err := task.SetSector(0); err != nil { |
|
547 |
+ return fmt.Errorf("Can't set sector") |
|
548 |
+ } |
|
549 |
+ |
|
550 |
+ if err := task.SetMessage(fmt.Sprintf("delete %d", deviceId)); err != nil { |
|
551 |
+ return fmt.Errorf("Can't set message") |
|
552 |
+ } |
|
553 |
+ |
|
554 |
+ if err := task.Run(); err != nil { |
|
555 |
+ return fmt.Errorf("Error running deleteDevice") |
|
556 |
+ } |
|
557 |
+ return nil |
|
558 |
+} |
|
559 |
+ |
|
560 |
+func removeDevice(name string) error { |
|
561 |
+ task, err := createTask(DeviceRemove, name) |
|
562 |
+ if task == nil { |
|
563 |
+ return err |
|
564 |
+ } |
|
565 |
+ if err = task.Run(); err != nil { |
|
566 |
+ return fmt.Errorf("Error running removeDevice") |
|
567 |
+ } |
|
568 |
+ return nil |
|
569 |
+} |
|
570 |
+ |
|
571 |
+func activateDevice(poolName string, name string, deviceId int, size uint64) error { |
|
572 |
+ task, err := createTask(DeviceCreate, name) |
|
573 |
+ if task == nil { |
|
574 |
+ return err |
|
575 |
+ } |
|
576 |
+ |
|
577 |
+ params := fmt.Sprintf("%s %d", poolName, deviceId) |
|
578 |
+ if err := task.AddTarget(0, size/512, "thin", params); err != nil { |
|
579 |
+ return fmt.Errorf("Can't add target") |
|
580 |
+ } |
|
581 |
+ |
|
582 |
+ var cookie uint32 = 0 |
|
583 |
+ if err := task.SetCookie(&cookie, 0); err != nil { |
|
584 |
+ return fmt.Errorf("Can't set cookie") |
|
585 |
+ } |
|
586 |
+ |
|
587 |
+ if err := task.Run(); err != nil { |
|
588 |
+ return fmt.Errorf("Error running DeviceCreate") |
|
589 |
+ } |
|
590 |
+ |
|
591 |
+ UdevWait(cookie) |
|
592 |
+ |
|
593 |
+ return nil |
|
594 |
+} |
|
595 |
+ |
|
596 |
+func (devices *DeviceSetDM) createSnapDevice(poolName string, deviceId int, baseName string, baseDeviceId int) error { |
|
597 |
+ devinfo, _ := getInfo(baseName) |
|
598 |
+ doSuspend := devinfo != nil && devinfo.Exists != 0 |
|
599 |
+ |
|
600 |
+ if doSuspend { |
|
601 |
+ if err := suspendDevice(baseName); err != nil { |
|
602 |
+ return err |
|
603 |
+ } |
|
604 |
+ } |
|
605 |
+ |
|
606 |
+ task, err := createTask(DeviceTargetMsg, poolName) |
|
607 |
+ if task == nil { |
|
608 |
+ if doSuspend { |
|
609 |
+ resumeDevice(baseName) |
|
610 |
+ } |
|
611 |
+ return err |
|
612 |
+ } |
|
613 |
+ |
|
614 |
+ if err := task.SetSector(0); err != nil { |
|
615 |
+ if doSuspend { |
|
616 |
+ resumeDevice(baseName) |
|
617 |
+ } |
|
618 |
+ return fmt.Errorf("Can't set sector") |
|
619 |
+ } |
|
620 |
+ |
|
621 |
+ if err := task.SetMessage(fmt.Sprintf("create_snap %d %d", deviceId, baseDeviceId)); err != nil { |
|
622 |
+ if doSuspend { |
|
623 |
+ resumeDevice(baseName) |
|
624 |
+ } |
|
625 |
+ return fmt.Errorf("Can't set message") |
|
626 |
+ } |
|
627 |
+ |
|
628 |
+ if err := task.Run(); err != nil { |
|
629 |
+ if doSuspend { |
|
630 |
+ resumeDevice(baseName) |
|
631 |
+ } |
|
632 |
+ return fmt.Errorf("Error running DeviceCreate") |
|
633 |
+ } |
|
634 |
+ |
|
635 |
+ if doSuspend { |
|
636 |
+ if err := resumeDevice(baseName); err != nil { |
|
637 |
+ return err |
|
638 |
+ } |
|
639 |
+ } |
|
640 |
+ |
|
641 |
+ return nil |
|
642 |
+} |
... | ... |
@@ -391,14 +391,14 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { |
391 | 391 |
|
392 | 392 |
if err := ioutil.WriteFile(path.Join(mountDir, ".docker-id"), []byte(image.ID), 0600); err != nil { |
393 | 393 |
utils.Debugf("Error writing file: %s", err) |
394 |
- devices.UnmountDevice(image.ID, mountDir) |
|
394 |
+ devices.UnmountDevice(image.ID, mountDir, true) |
|
395 | 395 |
devices.RemoveDevice(image.ID) |
396 | 396 |
return err |
397 | 397 |
} |
398 | 398 |
|
399 | 399 |
if err = image.applyLayer(layerPath(root), mountDir); err != nil { |
400 | 400 |
utils.Debugf("Error applying layer: %s", err) |
401 |
- devices.UnmountDevice(image.ID, mountDir) |
|
401 |
+ devices.UnmountDevice(image.ID, mountDir, true) |
|
402 | 402 |
devices.RemoveDevice(image.ID) |
403 | 403 |
return err |
404 | 404 |
} |
... | ... |
@@ -411,28 +411,24 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { |
411 | 411 |
// part of the container changes |
412 | 412 |
dockerinitLayer, err := image.getDockerInitLayer() |
413 | 413 |
if err != nil { |
414 |
- devices.UnmountDevice(image.ID, mountDir) |
|
414 |
+ devices.UnmountDevice(image.ID, mountDir, true) |
|
415 | 415 |
devices.RemoveDevice(image.ID) |
416 | 416 |
return err |
417 | 417 |
} |
418 | 418 |
|
419 | 419 |
if err := image.applyLayer(dockerinitLayer, mountDir); err != nil { |
420 |
- devices.UnmountDevice(image.ID, mountDir) |
|
420 |
+ devices.UnmountDevice(image.ID, mountDir, true) |
|
421 | 421 |
devices.RemoveDevice(image.ID) |
422 | 422 |
return err |
423 | 423 |
} |
424 | 424 |
|
425 |
- if err := devices.UnmountDevice(image.ID, mountDir); err != nil { |
|
425 |
+ if err := devices.UnmountDevice(image.ID, mountDir, true); err != nil { |
|
426 | 426 |
devices.RemoveDevice(image.ID) |
427 | 427 |
return err |
428 | 428 |
} |
429 | 429 |
|
430 | 430 |
devices.SetInitialized(image.ID) |
431 | 431 |
|
432 |
- // No need to the device-mapper device to hang around once we've written |
|
433 |
- // the image, it can be enabled on-demand when needed |
|
434 |
- devices.DeactivateDevice(image.ID) |
|
435 |
- |
|
436 | 432 |
return nil |
437 | 433 |
} |
438 | 434 |
|
... | ... |
@@ -491,11 +487,11 @@ func (image *Image) Unmount(runtime *Runtime, root string, id string) error { |
491 | 491 |
return err |
492 | 492 |
} |
493 | 493 |
|
494 |
- if err = devices.UnmountDevice(id, root); err != nil { |
|
494 |
+ if err = devices.UnmountDevice(id, root, true); err != nil { |
|
495 | 495 |
return err |
496 | 496 |
} |
497 | 497 |
|
498 |
- return devices.DeactivateDevice(id) |
|
498 |
+ return nil |
|
499 | 499 |
} |
500 | 500 |
|
501 | 501 |
func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, error) { |
... | ... |
@@ -518,10 +514,7 @@ func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, er |
518 | 518 |
} |
519 | 519 |
|
520 | 520 |
changes, err := ChangesDirs(root, rw) |
521 |
- devices.UnmountDevice(image.ID, rw) |
|
522 |
- if !wasActivated { |
|
523 |
- devices.DeactivateDevice(image.ID) |
|
524 |
- } |
|
521 |
+ devices.UnmountDevice(image.ID, rw, !wasActivated) |
|
525 | 522 |
if err != nil { |
526 | 523 |
return nil, err |
527 | 524 |
} |
... | ... |
@@ -356,8 +356,8 @@ func (wrapper *DeviceSetWrapper) MountDevice(hash, path string) error { |
356 | 356 |
return wrapper.wrapped.MountDevice(wrapper.wrap(hash), path) |
357 | 357 |
} |
358 | 358 |
|
359 |
-func (wrapper *DeviceSetWrapper) UnmountDevice(hash, path string) error { |
|
360 |
- return wrapper.wrapped.UnmountDevice(wrapper.wrap(hash), path) |
|
359 |
+func (wrapper *DeviceSetWrapper) UnmountDevice(hash, path string, deactivate bool) error { |
|
360 |
+ return wrapper.wrapped.UnmountDevice(wrapper.wrap(hash), path, deactivate) |
|
361 | 361 |
} |
362 | 362 |
|
363 | 363 |
func (wrapper *DeviceSetWrapper) HasDevice(hash string) bool { |