1
1
import enum
2
2
import os
3
+ import pathlib
3
4
import time
4
5
import subprocess
5
6
6
7
import attr
7
8
8
9
from ..factory import target_factory
10
+ from ..resource .remote import RemoteUSBResource
9
11
from ..step import step
10
12
from ..util .managedfile import ManagedFile
11
13
from .common import Driver
12
14
from ..driver .exception import ExecutionError
13
15
14
16
from ..util .helper import processwrapper
17
+ from ..util .agentwrapper import AgentWrapper
15
18
from ..util import Timeout
16
19
17
20
@@ -40,12 +43,69 @@ class USBStorageDriver(Driver):
40
43
default = None ,
41
44
validator = attr .validators .optional (attr .validators .instance_of (str ))
42
45
)
46
+ WAIT_FOR_MEDIUM_TIMEOUT = 10.0 # s
47
+ WAIT_FOR_MEDIUM_SLEEP = 0.5 # s
48
+
49
+ def __attrs_post_init__ (self ):
50
+ super ().__attrs_post_init__ ()
51
+ self .wrapper = None
43
52
44
53
def on_activate (self ):
45
- pass
54
+ host = self .storage .host if isinstance (self .storage , RemoteUSBResource ) else None
55
+ self .wrapper = AgentWrapper (host )
56
+ self .proxy = self .wrapper .load ('udisks2' )
46
57
47
58
def on_deactivate (self ):
48
- pass
59
+ self .wrapper .close ()
60
+ self .wrapper = None
61
+ self .proxy = None
62
+
63
+ @Driver .check_active
64
+ @step (args = ['sources' , 'target' , 'partition' , 'target_is_directory' ])
65
+ def write_files (self , sources , target , partition , target_is_directory = True ):
66
+ """
67
+ Write the file(s) specified by filename(s) to the
68
+ bound USB storage partition.
69
+
70
+ Args:
71
+ sources (List[str]): path(s) to the file(s) to be copied to the bound USB storage
72
+ partition.
73
+ target (str): target directory or file to copy to
74
+ partition (int): mount the specified partition or None to mount the whole disk
75
+ target_is_directory (bool): Whether target is a directory
76
+ """
77
+
78
+ self .devpath = self ._get_devpath (partition )
79
+ mount_path = self .proxy .mount (self .devpath )
80
+
81
+ try :
82
+ # (pathlib.PurePath(...) / "/") == "/", so we turn absolute paths into relative
83
+ # paths with respect to the mount point here
84
+ target_rel = target .relative_to (target .root ) if target .root is not None else target
85
+ target_path = str (pathlib .PurePath (mount_path ) / target_rel )
86
+
87
+ copied_sources = []
88
+
89
+ for f in sources :
90
+ mf = ManagedFile (f , self .storage )
91
+ mf .sync_to_resource ()
92
+ copied_sources .append (mf .get_remote_path ())
93
+
94
+ if target_is_directory :
95
+ args = ["cp" , "-t" , target_path ] + copied_sources
96
+ else :
97
+ if len (sources ) != 1 :
98
+ raise ValueError ("single source argument required when target_is_directory=False" )
99
+
100
+ args = ["cp" , "-T" , copied_sources [0 ], target_path ]
101
+
102
+ processwrapper .check_output (self .storage .command_prefix + args )
103
+ self .proxy .unmount (self .devpath )
104
+ except :
105
+ # We are going to die with an exception anyway, so no point in waiting
106
+ # to make sure everything has been written before continuing
107
+ self .proxy .unmount (self .devpath , lazy = True )
108
+ raise
49
109
50
110
@Driver .check_active
51
111
@step (args = ['filename' ])
@@ -68,22 +128,10 @@ def write_image(self, filename=None, mode=Mode.DD, partition=None, skip=0, seek=
68
128
mf = ManagedFile (filename , self .storage )
69
129
mf .sync_to_resource ()
70
130
71
- # wait for medium
72
- timeout = Timeout (10.0 )
73
- while not timeout .expired :
74
- try :
75
- if self .get_size () > 0 :
76
- break
77
- time .sleep (0.5 )
78
- except ValueError :
79
- # when the medium gets ready the sysfs attribute is empty for a short time span
80
- continue
81
- else :
82
- raise ExecutionError ("Timeout while waiting for medium" )
131
+ self ._wait_for_medium (partition )
83
132
84
- partition = "" if partition is None else partition
133
+ target = self . _get_devpath ( partition )
85
134
remote_path = mf .get_remote_path ()
86
- target = f"{ self .storage .path } { partition } "
87
135
88
136
if mode == Mode .DD :
89
137
self .logger .info ('Writing %s to %s using dd.' , remote_path , target )
@@ -139,12 +187,41 @@ def write_image(self, filename=None, mode=Mode.DD, partition=None, skip=0, seek=
139
187
print_on_silent_log = True
140
188
)
141
189
190
+ def _get_devpath (self , partition ):
191
+ partition = "" if partition is None else partition
192
+ # simple concatenation is sufficient for USB mass storage
193
+ return f"{ self .storage .path } { partition } "
194
+
142
195
@Driver .check_active
143
- @step (result = True )
144
- def get_size (self ):
145
- args = ["cat" , f"/sys/class/block/{ self .storage .path [5 :]} /size" ]
196
+ def _wait_for_medium (self , partition ):
197
+ timeout = Timeout (self .WAIT_FOR_MEDIUM_TIMEOUT )
198
+ while not timeout .expired :
199
+ if self .get_size (partition ) > 0 :
200
+ break
201
+ time .sleep (self .WAIT_FOR_MEDIUM_SLEEP )
202
+ else :
203
+ raise ExecutionError ("Timeout while waiting for medium" )
204
+
205
+ @Driver .check_active
206
+ @step (args = ['partition' ], result = True )
207
+ def get_size (self , partition = None ):
208
+ """
209
+ Get the size of the bound USB storage root device or partition.
210
+
211
+ Args:
212
+ partition (int or None): optional, get size of the specified partition or None for
213
+ getting the size of the root device (defaults to None)
214
+
215
+ Returns:
216
+ int: size in bytes
217
+ """
218
+ args = ["cat" , f"/sys/class/block/{ self ._get_devpath (partition )[5 :]} /size" ]
146
219
size = subprocess .check_output (self .storage .command_prefix + args )
147
- return int (size )* 512
220
+ try :
221
+ return int (size ) * 512
222
+ except ValueError :
223
+ # when the medium gets ready the sysfs attribute is empty for a short time span
224
+ return 0
148
225
149
226
150
227
@target_factory .reg_driver
0 commit comments