aboutsummaryrefslogtreecommitdiffstats
path: root/gpxe/src/arch/i386/interface/pxe/pxe_tftp.c
blob: 715a0b61ea38a45f257fc727365320870e045be5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
/** @file
 *
 * PXE TFTP API
 *
 */

/*
 * Copyright (C) 2004 Michael Brown <mbrown@fensystems.co.uk>.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <byteswap.h>
#include <gpxe/uaccess.h>
#include <gpxe/in.h>
#include <gpxe/tftp.h>
#include <gpxe/xfer.h>
#include <gpxe/open.h>
#include <gpxe/process.h>
#include <pxe.h>

/** A PXE TFTP connection */
struct pxe_tftp_connection {
	/** Data transfer interface */
	struct xfer_interface xfer;
	/** Data buffer */
	userptr_t buffer;
	/** Size of data buffer */
	size_t size;
	/** Starting offset of data buffer */
	size_t start;
	/** File position */
	size_t offset;
	/** Maximum file position */
	size_t max_offset;
	/** Block size */
	size_t blksize;
	/** Block index */
	unsigned int blkidx;
	/** Overall return status code */
	int rc;
};

/** The PXE TFTP connection */
static struct pxe_tftp_connection pxe_tftp = {
	.xfer = XFER_INIT ( &null_xfer_ops ),
};

/**
 * Close PXE TFTP connection
 *
 * @v rc		Final status code
 */
static void pxe_tftp_close ( int rc ) {
	xfer_nullify ( &pxe_tftp.xfer );
	xfer_close ( &pxe_tftp.xfer, rc );
	pxe_tftp.rc = rc;
}

/**
 * Receive new data
 *
 * @v xfer		Data transfer interface
 * @v iobuf		I/O buffer
 * @v meta		Transfer metadata
 * @ret rc		Return status code
 */
static int pxe_tftp_xfer_deliver_iob ( struct xfer_interface *xfer __unused,
				       struct io_buffer *iobuf,
				       struct xfer_metadata *meta ) {
	size_t len = iob_len ( iobuf );
	int rc = 0;

	/* Calculate new buffer position */
	if ( meta->whence != SEEK_CUR )
		pxe_tftp.offset = 0;
	pxe_tftp.offset += meta->offset;

	/* Copy data block to buffer */
	if ( len == 0 ) {
		/* No data (pure seek); treat as success */
	} else if ( pxe_tftp.offset < pxe_tftp.start ) {
		DBG ( " buffer underrun at %zx (min %zx)",
		      pxe_tftp.offset, pxe_tftp.start );
		rc = -ENOBUFS;
	} else if ( ( pxe_tftp.offset + len ) >
		    ( pxe_tftp.start + pxe_tftp.size ) ) {
		DBG ( " buffer overrun at %zx (max %zx)",
		      ( pxe_tftp.offset + len ),
		      ( pxe_tftp.start + pxe_tftp.size ) );
		rc = -ENOBUFS;
	} else {
		copy_to_user ( pxe_tftp.buffer,
			       ( pxe_tftp.offset - pxe_tftp.start ),
			       iobuf->data, len );
	}

	/* Calculate new buffer position */
	pxe_tftp.offset += len;

	/* Record maximum offset as the file size */
	if ( pxe_tftp.max_offset < pxe_tftp.offset )
		pxe_tftp.max_offset = pxe_tftp.offset;

	/* Terminate transfer on error */
	if ( rc != 0 )
		pxe_tftp_close ( rc );

	free_iob ( iobuf );
	return rc;
}

/**
 * Handle close() event
 *
 * @v xfer		Data transfer interface
 * @v rc		Reason for close
 */
static void pxe_tftp_xfer_close ( struct xfer_interface *xfer __unused,
				  int rc ) {
	pxe_tftp_close ( rc );
}

static struct xfer_interface_operations pxe_tftp_xfer_ops = {
	.close		= pxe_tftp_xfer_close,
	.vredirect	= xfer_vopen,
	.window		= unlimited_xfer_window,
	.alloc_iob	= default_xfer_alloc_iob,
	.deliver_iob	= pxe_tftp_xfer_deliver_iob,
	.deliver_raw	= xfer_deliver_as_iob,
};

/**
 * Maximum length of a PXE TFTP URI
 *
 * The PXE TFTP API provides 128 characters for the filename; the
 * extra 128 bytes allow for the remainder of the URI.
 */
