10
10
11
11
using namespace bundle ;
12
12
13
- // Compute the final extraction location as:
14
- // m_extraction_dir = $DOTNET_BUNDLE_EXTRACT_BASE_DIR/<app>/<id>/...
15
- //
16
- // If DOTNET_BUNDLE_EXTRACT_BASE_DIR is not set in the environment, the
17
- // base directory defaults to $TMPDIR/.net
18
- void extractor_t::determine_extraction_dir ()
13
+ pal::string_t & extractor_t::extraction_dir ()
19
14
{
20
- if (! pal::getenv ( _X ( " DOTNET_BUNDLE_EXTRACT_BASE_DIR " ), & m_extraction_dir))
15
+ if (m_extraction_dir. empty ( ))
21
16
{
22
- if (!pal::get_default_bundle_extraction_base_dir (m_extraction_dir))
17
+ // Compute the final extraction location as:
18
+ // m_extraction_dir = $DOTNET_BUNDLE_EXTRACT_BASE_DIR/<app>/<id>/...
19
+ //
20
+ // If DOTNET_BUNDLE_EXTRACT_BASE_DIR is not set in the environment,
21
+ // a default is choosen within the temporary directory.
22
+
23
+ if (!pal::getenv (_X (" DOTNET_BUNDLE_EXTRACT_BASE_DIR" ), &m_extraction_dir))
23
24
{
24
- trace::error (_X (" Failure processing application bundle." ));
25
- trace::error (_X (" Failed to determine location for extracting embedded files." ));
26
- trace::error (_X (" DOTNET_BUNDLE_EXTRACT_BASE_DIR is not set, and a read-write temp-directory couldn't be created." ));
27
- throw StatusCode::BundleExtractionFailure;
25
+ if (!pal::get_default_bundle_extraction_base_dir (m_extraction_dir))
26
+ {
27
+ trace::error (_X (" Failure processing application bundle." ));
28
+ trace::error (_X (" Failed to determine location for extracting embedded files." ));
29
+ trace::error (_X (" DOTNET_BUNDLE_EXTRACT_BASE_DIR is not set, and a read-write temp-directory couldn't be created." ));
30
+ throw StatusCode::BundleExtractionFailure;
31
+ }
28
32
}
29
- }
30
33
31
- pal::string_t host_name = strip_executable_ext (get_filename (m_bundle_path));
32
- append_path (&m_extraction_dir, host_name.c_str ());
33
- append_path (&m_extraction_dir, m_bundle_id.c_str ());
34
+ pal::string_t host_name = strip_executable_ext (get_filename (m_bundle_path));
35
+ append_path (&m_extraction_dir, host_name.c_str ());
36
+ append_path (&m_extraction_dir, m_bundle_id.c_str ());
37
+
38
+ trace::info (_X (" Files embedded within the bundled will be extracted to [%s] directory." ), m_extraction_dir.c_str ());
39
+ }
34
40
35
- trace::info ( _X ( " Files embedded within the bundled will be extracted to [%s] directory. " ), m_extraction_dir. c_str ()) ;
41
+ return m_extraction_dir;
36
42
}
37
43
38
- // Compute the working extraction location for this process, before the
39
- // extracted files are committed to the final location
40
- // m_working_extraction_dir = $DOTNET_BUNDLE_EXTRACT_BASE_DIR/<app>/<proc-id-hex>
41
- void extractor_t::determine_working_extraction_dir ()
44
+ pal::string_t & extractor_t::working_extraction_dir ()
42
45
{
43
- m_working_extraction_dir = get_directory (extraction_dir ());
44
- pal::char_t pid[32 ];
45
- pal::snwprintf (pid, 32 , _X (" %x" ), pal::get_pid ());
46
- append_path (&m_working_extraction_dir, pid);
46
+ if (m_working_extraction_dir.empty ())
47
+ {
48
+ // Compute the working extraction location for this process,
49
+ // before the extracted files are committed to the final location
50
+ // working_extraction_dir = $DOTNET_BUNDLE_EXTRACT_BASE_DIR/<app>/<proc-id-hex>
47
51
48
- dir_utils_t::create_directory_tree (m_working_extraction_dir);
52
+ m_working_extraction_dir = get_directory (extraction_dir ());
53
+ pal::char_t pid[32 ];
54
+ pal::snwprintf (pid, 32 , _X (" %x" ), pal::get_pid ());
55
+ append_path (&m_working_extraction_dir, pid);
56
+
57
+ trace::info (_X (" Temporary directory used to extract bundled files is [%s]." ), m_working_extraction_dir.c_str ());
58
+ }
49
59
50
- trace::info ( _X ( " Temporary directory used to extract bundled files is [%s]. " ), m_working_extraction_dir. c_str ()) ;
60
+ return m_working_extraction_dir;
51
61
}
52
62
53
63
// Create a file to be extracted out on disk, including any intermediate sub-directories.
54
64
FILE* extractor_t::create_extraction_file (const pal::string_t & relative_path)
55
65
{
56
- pal::string_t file_path = m_working_extraction_dir ;
66
+ pal::string_t file_path = working_extraction_dir () ;
57
67
append_path (&file_path, relative_path.c_str ());
58
68
59
- // m_working_extraction_dir is assumed to exist,
69
+ // working_extraction_dir is assumed to exist,
60
70
// so we only create sub-directories if relative_path contains directories
61
71
if (dir_utils_t::has_dirs_in_path (relative_path))
62
72
{
@@ -92,29 +102,6 @@ void extractor_t::extract(const file_entry_t &entry, reader_t &reader)
92
102
fclose (file);
93
103
}
94
104
95
- pal::string_t & extractor_t::extraction_dir ()
96
- {
97
- if (m_extraction_dir.empty ())
98
- {
99
- determine_extraction_dir ();
100
- }
101
-
102
- return m_extraction_dir;
103
- }
104
-
105
- bool extractor_t::can_reuse_extraction ()
106
- {
107
- // In this version, the extracted files are assumed to be
108
- // correct by construction.
109
- //
110
- // Files embedded in the bundle are first extracted to m_working_extraction_dir
111
- // Once all files are successfully extracted, the extraction location is
112
- // committed (renamed) to m_extraction_dir. Therefore, the presence of
113
- // m_extraction_dir means that the files are pre-extracted.
114
-
115
- return pal::directory_exists (extraction_dir ());
116
- }
117
-
118
105
void extractor_t::begin ()
119
106
{
120
107
// Files are extracted to a specific deterministic location on disk
@@ -126,58 +113,138 @@ void extractor_t::begin()
126
113
//
127
114
// In order to solve these issues, we implement a extraction as a two-phase approach:
128
115
// 1) Files embedded in a bundle are extracted to a process-specific temporary
129
- // extraction location (m_working_extraction_dir )
130
- // 2) Upon successful extraction, m_working_extraction_dir is renamed to the actual
131
- // extraction location (m_extraction_dir )
116
+ // extraction location (working_extraction_dir )
117
+ // 2) Upon successful extraction, working_extraction_dir is renamed to the actual
118
+ // extraction location (extraction_dir )
132
119
//
133
120
// This effectively creates a file-lock to protect against races and failed extractions.
134
121
135
- determine_working_extraction_dir ();
122
+
123
+ dir_utils_t::create_directory_tree (working_extraction_dir ());
124
+ }
125
+
126
+ void extractor_t::clean ()
127
+ {
128
+ dir_utils_t::remove_directory_tree (working_extraction_dir ());
136
129
}
137
130
138
- void extractor_t::commit ()
131
+ void extractor_t::commit_dir ()
139
132
{
140
- // Commit files to the final extraction directory
133
+ // Commit an entire new extraction to the final extraction directory
141
134
// Retry the move operation with some wait in between the attempts. This is to workaround for possible file locking
142
135
// caused by AV software. Basically the extraction process above writes a bunch of executable files to disk
143
136
// and some AV software may decide to scan them on write. If this happens the files will be locked which blocks
144
137
// our ablity to move them.
145
- int retry_count = 500 ;
146
- while (true )
138
+
139
+ bool extracted_by_concurrent_process = false ;
140
+ bool extracted_by_current_process =
141
+ dir_utils_t::rename_with_retries (working_extraction_dir (), extraction_dir (), extracted_by_concurrent_process);
142
+
143
+ if (extracted_by_concurrent_process)
147
144
{
148
- if (pal::rename (m_working_extraction_dir.c_str (), m_extraction_dir.c_str ()) == 0 )
149
- break ;
145
+ // Another process successfully extracted the dependencies
146
+ trace::info (_X (" Extraction completed by another process, aborting current extraction." ));
147
+ clean ();
148
+ }
150
149
151
- bool should_retry = errno == EACCES;
152
- if (can_reuse_extraction ())
153
- {
154
- // Another process successfully extracted the dependencies
155
- trace::info (_X (" Extraction completed by another process, aborting current extraction." ));
150
+ if (!extracted_by_current_process && !extracted_by_concurrent_process)
151
+ {
152
+ trace::error (_X (" Failure processing application bundle." ));
153
+ trace::error (_X (" Failed to commit extracted files to directory [%s]." ), extraction_dir ().c_str ());
154
+ throw StatusCode::BundleExtractionFailure;
155
+ }
156
156
157
- dir_utils_t::remove_directory_tree (m_working_extraction_dir);
158
- break ;
159
- }
157
+ trace::info (_X (" Completed new extraction." ));
158
+ }
160
159
161
- if (should_retry && (retry_count--) > 0 )
162
- {
163
- trace::info (_X (" Retrying extraction due to EACCES trying to rename the extraction folder to [%s]." ), m_extraction_dir.c_str ());
164
- pal::sleep (100 );
165
- continue ;
166
- }
167
- else
168
- {
169
- trace::error (_X (" Failure processing application bundle." ));
170
- trace::error (_X (" Failed to commit extracted files to directory [%s]." ), m_extraction_dir.c_str ());
171
- throw StatusCode::BundleExtractionFailure;
172
- }
160
+ void extractor_t::commit_file (const pal::string_t & relative_path)
161
+ {
162
+ // Commit individual files to the final extraction directory.
163
+
164
+ pal::string_t working_file_path = working_extraction_dir ();
165
+ append_path (&working_file_path, relative_path.c_str ());
166
+
167
+ pal::string_t final_file_path = extraction_dir ();
168
+ append_path (&final_file_path, relative_path.c_str ());
169
+
170
+ if (dir_utils_t::has_dirs_in_path (relative_path))
171
+ {
172
+ dir_utils_t::create_directory_tree (get_directory (final_file_path));
173
173
}
174
+
175
+ bool extracted_by_concurrent_process = false ;
176
+ bool extracted_by_current_process =
177
+ dir_utils_t::rename_with_retries (working_file_path, final_file_path, extracted_by_concurrent_process);
178
+
179
+ if (extracted_by_concurrent_process)
180
+ {
181
+ // Another process successfully extracted the dependencies
182
+ trace::info (_X (" Extraction completed by another process, aborting current extraction." ));
183
+ }
184
+
185
+ if (!extracted_by_current_process && !extracted_by_concurrent_process)
186
+ {
187
+ trace::error (_X (" Failure processing application bundle." ));
188
+ trace::error (_X (" Failed to commit extracted files to directory [%s]." ), extraction_dir ().c_str ());
189
+ throw StatusCode::BundleExtractionFailure;
190
+ }
191
+
192
+ trace::info (_X (" Extraction recovered [%s]" ), relative_path.c_str ());
174
193
}
175
194
176
- void extractor_t::extract ( const manifest_t & manifest, reader_t & reader)
195
+ void extractor_t::extract_new ( reader_t & reader)
177
196
{
178
197
begin ();
179
- for (const file_entry_t & entry : manifest.files ) {
198
+ for (const file_entry_t & entry : m_manifest.files )
199
+ {
180
200
extract (entry, reader);
181
201
}
182
- commit ();
202
+ commit_dir ();
203
+ }
204
+
205
+ // Verify an existing extraction contains all files listed in the bundle manifest.
206
+ // If some files are missing, extract them individually.
207
+ void extractor_t::verify_recover_extraction (reader_t & reader)
208
+ {
209
+ pal::string_t & ext_dir = extraction_dir ();
210
+ bool recovered = false ;
211
+
212
+ for (const file_entry_t & entry : m_manifest.files )
213
+ {
214
+ pal::string_t file_path = ext_dir;
215
+ append_path (&file_path, entry.relative_path ().c_str ());
216
+
217
+ if (!pal::file_exists (file_path))
218
+ {
219
+ if (!recovered)
220
+ {
221
+ recovered = true ;
222
+ begin ();
223
+ }
224
+
225
+ extract (entry, reader);
226
+ commit_file (entry.relative_path ());
227
+ }
228
+ }
229
+
230
+ if (recovered)
231
+ {
232
+ clean ();
233
+ }
234
+ }
235
+
236
+ pal::string_t & extractor_t::extract (reader_t & reader)
237
+ {
238
+ if (pal::directory_exists (extraction_dir ()))
239
+ {
240
+ trace::info (_X (" Reusing existing extraction of application bundle." ));
241
+ verify_recover_extraction (reader);
242
+ }
243
+ else
244
+ {
245
+ trace::info (_X (" Starting new extraction of application bundle." ));
246
+ extract_new (reader);
247
+ }
248
+
249
+ return m_extraction_dir;
183
250
}
0 commit comments