1 /*
2  * Copyright 2012 <James.Bottomley@HansenPartnership.com>
3  *
4  * see COPYING file
5  */
6 
7 #include <efi.h>
8 #include <efilib.h>
9 
10 #include <console.h>
11 #include <simple_file.h>
12 #include <efiauthenticated.h>
13 #include <execute.h>		/* for generate_path() */
14 
15 static EFI_GUID IMAGE_PROTOCOL = LOADED_IMAGE_PROTOCOL;
16 static EFI_GUID SIMPLE_FS_PROTOCOL = SIMPLE_FILE_SYSTEM_PROTOCOL;
17 static EFI_GUID FILE_INFO = EFI_FILE_INFO_ID;
18 static EFI_GUID FS_INFO = EFI_FILE_SYSTEM_INFO_ID;
19 
20 EFI_STATUS
simple_file_open_by_handle(EFI_HANDLE device,CHAR16 * name,EFI_FILE ** file,UINT64 mode)21 simple_file_open_by_handle(EFI_HANDLE device, CHAR16 *name, EFI_FILE **file, UINT64 mode)
22 {
23 	EFI_STATUS efi_status;
24 	EFI_FILE_IO_INTERFACE *drive;
25 	EFI_FILE *root;
26 
27 	efi_status = uefi_call_wrapper(BS->HandleProtocol, 3, device,
28 				       &SIMPLE_FS_PROTOCOL, (void **)&drive);
29 
30 	if (efi_status != EFI_SUCCESS) {
31 		Print(L"Unable to find simple file protocol (%d)\n", efi_status);
32 		goto error;
33 	}
34 
35 	efi_status = uefi_call_wrapper(drive->OpenVolume, 2, drive, &root);
36 
37 	if (efi_status != EFI_SUCCESS) {
38 		Print(L"Failed to open drive volume (%d)\n", efi_status);
39 		goto error;
40 	}
41 
42 	efi_status = uefi_call_wrapper(root->Open, 5, root, file, name,
43 				       mode, 0);
44 
45  error:
46 	return efi_status;
47 }
48 
49 EFI_STATUS
simple_file_open(EFI_HANDLE image,CHAR16 * name,EFI_FILE ** file,UINT64 mode)50 simple_file_open(EFI_HANDLE image, CHAR16 *name, EFI_FILE **file, UINT64 mode)
51 {
52 	EFI_STATUS efi_status;
53 	EFI_HANDLE device;
54 	EFI_LOADED_IMAGE *li;
55 	EFI_DEVICE_PATH *loadpath = NULL;
56 	CHAR16 *PathName = NULL;
57 
58 	efi_status = uefi_call_wrapper(BS->HandleProtocol, 3, image,
59 				       &IMAGE_PROTOCOL, (void **)&li);
60 
61 	if (efi_status != EFI_SUCCESS)
62 		return simple_file_open_by_handle(image, name, file, mode);
63 
64 	efi_status = generate_path(name, li, &loadpath, &PathName);
65 
66 	if (efi_status != EFI_SUCCESS) {
67 		Print(L"Unable to generate load path for %s\n", name);
68 		return efi_status;
69 	}
70 
71 	device = li->DeviceHandle;
72 
73 	efi_status = simple_file_open_by_handle(device, PathName, file, mode);
74 
75 	FreePool(PathName);
76 	FreePool(loadpath);
77 
78 	return efi_status;
79 }
80 
81 EFI_STATUS
simple_dir_read_all_by_handle(EFI_HANDLE image,EFI_FILE * file,CHAR16 * name,EFI_FILE_INFO ** entries,int * count)82 simple_dir_read_all_by_handle(EFI_HANDLE image, EFI_FILE *file, CHAR16* name, EFI_FILE_INFO **entries,
83 		    int *count)
84 {
85 	EFI_STATUS status;
86 	char buf[4096];
87 	UINTN size = sizeof(buf);
88 	EFI_FILE_INFO *fi = (void *)buf;
89 
90 	status = uefi_call_wrapper(file->GetInfo, 4, file, &FILE_INFO,
91 				   &size, fi);
92 	if (status != EFI_SUCCESS) {
93 		Print(L"Failed to get file info\n");
94 		goto out;
95 	}
96 	if ((fi->Attribute & EFI_FILE_DIRECTORY) == 0) {
97 		Print(L"Not a directory %s\n", name);
98 		status = EFI_INVALID_PARAMETER;
99 		goto out;
100 	}
101 	size = 0;
102 	*count = 0;
103 	for (;;) {
104 		UINTN len = sizeof(buf);
105 		status = uefi_call_wrapper(file->Read, 3, file, &len, buf);
106 		if (status != EFI_SUCCESS || len == 0)
107 			break;
108 		(*count)++;
109 		size += len;
110 	}
111 	uefi_call_wrapper(file->SetPosition, 2, file, 0);
112 
113 	char *ptr = AllocatePool(size);
114 	*entries = (EFI_FILE_INFO *)ptr;
115 	if (!*entries)
116 		return EFI_OUT_OF_RESOURCES;
117 	int i;
118 	for (i = 0; i < *count; i++) {
119 		UINTN len = size;
120 		uefi_call_wrapper(file->Read, 3, file, &len, ptr);
121 		ptr += len;
122 		size -= len;
123 	}
124 	status = EFI_SUCCESS;
125  out:
126 	simple_file_close(file);
127 	if (status != EFI_SUCCESS && *entries) {
128 		FreePool(*entries);
129 		*entries = NULL;
130 	}
131 	return status;
132 }
133 
134 EFI_STATUS
simple_dir_read_all(EFI_HANDLE image,CHAR16 * name,EFI_FILE_INFO ** entries,int * count)135 simple_dir_read_all(EFI_HANDLE image, CHAR16 *name, EFI_FILE_INFO **entries,
136 		    int *count)
137 {
138 	EFI_FILE *file;
139 	EFI_STATUS status;
140 
141 	status = simple_file_open(image, name, &file, EFI_FILE_MODE_READ);
142 	if (status != EFI_SUCCESS) {
143 		Print(L"failed to open file %s: %d\n", name, status);
144 		return status;
145 	}
146 
147 	return simple_dir_read_all_by_handle(image, file, name, entries, count);
148 }
149 
150 EFI_STATUS
simple_file_read_all(EFI_FILE * file,UINTN * size,void ** buffer)151 simple_file_read_all(EFI_FILE *file, UINTN *size, void **buffer)
152 {
153 	EFI_STATUS efi_status;
154 	EFI_FILE_INFO *fi;
155 	char buf[1024];
156 
157 	*size = sizeof(buf);
158 	fi = (void *)buf;
159 
160 
161 	efi_status = uefi_call_wrapper(file->GetInfo, 4, file, &FILE_INFO,
162 				       size, fi);
163 	if (efi_status != EFI_SUCCESS) {
164 		Print(L"Failed to get file info\n");
165 		return efi_status;
166 	}
167 
168 	*size = fi->FileSize;
169 
170 	*buffer = AllocatePool(*size);
171 	if (!*buffer) {
172 		Print(L"Failed to allocate buffer of size %d\n", *size);
173 		return EFI_OUT_OF_RESOURCES;
174 	}
175 	efi_status = uefi_call_wrapper(file->Read, 3, file, size, *buffer);
176 
177 	return efi_status;
178 }
179 
180 
181 EFI_STATUS
simple_file_write_all(EFI_FILE * file,UINTN size,void * buffer)182 simple_file_write_all(EFI_FILE *file, UINTN size, void *buffer)
183 {
184 	EFI_STATUS efi_status;
185 
186 	efi_status = uefi_call_wrapper(file->Write, 3, file, &size, buffer);
187 
188 	return efi_status;
189 }
190 
191 void
simple_file_close(EFI_FILE * file)192 simple_file_close(EFI_FILE *file)
193 {
194 	uefi_call_wrapper(file->Close, 1, file);
195 }
196 
197 EFI_STATUS
simple_volume_selector(CHAR16 ** title,CHAR16 ** selected,EFI_HANDLE * h)198 simple_volume_selector(CHAR16 **title, CHAR16 **selected, EFI_HANDLE *h)
199 {
200 	UINTN count, i;
201 	EFI_HANDLE *vol_handles = NULL;
202 	EFI_STATUS status;
203 	CHAR16 **entries;
204 	int val;
205 
206 	uefi_call_wrapper(BS->LocateHandleBuffer, 5, ByProtocol,
207 			  &SIMPLE_FS_PROTOCOL, NULL, &count, &vol_handles);
208 
209 	if (!count || !vol_handles)
210 		return EFI_NOT_FOUND;
211 
212 	entries = AllocatePool(sizeof(CHAR16 *) * (count+1));
213 	if (!entries)
214 		return EFI_OUT_OF_RESOURCES;
215 
216 	for (i = 0; i < count; i++) {
217 		char buf[4096];
218 		UINTN size = sizeof(buf);
219 		EFI_FILE_SYSTEM_INFO *fi = (void *)buf;
220 		EFI_FILE *root;
221 		CHAR16 *name;
222 		EFI_FILE_IO_INTERFACE *drive;
223 
224 		status = uefi_call_wrapper(BS->HandleProtocol, 3,
225 					   vol_handles[i],
226 					   &SIMPLE_FS_PROTOCOL,
227 					   (void **)&drive);
228 		if (status != EFI_SUCCESS || !drive)
229 			continue;
230 
231 		status = uefi_call_wrapper(drive->OpenVolume, 2, drive, &root);
232 		if (status != EFI_SUCCESS)
233 			continue;
234 
235 		status = uefi_call_wrapper(root->GetInfo, 4, root, &FS_INFO,
236 					   &size, fi);
237 		if (status != EFI_SUCCESS)
238 			continue;
239 
240 		name = fi->VolumeLabel;
241 
242 		if (!name || StrLen(name) == 0 || StrCmp(name, L" ") == 0)
243 			name = DevicePathToStr(DevicePathFromHandle(vol_handles[i]));
244 
245 		entries[i] = AllocatePool((StrLen(name) + 2) * sizeof(CHAR16));
246 		if (!entries[i])
247 			break;
248 		StrCpy(entries[i], name);
249 	}
250 	entries[i] = NULL;
251 
252 	val = console_select(title, entries, 0);
253 
254 	if (val >= 0) {
255 		*selected = AllocatePool((StrLen(entries[val]) + 1) * sizeof(CHAR16));
256 		if (*selected) {
257 			StrCpy(*selected , entries[val]);
258 		}
259 		*h = vol_handles[val];
260 	} else {
261 		*selected = NULL;
262 		*h = 0;
263 	}
264 
265 	for (i = 0; i < count; i++) {
266 		if (entries[i])
267 			FreePool(entries[i]);
268 	}
269 	FreePool(entries);
270 	FreePool(vol_handles);
271 
272 
273 	return EFI_SUCCESS;
274 }
275 
276 EFI_STATUS
simple_dir_filter(EFI_HANDLE image,CHAR16 * name,CHAR16 * filter,CHAR16 *** result,int * count,EFI_FILE_INFO ** entries)277 simple_dir_filter(EFI_HANDLE image, CHAR16 *name, CHAR16 *filter,
278 		  CHAR16 ***result, int *count, EFI_FILE_INFO **entries)
279 {
280 	EFI_STATUS status;
281 	int tot, offs = StrLen(filter), i, c, filtercount = 1;
282 	EFI_FILE_INFO *next;
283 	void *ptr;
284 	CHAR16 *newfilter = AllocatePool((StrLen(filter) + 1) * sizeof(CHAR16)),
285 		**filterarr;
286 
287 	if (!newfilter)
288 		return EFI_OUT_OF_RESOURCES;
289 
290 	/* just in case efi ever stops writeable strings */
291 	StrCpy(newfilter, filter);
292 
293 	for (i = 0; i < offs; i++) {
294 		if (filter[i] == '|')
295 			filtercount++;
296 	}
297 	filterarr = AllocatePool(filtercount * sizeof(void *));
298 	if (!filterarr)
299 		return EFI_OUT_OF_RESOURCES;
300 	c = 0;
301 	filterarr[c++] = newfilter;
302 	for (i = 0; i < offs; i++) {
303 		if (filter[i] == '|') {
304 			newfilter[i] = '\0';
305 			filterarr[c++] = &newfilter[i+1];
306 		}
307 	}
308 
309 	*count = 0;
310 
311 	status = simple_dir_read_all(image, name, entries, &tot);
312 
313 	if (status != EFI_SUCCESS)
314 		goto out;
315 	ptr = next = *entries;
316 
317 	for (i = 0; i < tot; i++) {
318 		int len = StrLen(next->FileName);
319 
320 		for (c = 0; c < filtercount; c++) {
321 			offs = StrLen(filterarr[c]);
322 
323 			if (StrCmp(&next->FileName[len - offs], filterarr[c]) == 0
324 			    || (next->Attribute & EFI_FILE_DIRECTORY)) {
325 				(*count)++;
326 				break;
327 			}
328 		}
329 		ptr += OFFSET_OF(EFI_FILE_INFO, FileName) + (len + 1)*sizeof(CHAR16);
330 		next = ptr;
331 	}
332 	if (*count)
333 		*result = AllocatePool(((*count) + 1) * sizeof(void *));
334 	else
335 		*result = AllocatePool(2 * sizeof(void *));
336 
337 	*count = 0;
338 	ptr = next = *entries;
339 
340 	for (i = 0; i < tot; i++) {
341 		int len = StrLen(next->FileName);
342 
343 		if (StrCmp(next->FileName, L".") == 0)
344 			/* ignore . directory */
345 			goto next;
346 
347 		if (next->Attribute & EFI_FILE_DIRECTORY) {
348 				(*result)[(*count)] = PoolPrint(L"%s/", next->FileName);
349 				if (!(*result)[(*count)]) {
350 					Print(L"Failed to allocate buffer");
351 					return EFI_OUT_OF_RESOURCES;
352 				}
353 				(*count)++;
354 				goto next;
355 		}
356 
357 		for (c = 0; c < filtercount; c++) {
358 			offs = StrLen(filterarr[c]);
359 
360 			if (StrCmp(&next->FileName[len - offs], filterarr[c]) == 0) {
361 				(*result)[(*count)] = StrDuplicate(next->FileName);
362 				if (!(*result)[(*count)]) {
363 					Print(L"Failed to allocate buffer");
364 					return EFI_OUT_OF_RESOURCES;
365 				}
366 				(*count)++;
367 			} else {
368 				continue;
369 			}
370 			break;
371 		}
372 
373 	next:
374 		if (StrCmp(next->FileName, L"..") == 0) {
375 			/* place .. directory first */
376 			CHAR16 *tmp = (*result)[(*count) - 1];
377 
378 			(*result)[(*count) - 1] = (*result)[0];
379 			(*result)[0] = tmp;
380 		}
381 
382 		ptr += OFFSET_OF(EFI_FILE_INFO, FileName) + (len + 1)*sizeof(CHAR16);
383 		next = ptr;
384 	}
385 	if (*count == 0) {
386 		/* no entries at all ... can happen because top level dir has no . or .. */
387 		(*result)[(*count)++] = L"./";
388 	}
389 	(*result)[*count] = NULL;
390 	status = EFI_SUCCESS;
391 
392  out:
393 	if (status != EFI_SUCCESS) {
394 		if (*entries)
395 			FreePool(*entries);
396 		*entries = NULL;
397 		if (*result)
398 			FreePool(*result);
399 		*result = NULL;
400 	}
401 	return status;
402 }
403 
404 static void
free_entries(CHAR16 ** entries,int count)405 free_entries(CHAR16 **entries, int count)
406 {
407 	int i;
408 
409 	for (i = 0; i<count; i++)
410 		FreePool(entries[i]);
411 }
412 
413 void
simple_file_selector(EFI_HANDLE * im,CHAR16 ** title,CHAR16 * name,CHAR16 * filter,CHAR16 ** result)414 simple_file_selector(EFI_HANDLE *im, CHAR16 **title, CHAR16 *name,
415 		     CHAR16 *filter, CHAR16 **result)
416 {
417 	EFI_STATUS status;
418 	CHAR16 **entries = NULL;
419 	EFI_FILE_INFO *dmp;
420 	int count, select, len;
421 	CHAR16 *newname, *selected;
422 
423 	*result = NULL;
424 	if (!name)
425 		name = L"\\";
426 	if (!filter)
427 		filter = L"";
428 	if (!*im) {
429 		EFI_HANDLE h;
430 		CHAR16 *volname;
431 
432 		simple_volume_selector(title, &volname, &h);
433 		if (!volname)
434 			return;
435 		FreePool(volname);
436 		*im = h;
437 	}
438 
439 	newname = AllocatePool((StrLen(name) + 1)*sizeof(CHAR16));
440 	if (!newname)
441 		return;
442 
443 	StrCpy(newname, name);
444 	name = newname;
445 
446  redo:
447 	status = simple_dir_filter(*im, name, filter, &entries, &count, &dmp);
448 
449 	if (status != EFI_SUCCESS)
450 		goto out_free_name;
451 
452 	select = console_select(title, entries, 0);
453 	if (select < 0)
454 		/* ESC key */
455 		goto out_free;
456 	selected = entries[select];
457 	/* note that memory used by selected is valid until dmp is freed */
458 	len = StrLen(selected);
459 	if (selected[len - 1] == '/') {
460 		CHAR16 *newname;
461 
462 		/* stay where we are */
463 		if (StrCmp(selected, L"./") == 0) {
464 			free_entries(entries, count);
465 			FreePool(entries);
466 			entries = NULL;
467 			FreePool(dmp);
468 			goto redo;
469 		} else if (StrCmp(selected, L"../") == 0) {
470 			int i;
471 
472 			i = StrLen(name) - 1;
473 
474 
475 			for (i = StrLen(name); i > 0; --i) {
476 				if (name[i] == '\\')
477 					break;
478 			}
479 			if (i == 0)
480 				i = 1;
481 
482 			if (StrCmp(name, L"\\") != 0
483 			    && StrCmp(&name[i], L"..") != 0) {
484 				name[i] = '\0';
485 				free_entries(entries, count);
486 				FreePool(entries);
487 				entries = NULL;
488 				FreePool(dmp);
489 				goto redo;
490 			}
491 		}
492 		newname = AllocatePool((StrLen(name) + len + 2)*sizeof(CHAR16));
493 		if (!newname)
494 			goto out_free;
495 		StrCpy(newname, name);
496 
497 		if (name[StrLen(name) - 1] != '\\')
498 			StrCat(newname, L"\\");
499 		StrCat(newname, selected);
500 		/* remove trailing / */
501 		newname[StrLen(newname) - 1] = '\0';
502 
503 		free_entries(entries, count);
504 		FreePool(entries);
505 		entries = NULL;
506 		FreePool(dmp);
507 		FreePool(name);
508 		name = newname;
509 
510 		goto redo;
511 	}
512 	*result = AllocatePool((StrLen(name) + len + 2)*sizeof(CHAR16));
513 	if (*result) {
514 		StrCpy(*result, name);
515 		if (name[StrLen(name) - 1] != '\\')
516 			StrCat(*result, L"\\");
517 		StrCat(*result, selected);
518 	}
519 
520  out_free:
521 	FreePool(dmp);
522 	if (entries) {
523 		free_entries(entries, count);
524 		FreePool(entries);
525 	}
526  out_free_name:
527 	FreePool(name);
528 }
529