#define PXE_TFTP_URI_LEN 256

/**
 * Open PXE TFTP connection
 *
 * @v ipaddress		IP address
 * @v port		TFTP server port
 * @v filename		File name
 * @v blksize		Requested block size
 * @ret rc		Return status code
 */
static int pxe_tftp_open ( uint32_t ipaddress, unsigned int port,
			   const unsigned char *filename, size_t blksize ) {
	char uri_string[PXE_TFTP_URI_LEN];
	struct in_addr address;
	int rc;

	/* Intel bug-for-bug hack */
	pxe_set_cached_filename ( filename );

	/* Reset PXE TFTP connection structure */
	memset ( &pxe_tftp, 0, sizeof ( pxe_tftp ) );
	xfer_init ( &pxe_tftp.xfer, &pxe_tftp_xfer_ops, NULL );
	pxe_tftp.rc = -EINPROGRESS;

	/* Construct URI string */
	address.s_addr = ipaddress;
	if ( ! port )
		port = htons ( TFTP_PORT );
	if ( blksize < TFTP_DEFAULT_BLKSIZE )
		blksize = TFTP_DEFAULT_BLKSIZE;
	snprintf ( uri_string, sizeof ( uri_string ),
		   "tftp://%s:%d%s%s?blksize=%zd",
		   inet_ntoa ( address ), ntohs ( port ),
		   ( ( filename[0] == '/' ) ? "" : "/" ), filename, blksize );
	DBG ( " %s", uri_string );

	/* Open PXE TFTP connection */
	if ( ( rc = xfer_open_uri_string ( &pxe_tftp.xfer,
					   uri_string ) ) != 0 ) {
		DBG ( " could not open (%s)\n", strerror ( rc ) );
		return rc;
	}

	return 0;
}

/**
 * TFTP OPEN
 *
 * @v tftp_open				Pointer to a struct s_PXENV_TFTP_OPEN
 * @v s_PXENV_TFTP_OPEN::ServerIPAddress TFTP server IP address
 * @v s_PXENV_TFTP_OPEN::GatewayIPAddress Relay agent IP address, or 0.0.0.0
 * @v s_PXENV_TFTP_OPEN::FileName	Name of file to open
 * @v s_PXENV_TFTP_OPEN::TFTPPort	TFTP server UDP port
 * @v s_PXENV_TFTP_OPEN::PacketSize	TFTP blksize option to request
 * @ret #PXENV_EXIT_SUCCESS		File was opened
 * @ret #PXENV_EXIT_FAILURE		File was not opened
 * @ret s_PXENV_TFTP_OPEN::Status	PXE status code
 * @ret s_PXENV_TFTP_OPEN::PacketSize	Negotiated blksize
 * @err #PXENV_STATUS_TFTP_INVALID_PACKET_SIZE Requested blksize too small
 *
 * Opens a TFTP connection for downloading a file a block at a time
 * using pxenv_tftp_read().
 *
 * If s_PXENV_TFTP_OPEN::GatewayIPAddress is 0.0.0.0, normal IP
 * routing will take place.  See the relevant
 * @ref pxe_routing "implementation note" for more details.
 *
 * On x86, you must set the s_PXE::StatusCallout field to a nonzero
 * value before calling this function in protected mode.  You cannot
 * call this function with a 32-bit stack segment.  (See the relevant
 * @ref pxe_x86_pmode16 "implementation note" for more details.)
 * 
 * @note According to the PXE specification version 2.1, this call
 * "opens a file for reading/writing", though how writing is to be
 * achieved without the existence of an API call %pxenv_tftp_write()
 * is not made clear.
 *
 * @note Despite the existence of the numerous statements within the
 * PXE specification of the form "...if a TFTP/MTFTP or UDP connection
 * is active...", you cannot use pxenv_tftp_open() and
 * pxenv_tftp_read() to read a file via MTFTP; only via plain old
 * TFTP.  If you want to use MTFTP, use pxenv_tftp_read_file()
 * instead.  Astute readers will note that, since
 * pxenv_tftp_read_file() is an atomic operation from the point of
 * view of the PXE API, it is conceptually impossible to issue any
 * other PXE API call "if an MTFTP connection is active".
 */
PXENV_EXIT_t pxenv_tftp_open ( struct s_PXENV_TFTP_OPEN *tftp_open ) {
	int rc;

	DBG ( "PXENV_TFTP_OPEN" );

	/* Guard against callers that fail to close before re-opening */
	pxe_tftp_close ( 0 );

	/* Open connection */
	if ( ( rc = pxe_tftp_open ( tftp_open->ServerIPAddress,
				    tftp_open->TFTPPort,
				    tftp_open->FileName,
				    tftp_open->PacketSize ) ) != 0 ) {
		tftp_open->Status = PXENV_STATUS ( rc );
		return PXENV_EXIT_FAILURE;
	}

	/* Wait for OACK to arrive so that we have the block size */
	while ( ( ( rc = pxe_tftp.rc ) == -EINPROGRESS ) &&
		( pxe_tftp.max_offset == 0 ) ) {
		step();
	}
	pxe_tftp.blksize = xfer_window ( &pxe_tftp.xfer );
	tftp_open->PacketSize = pxe_tftp.blksize;
	DBG ( " blksize=%d", tftp_open->PacketSize );

	/* EINPROGRESS is normal; we don't wait for the whole transfer */
	if ( rc == -EINPROGRESS )
		rc = 0;

	tftp_open->Status = PXENV_STATUS ( rc );
	return ( rc ? PXENV_EXIT_FAILURE : PXENV_EXIT_SUCCESS );
}

/**
 * TFTP CLOSE
 *
 * @v tftp_close			Pointer to a struct s_PXENV_TFTP_CLOSE
 * @ret #PXENV_EXIT_SUCCESS		File was closed successfully
 * @ret #PXENV_EXIT_FAILURE		File was not closed
 * @ret s_PXENV_TFTP_CLOSE::Status	PXE status code
 * @err None				-
 *
 * Close a connection previously opened with pxenv_tftp_open().  You
 * must have previously opened a connection with pxenv_tftp_open().
 *
 * On x86, you must set the s_PXE::StatusCallout field to a nonzero
 * value before calling this function in protected mode.  You cannot
 * call this function with a 32-bit stack segment.  (See the relevant
 * @ref pxe_x86_pmode16 "implementation note" for more details.)
 */
PXENV_EXIT_t pxenv_tftp_close ( struct s_PXENV_TFTP_CLOSE *tftp_close ) {
	DBG ( "PXENV_TFTP_CLOSE" );

	pxe_tftp_close ( 0 );
	tftp_close->Status = PXENV_STATUS_SUCCESS;
	return PXENV_EXIT_SUCCESS;
}

/**
 * TFTP READ
 *
 * @v tftp_read				Pointer to a struct s_PXENV_TFTP_READ
 * @v s_PXENV_TFTP_READ::Buffer		Address of data buffer
 * @ret #PXENV_EXIT_SUCCESS		Data was read successfully
 * @ret #PXENV_EXIT_FAILURE		Data was not read
 * @ret s_PXENV_TFTP_READ::Status	PXE status code
 * @ret s_PXENV_TFTP_READ::PacketNumber	TFTP packet number
 * @ret s_PXENV_TFTP_READ::BufferSize	Length of data written into buffer
 *
 * Reads a single packet from a connection previously opened with
 * pxenv_tftp_open() into the data buffer pointed to by
 * s_PXENV_TFTP_READ::Buffer.  You must have previously opened a
 * connection with pxenv_tftp_open().  The data written into
 * s_PXENV_TFTP_READ::Buffer is just the file data; the various
 * network headers have already been removed.
 *
 * The buffer must be large enough to contain a packet of the size
 * negotiated via the s_PXENV_TFTP_OPEN::PacketSize field in the
 * pxenv_tftp_open() call.  It is worth noting that the PXE
 * specification does @b not require the caller to fill in
 * s_PXENV_TFTP_READ::BufferSize before calling pxenv_tftp_read(), so
 * the PXE stack is free to ignore whatever value the caller might
 * place there and just assume that the buffer is large enough.  That
 * said, it may be worth the caller always filling in
 * s_PXENV_TFTP_READ::BufferSize to guard against PXE stacks that
 * mistake it for an input parameter.
 *
 * The length of the TFTP data packet will be returned via
 * s_PXENV_TFTP_READ::BufferSize.  If this length is less than the
 * blksize negotiated via s_PXENV_TFTP_OPEN::PacketSize in the call to
 * pxenv_tftp_open(), this indicates that the block is the last block
 * in the file.  Note that zero is a valid length for
 * s_PXENV_TFTP_READ::BufferSize, and will occur when the length of
 * the file is a multiple of the blksize.
 *
 * The PXE specification doesn't actually state that calls to
 * pxenv_tftp_read() will return the data packets in strict sequential
 * order, though most PXE stacks will probably do so.  The sequence
 * number of the packet will be returned in
 * s_PXENV_TFTP_READ::PacketNumber.  The first packet in the file has
 * a sequence number of one, not zero.
 *
 * To guard against flawed PXE stacks, the caller should probably set
 * s_PXENV_TFTP_READ::PacketNumber to one less than the expected
 * returned value (i.e. set it to zero for the first call to
 * pxenv_tftp_read() and then re-use the returned s_PXENV_TFTP_READ
 * parameter block for subsequent calls without modifying
 * s_PXENV_TFTP_READ::PacketNumber between calls).  The caller should
 * also guard against potential problems caused by flawed
 * implementations returning the occasional duplicate packet, by
 * checking that the value returned in s_PXENV_TFTP_READ::PacketNumber
 * is as expected (i.e. one greater than that returned from the
 * previous call to pxenv_tftp_read()).
 *
 * On x86, you must set the s_PXE::StatusCallout field to a nonzero
 * value before calling this function in protected mode.  You cannot
 * call this function with a 32-bit stack segment.  (See the relevant
 * @ref pxe_x86_pmode16 "implementation note" for more details.)
 */
PXENV_EXIT_t pxenv_tftp_read ( struct s_PXENV_TFTP_READ *tftp_read ) {
	int rc;

	DBG ( "PXENV_TFTP_READ to %04x:%04x",
	      tftp_read->Buffer.segment, tftp_read->Buffer.offset );

	/* Read single block into buffer */
	pxe_tftp.buffer = real_to_user ( tftp_read->Buffer.segment,
					 tftp_read->Buffer.offset );
	pxe_tftp.size = pxe_tftp.blksize;
	pxe_tftp.start = pxe_tftp.offset;
	while ( ( ( rc = pxe_tftp.rc ) == -EINPROGRESS ) &&
		( pxe_tftp.offset == pxe_tftp.start ) )
		step();
	pxe_tftp.buffer = UNULL;
	tftp_read->BufferSize = ( pxe_tftp.offset - pxe_tftp.start );
	tftp_read->PacketNumber = ++pxe_tftp.blkidx;

	/* EINPROGRESS is normal if we haven't reached EOF yet */
	if ( rc == -EINPROGRESS )
		rc = 0;

	tftp_read->Status = PXENV_STATUS ( rc );
	return ( rc ? PXENV_EXIT_FAILURE : PXENV_EXIT_SUCCESS );
}

/**
 * TFTP/MTFTP read file
 *
 * @v tftp_read_file		     Pointer to a struct s_PXENV_TFTP_READ_FILE
 * @v s_PXENV_TFTP_READ_FILE::FileName		File name
 * @v s_PXENV_TFTP_READ_FILE::BufferSize 	Size of the receive buffer
 * @v s_PXENV_TFTP_READ_FILE::Buffer		Address of the receive buffer
 * @v s_PXENV_TFTP_READ_FILE::ServerIPAddress	TFTP server IP address
 * @v s_PXENV_TFTP_READ_FILE::GatewayIPAddress	Relay agent IP address
 * @v s_PXENV_TFTP_READ_FILE::McastIPAddress	File's multicast IP address
 * @v s_PXENV_TFTP_READ_FILE::TFTPClntPort	Client multicast UDP port
 * @v s_PXENV_TFTP_READ_FILE::TFTPSrvPort	Server multicast UDP port
 * @v s_PXENV_TFTP_READ_FILE::TFTPOpenTimeOut	Time to wait for first packet
 * @v s_PXENV_TFTP_READ_FILE::TFTPReopenDelay	MTFTP inactivity timeout
 * @ret #PXENV_EXIT_SUCCESS			File downloaded successfully
 * @ret #PXENV_EXIT_FAILURE			File not downloaded
 * @ret s_PXENV_TFTP_READ_FILE::Status		PXE status code
 * @ret s_PXENV_TFTP_READ_FILE::BufferSize	Length of downloaded file
 *
 * Downloads an entire file via either TFTP or MTFTP into the buffer
 * pointed to by s_PXENV_TFTP_READ_FILE::Buffer.
 *
 * The PXE specification does not make it clear how the caller
 * requests that MTFTP be used rather than TFTP (or vice versa).  One
 * reasonable guess is that setting
 * s_PXENV_TFTP_READ_FILE::McastIPAddress to 0.0.0.0 would cause TFTP
 * to be used instead of MTFTP, though it is conceivable that some PXE
 * stacks would interpret that as "use the DHCP-provided multicast IP
 * address" instead.  Some PXE stacks will not implement MTFTP at all,
 * and will always use TFTP.
 *
 * It is not specified whether or not
 * s_PXENV_TFTP_READ_FILE::TFTPSrvPort will be used as the TFTP server
 * port for TFTP (rather than MTFTP) downloads.  Callers should assume
 * that the only way to access a TFTP server on a non-standard port is
 * to use pxenv_tftp_open() and pxenv_tftp_read().
 *
 * If s_PXENV_TFTP_READ_FILE::GatewayIPAddress is 0.0.0.0, normal IP
 * routing will take place.  See the relevant
 * @ref pxe_routing "implementation note" for more details.
 *
 * It is interesting to note that s_PXENV_TFTP_READ_FILE::Buffer is an
 * #ADDR32_t type, i.e. nominally a flat physical address.  Some PXE
 * NBPs (e.g. NTLDR) are known to call pxenv_tftp_read_file() in real
 * mode with s_PXENV_TFTP_READ_FILE::Buffer set to an address above
 * 1MB.  This means that PXE stacks must be prepared to write to areas
 * outside base memory.  Exactly how this is to be achieved is not
 * specified, though using INT 15,87 is as close to a standard method
 * as any, and should probably be used.  Switching to protected-mode
 * in order to access high memory will fail if pxenv_tftp_read_file()
 * is called in V86 mode; it is reasonably to expect that a V86
 * monitor would intercept the relatively well-defined INT 15,87 if it
 * wants the PXE stack to be able to write to high memory.
 *
 * Things get even more interesting if pxenv_tftp_read_file() is
 * called in protected mode, because there is then absolutely no way
 * for the PXE stack to write to an absolute physical address.  You
 * can't even get around the problem by creating a special "access
 * everything" segment in the s_PXE data structure, because the
 * #SEGDESC_t descriptors are limited to 64kB in size.
 *
 * Previous versions of the PXE specification (e.g. WfM 1.1a) provide
 * a separate API call, %pxenv_tftp_read_file_pmode(), specifically to
 * work around this problem.  The s_PXENV_TFTP_READ_FILE_PMODE
 * parameter block splits s_PXENV_TFTP_READ_FILE::Buffer into
 * s_PXENV_TFTP_READ_FILE_PMODE::BufferSelector and
 * s_PXENV_TFTP_READ_FILE_PMODE::BufferOffset, i.e. it provides a
 * protected-mode segment:offset address for the data buffer.  This
 * API call is no longer present in version 2.1 of the PXE
 * specification.
 *
 * Etherboot makes the assumption that s_PXENV_TFTP_READ_FILE::Buffer
 * is an offset relative to the caller's data segment, when
 * pxenv_tftp_read_file() is called in protected mode.
 *
 * On x86, you must set the s_PXE::StatusCallout field to a nonzero
 * value before calling this function in protected mode.  You cannot
 * call this function with a 32-bit stack segment.  (See the relevant
 * @ref pxe_x86_pmode16 "implementation note" for more details.)
 *
 * @note Microsoft's NTLDR assumes that the filename passed in via
 * s_PXENV_TFTP_READ_FILE::FileName will be stored in the "file" field
 * of the stored DHCPACK packet, whence it will be returned via any
 * subsequent calls to pxenv_get_cached_info().  Though this is
 * essentially a bug in the Intel PXE implementation (not, for once,
 * in the specification!), it is a bug that Microsoft relies upon, and
 * so we implement this bug-for-bug compatibility by overwriting the
 * filename stored DHCPACK packet with the filename passed in
 * s_PXENV_TFTP_READ_FILE::FileName.
 *
 */
PXENV_EXIT_t pxenv_tftp_read_file ( struct s_PXENV_TFTP_READ_FILE
				    *tftp_read_file ) {
	int rc;

	DBG ( "PXENV_TFTP_READ_FILE to %08x+%x", tftp_read_file->Buffer,
	      tftp_read_file->BufferSize );

	/* Open TFTP file */
	if ( ( rc = pxe_tftp_open ( tftp_read_file->ServerIPAddress, 0,
				    tftp_read_file->FileName, 0 ) ) != 0 ) {
		tftp_read_file->Status = PXENV_STATUS ( rc );
		return PXENV_EXIT_FAILURE;
	}

	/* Read entire file */
	pxe_tftp.buffer = phys_to_user ( tftp_read_file->Buffer );
	pxe_tftp.size = tftp_read_file->BufferSize;
	while ( ( rc = pxe_tftp.rc ) == -EINPROGRESS )
		step();
	pxe_tftp.buffer = UNULL;
	tftp_read_file->BufferSize = pxe_tftp.max_offset;

	/* Close TFTP file */
	pxe_tftp_close ( rc );

	tftp_read_file->Status = PXENV_STATUS ( rc );
	return ( rc ? PXENV_EXIT_FAILURE : PXENV_EXIT_SUCCESS );
}

/**
 * TFTP GET FILE SIZE
 *
 * @v tftp_get_fsize		     Pointer to a struct s_PXENV_TFTP_GET_FSIZE
 * @v s_PXENV_TFTP_GET_FSIZE::ServerIPAddress	TFTP server IP address
 * @v s_PXENV_TFTP_GET_FSIZE::GatewayIPAddress	Relay agent IP address
 * @v s_PXENV_TFTP_GET_FSIZE::FileName	File name
 * @ret #PXENV_EXIT_SUCCESS		File size was determined successfully
 * @ret #PXENV_EXIT_FAILURE		File size was not determined
 * @ret s_PXENV_TFTP_GET_FSIZE::Status	PXE status code
 * @ret s_PXENV_TFTP_GET_FSIZE::FileSize	File size
 *
 * Determine the size of a file on a TFTP server.  This uses the
 * "tsize" TFTP option, and so will not work with a TFTP server that
 * does not support TFTP options, or that does not support the "tsize"
 * option.
 *
 * The PXE specification states that this API call will @b not open a
 * TFTP connection for subsequent use with pxenv_tftp_read().  (This
 * is somewhat daft, since the only way to obtain the file size via
 * the "tsize" option involves issuing a TFTP open request, but that's
 * life.)
 *
 * You cannot call pxenv_tftp_get_fsize() while a TFTP or UDP
 * connection is open.
 *
 * If s_PXENV_TFTP_GET_FSIZE::GatewayIPAddress is 0.0.0.0, normal IP
 * routing will take place.  See the relevant
 * @ref pxe_routing "implementation note" for more details.
 *
 * On x86, you must set the s_PXE::StatusCallout field to a nonzero
 * value before calling this function in protected mode.  You cannot
 * call this function with a 32-bit stack segment.  (See the relevant
 * @ref pxe_x86_pmode16 "implementation note" for more details.)
 * 
 * @note There is no way to specify the TFTP server port with this API
 * call.  Though you can open a file using a non-standard TFTP server
 * port (via s_PXENV_TFTP_OPEN::TFTPPort or, potentially,
 * s_PXENV_TFTP_READ_FILE::TFTPSrvPort), you can only get the size of
 * a file from a TFTP server listening on the standard TFTP port.
 * "Consistency" is not a word in Intel's vocabulary.
 */
PXENV_EXIT_t pxenv_tftp_get_fsize ( struct s_PXENV_TFTP_GET_FSIZE
				    *tftp_get_fsize ) {
	int rc;

	DBG ( "PXENV_TFTP_GET_FSIZE" );

	/* Open TFTP file */
	if ( ( rc = pxe_tftp_open ( tftp_get_fsize->ServerIPAddress, 0,
				    tftp_get_fsize->FileName, 0 ) ) != 0 ) {
		tftp_get_fsize->Status = PXENV_STATUS ( rc );
		return PXENV_EXIT_FAILURE;
	}

	/* Wait for initial seek to arrive, and record size */
	while ( ( ( rc = pxe_tftp.rc ) == -EINPROGRESS ) &&
		( pxe_tftp.max_offset == 0 ) ) {
		step();
	}
	tftp_get_fsize->FileSize = pxe_tftp.max_offset;
	DBG ( " fsize=%d", tftp_get_fsize->FileSize );

	/* EINPROGRESS is normal; we don't wait for the whole transfer */
	if ( rc == -EINPROGRESS )
		rc = 0;

	/* Close TFTP file */
	pxe_tftp_close ( rc );

	tftp_get_fsize->Status = PXENV_STATUS ( rc );
	return ( rc ? PXENV_EXIT_FAILURE : PXENV_EXIT_SUCCESS );
}