1da6c28aamw/*
2da6c28aamw * CDDL HEADER START
3da6c28aamw *
4da6c28aamw * The contents of this file are subject to the terms of the
5da6c28aamw * Common Development and Distribution License (the "License").
6da6c28aamw * You may not use this file except in compliance with the License.
7da6c28aamw *
8da6c28aamw * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9da6c28aamw * or http://www.opensolaris.org/os/licensing.
10da6c28aamw * See the License for the specific language governing permissions
11da6c28aamw * and limitations under the License.
12da6c28aamw *
13da6c28aamw * When distributing Covered Code, include this CDDL HEADER in each
14da6c28aamw * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15da6c28aamw * If applicable, add the following below this CDDL HEADER, with the
16da6c28aamw * fields enclosed by brackets "[]" replaced with your own identifying
17da6c28aamw * information: Portions Copyright [yyyy] [name of copyright owner]
18da6c28aamw *
19da6c28aamw * CDDL HEADER END
20da6c28aamw */
21da6c28aamw/*
22148c5f4Alan Wright * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
230292c17Matt Barden * Copyright 2019 Nexenta by DDN, Inc. All rights reserved.
24da6c28aamw */
25da6c28aamw
26da6c28aamw/*
27da6c28aamw * Structures and type definitions for the SMB module.
28da6c28aamw */
29da6c28aamw
30faa1795jb#ifndef _SMBSRV_SMB_KTYPES_H
31faa1795jb#define	_SMBSRV_SMB_KTYPES_H
32da6c28aamw
33da6c28aamw#ifdef	__cplusplus
34da6c28aamwextern "C" {
35da6c28aamw#endif
36da6c28aamw
37faa1795jb#include <sys/note.h>
38da6c28aamw#include <sys/systm.h>
39da6c28aamw#include <sys/param.h>
40da6c28aamw#include <sys/types.h>
41da6c28aamw#include <sys/synch.h>
42da6c28aamw#include <sys/taskq.h>
433db3f65amw#include <sys/socket.h>
44da6c28aamw#include <sys/acl.h>
45da6c28aamw#include <sys/sdt.h>
46c8ec8eejose borrego#include <sys/stat.h>
47da6c28aamw#include <sys/vnode.h>
48da6c28aamw#include <sys/cred.h>
490f1702cYu Xiangning<Eric.Yu@Sun.COM>#include <netinet/in.h>
500f1702cYu Xiangning<Eric.Yu@Sun.COM>#include <sys/ksocket.h>
51faa1795jb#include <sys/fem.h>
52da6c28aamw#include <smbsrv/smb.h>
53a90cf9fGordon Ross#include <smbsrv/smb2.h>
54da6c28aamw#include <smbsrv/smbinfo.h>
55da6c28aamw#include <smbsrv/mbuf.h>
566537f38as#include <smbsrv/smb_sid.h>
573db3f65amw#include <smbsrv/smb_xdr.h>
5821b7895jb#include <smbsrv/netbios.h>
59da6c28aamw#include <smbsrv/smb_vops.h>
60148c5f4Alan Wright#include <smbsrv/smb_kstat.h>
61da6c28aamw
62b819ceaGordon Rossstruct __door_handle;	/* <sys/door.h> */
63b819ceaGordon Rossstruct edirent;		/* <sys/extdirent.h> */
648d94f65Gordon Rossstruct nvlist;
65b819ceaGordon Ross
662c2961fjose borregostruct smb_disp_entry;
67faa1795jbstruct smb_request;
68faa1795jbstruct smb_server;
699fb67eaafshin salek ardakani - Sun Microsystems - Irvine United Statesstruct smb_event;
708622ec4Gordon Rossstruct smb_export;
71da6c28aamw
72148c5f4Alan Wright/*
73148c5f4Alan Wright * Accumulated time and queue length statistics.
74148c5f4Alan Wright *
75148c5f4Alan Wright * Accumulated time statistics are kept as a running sum of "active" time.
76148c5f4Alan Wright * Queue length statistics are kept as a running sum of the product of queue
77148c5f4Alan Wright * length and elapsed time at that length -- i.e., a Riemann sum for queue
78148c5f4Alan Wright * length integrated against time.  (You can also think of the active time as a
79148c5f4Alan Wright * Riemann sum, for the boolean function (queue_length > 0) integrated against
80148c5f4Alan Wright * time, or you can think of it as the Lebesgue measure of the set on which
81148c5f4Alan Wright * queue_length > 0.)
82148c5f4Alan Wright *
83148c5f4Alan Wright *		^
84148c5f4Alan Wright *		|			_________
85148c5f4Alan Wright *		8			| i4	|
86148c5f4Alan Wright *		|			|	|
87148c5f4Alan Wright *	Queue	6			|	|
88148c5f4Alan Wright *	Length	|	_________	|	|
89148c5f4Alan Wright *		4	| i2	|_______|	|
90148c5f4Alan Wright *		|	|	    i3		|
91148c5f4Alan Wright *		2_______|			|
92148c5f4Alan Wright *		|    i1				|
93148c5f4Alan Wright *		|_______________________________|
94148c5f4Alan Wright *		Time->	t1	t2	t3	t4
95148c5f4Alan Wright *
96148c5f4Alan Wright * At each change of state (entry or exit from the queue), we add the elapsed
97148c5f4Alan Wright * time (since the previous state change) to the active time if the queue length
98148c5f4Alan Wright * was non-zero during that interval; and we add the product of the elapsed time
99148c5f4Alan Wright * times the queue length to the running length*time sum.
100148c5f4Alan Wright *
101148c5f4Alan Wright * This method is generalizable to measuring residency in any defined system:
102148c5f4Alan Wright * instead of queue lengths, think of "outstanding RPC calls to server X".
103148c5f4Alan Wright *
104148c5f4Alan Wright * A large number of I/O subsystems have at least two basic "lists" of
105148c5f4Alan Wright * transactions they manage: one for transactions that have been accepted for
106148c5f4Alan Wright * processing but for which processing has yet to begin, and one for
107148c5f4Alan Wright * transactions which are actively being processed (but not done). For this
108148c5f4Alan Wright * reason, two cumulative time statistics are defined here: wait (pre-service)
109148c5f4Alan Wright * time, and run (service) time.
110148c5f4Alan Wright *
111148c5f4Alan Wright * All times are 64-bit nanoseconds (hrtime_t), as returned by gethrtime().
112148c5f4Alan Wright *
113148c5f4Alan Wright * The units of cumulative busy time are accumulated nanoseconds. The units of
114148c5f4Alan Wright * cumulative length*time products are elapsed time times queue length.
115148c5f4Alan Wright *
116148c5f4Alan Wright * Updates to the fields below are performed implicitly by calls to
117148c5f4Alan Wright * these functions:
118148c5f4Alan Wright *
119148c5f4Alan Wright *	smb_srqueue_init()
120148c5f4Alan Wright *	smb_srqueue_destroy()
121148c5f4Alan Wright *	smb_srqueue_waitq_enter()
122148c5f4Alan Wright *	smb_srqueue_runq_exit()
123148c5f4Alan Wright *	smb_srqueue_waitq_to_runq()
124148c5f4Alan Wright *	smb_srqueue_update()
125148c5f4Alan Wright *
126148c5f4Alan Wright * These fields should never be updated by any other means.
127148c5f4Alan Wright */
128148c5f4Alan Wrighttypedef struct smb_srqueue {
129148c5f4Alan Wright	kmutex_t	srq_mutex;
130148c5f4Alan Wright	hrtime_t	srq_wlastupdate;
131148c5f4Alan Wright	hrtime_t	srq_wtime;
132148c5f4Alan Wright	hrtime_t	srq_wlentime;
133148c5f4Alan Wright	hrtime_t	srq_rlastupdate;
134148c5f4Alan Wright	hrtime_t	srq_rtime;
135148c5f4Alan Wright	hrtime_t	srq_rlentime;
136148c5f4Alan Wright	uint32_t	srq_wcnt;
137148c5f4Alan Wright	uint32_t	srq_rcnt;
138148c5f4Alan Wright} smb_srqueue_t;
139148c5f4Alan Wright
140148c5f4Alan Wright/*
141148c5f4Alan Wright * The fields with the prefix 'ly_a' contain the statistics collected since the
142148c5f4Alan Wright * server was last started ('a' for 'aggregated'). The fields with the prefix
143148c5f4Alan Wright * 'ly_d' contain the statistics collected since the last snapshot ('d' for
144148c5f4Alan Wright * 'delta').
145148c5f4Alan Wright */
146148c5f4Alan Wrighttypedef struct smb_latency {
147148c5f4Alan Wright	kmutex_t	ly_mutex;
148148c5f4Alan Wright	uint64_t	ly_a_nreq;
149148c5f4Alan Wright	hrtime_t	ly_a_sum;
150148c5f4Alan Wright	hrtime_t	ly_a_mean;
151148c5f4Alan Wright	hrtime_t	ly_a_stddev;
152148c5f4Alan Wright	uint64_t	ly_d_nreq;
153148c5f4Alan Wright	hrtime_t	ly_d_sum;
154148c5f4Alan Wright	hrtime_t	ly_d_mean;
155148c5f4Alan Wright	hrtime_t	ly_d_stddev;
156148c5f4Alan Wright} smb_latency_t;
157148c5f4Alan Wright
1588622ec4Gordon Rosstypedef struct smb_disp_stats {
1598622ec4Gordon Ross	volatile uint64_t sdt_txb;
1608622ec4Gordon Ross	volatile uint64_t sdt_rxb;
1618622ec4Gordon Ross	smb_latency_t	sdt_lat;
1628622ec4Gordon Ross} smb_disp_stats_t;
1638622ec4Gordon Ross
164da6c28aamwint smb_noop(void *, size_t, int);
165da6c28aamw
166da6c28aamw#define	SMB_AUDIT_STACK_DEPTH	16
167da6c28aamw#define	SMB_AUDIT_BUF_MAX_REC	16
168da6c28aamw#define	SMB_AUDIT_NODE		0x00000001
169da6c28aamw
170c8ec8eejose borrego/*
171c8ec8eejose borrego * Maximum number of records returned in SMBsearch, SMBfind
172c8ec8eejose borrego * and SMBfindunique response. Value set to 10 for compatibility
173c8ec8eejose borrego * with Windows.
174c8ec8eejose borrego */
175c8ec8eejose borrego#define	SMB_MAX_SEARCH		10
176c8ec8eejose borrego
1777f667e7jose borrego#define	SMB_SEARCH_ATTRIBUTES    \
1787f667e7jose borrego	(FILE_ATTRIBUTE_HIDDEN | \
1797f667e7jose borrego	FILE_ATTRIBUTE_SYSTEM |  \
1807f667e7jose borrego	FILE_ATTRIBUTE_DIRECTORY)
1817f667e7jose borrego
1822c1b14ejose borrego#define	SMB_SEARCH_HIDDEN(sattr) ((sattr) & FILE_ATTRIBUTE_HIDDEN)
1832c1b14ejose borrego#define	SMB_SEARCH_SYSTEM(sattr) ((sattr) & FILE_ATTRIBUTE_SYSTEM)
1842c1b14ejose borrego#define	SMB_SEARCH_DIRECTORY(sattr) ((sattr) & FILE_ATTRIBUTE_DIRECTORY)
1857f667e7jose borrego#define	SMB_SEARCH_ALL(sattr) ((sattr) & SMB_SEARCH_ATTRIBUTES)
1862c1b14ejose borrego
187da6c28aamwtypedef struct {
188da6c28aamw	uint32_t		anr_refcnt;
189da6c28aamw	int			anr_depth;
190da6c28aamw	pc_t			anr_stack[SMB_AUDIT_STACK_DEPTH];
191da6c28aamw} smb_audit_record_node_t;
192da6c28aamw
193da6c28aamwtypedef struct {
194da6c28aamw	int			anb_index;
195da6c28aamw	int			anb_max_index;
196da6c28aamw	smb_audit_record_node_t	anb_records[SMB_AUDIT_BUF_MAX_REC];
197da6c28aamw} smb_audit_buf_node_t;
198da6c28aamw
199da6c28aamw/*
200da6c28aamw * Thread State Machine
201da6c28aamw * --------------------
202da6c28aamw *
203da6c28aamw *			    T5			   T0
204da6c28aamw * smb_thread_destroy()	<-------+		+------- smb_thread_init()
205da6c28aamw *                              |		|
206da6c28aamw *				|		v
207da6c28aamw *			+-----------------------------+
208da6c28aamw *			|   SMB_THREAD_STATE_EXITED   |<---+
209da6c28aamw *			+-----------------------------+	   |
210da6c28aamw *				      | T1		   |
211da6c28aamw *				      v			   |
212da6c28aamw *			+-----------------------------+	   |
213da6c28aamw *			|  SMB_THREAD_STATE_STARTING  |	   |
214da6c28aamw *			+-----------------------------+	   |
215da6c28aamw *				     | T2		   | T4
216da6c28aamw *				     v			   |
217da6c28aamw *			+-----------------------------+	   |
218da6c28aamw *			|  SMB_THREAD_STATE_RUNNING   |	   |
219da6c28aamw *			+-----------------------------+	   |
220da6c28aamw *				     | T3		   |
221da6c28aamw *				     v			   |
222da6c28aamw *			+-----------------------------+	   |
223da6c28aamw *			|  SMB_THREAD_STATE_EXITING   |----+
224da6c28aamw *			+-----------------------------+
225da6c28aamw *
226da6c28aamw * Transition T0
227da6c28aamw *
228da6c28aamw *    This transition is executed in smb_thread_init().
229da6c28aamw *
230da6c28aamw * Transition T1
231da6c28aamw *
232da6c28aamw *    This transition is executed in smb_thread_start().
233da6c28aamw *
234da6c28aamw * Transition T2
235da6c28aamw *
236da6c28aamw *    This transition is executed by the thread itself when it starts running.
237da6c28aamw *
238da6c28aamw * Transition T3
239da6c28aamw *
240da6c28aamw *    This transition is executed by the thread itself in
241da6c28aamw *    smb_thread_entry_point() just before calling thread_exit().
242da6c28aamw *
243da6c28aamw *
244da6c28aamw * Transition T4
245da6c28aamw *
246da6c28aamw *    This transition is executed in smb_thread_stop().
247da6c28aamw *
248da6c28aamw * Transition T5
249da6c28aamw *
250da6c28aamw *    This transition is executed in smb_thread_destroy().
251da6c28aamw */
252da6c28aamwtypedef enum smb_thread_state {
253da6c28aamw	SMB_THREAD_STATE_STARTING = 0,
254da6c28aamw	SMB_THREAD_STATE_RUNNING,
255da6c28aamw	SMB_THREAD_STATE_EXITING,
256b819ceaGordon Ross	SMB_THREAD_STATE_EXITED,
257b819ceaGordon Ross	SMB_THREAD_STATE_FAILED
258da6c28aamw} smb_thread_state_t;
259da6c28aamw
260da6c28aamwstruct _smb_thread;
261da6c28aamw
262da6c28aamwtypedef void (*smb_thread_ep_t)(struct _smb_thread *, void *ep_arg);
263da6c28aamw
264da6c28aamw#define	SMB_THREAD_MAGIC	0x534D4254	/* SMBT */
265da6c28aamw
266da6c28aamwtypedef struct _smb_thread {
267da6c28aamw	uint32_t		sth_magic;
268a90cf9fGordon Ross	char			sth_name[32];
269da6c28aamw	smb_thread_state_t	sth_state;
270da6c28aamw	kthread_t		*sth_th;
271da6c28aamw	kt_did_t		sth_did;
272da6c28aamw	smb_thread_ep_t		sth_ep;
273da6c28aamw	void			*sth_ep_arg;
27408344b2Gordon Ross	pri_t			sth_pri;
275da6c28aamw	boolean_t		sth_kill;
276da6c28aamw	kmutex_t		sth_mtx;
277da6c28aamw	kcondvar_t		sth_cv;
278da6c28aamw} smb_thread_t;
279da6c28aamw
280da6c28aamw/*
281da6c28aamw * Pool of IDs
282da6c28aamw * -----------
283da6c28aamw *
284da6c28aamw *    A pool of IDs is a pool of 16 bit numbers. It is implemented as a bitmap.
285da6c28aamw *    A bit set to '1' indicates that that particular value has been allocated.
286da6c28aamw *    The allocation process is done shifting a bit through the whole bitmap.
287da6c28aamw *    The current position of that index bit is kept in the smb_idpool_t
288da6c28aamw *    structure and represented by a byte index (0 to buffer size minus 1) and
289da6c28aamw *    a bit index (0 to 7).
290da6c28aamw *
291da6c28aamw *    The pools start with a size of 8 bytes or 64 IDs. Each time the pool runs
292da6c28aamw *    out of IDs its current size is doubled until it reaches its maximum size
293da6c28aamw *    (8192 bytes or 65536 IDs). The IDs 0 and 65535 are never given out which
294da6c28aamw *    means that a pool can have a maximum number of 65534 IDs available.
295da6c28aamw */
296da6c28aamw#define	SMB_IDPOOL_MAGIC	0x4944504C	/* IDPL */
297da6c28aamw#define	SMB_IDPOOL_MIN_SIZE	64	/* Number of IDs to begin with */
298da6c28aamw#define	SMB_IDPOOL_MAX_SIZE	64 * 1024
299da6c28aamw
300da6c28aamwtypedef struct smb_idpool {
301da6c28aamw	uint32_t	id_magic;
302da6c28aamw	kmutex_t	id_mutex;
303da6c28aamw	uint8_t		*id_pool;
304da6c28aamw	uint32_t	id_size;
30512b6558Gordon Ross	uint32_t	id_maxsize;
306da6c28aamw	uint8_t		id_bit;
307da6c28aamw	uint8_t		id_bit_idx;
308da6c28aamw	uint32_t	id_idx;
309da6c28aamw	uint32_t	id_idx_msk;
310da6c28aamw	uint32_t	id_free_counter;
311da6c28aamw	uint32_t	id_max_free_counter;
312da6c28aamw} smb_idpool_t;
313da6c28aamw
314da6c28aamw/*
3152c2961fjose borrego * Maximum size of a Transport Data Unit when CAP_LARGE_READX and
3162c2961fjose borrego * CAP_LARGE_WRITEX are not set.  CAP_LARGE_READX/CAP_LARGE_WRITEX
3172c2961fjose borrego * allow the payload to exceed the negotiated buffer size.
31821b7895jb *     4 --> NBT/TCP Transport Header.
31921b7895jb *    32 --> SMB Header
32021b7895jb *     1 --> Word Count byte
32121b7895jb *   510 --> Maximum Number of bytes of the Word Table (2 * 255)
32221b7895jb *     2 --> Byte count of the data
32321b7895jb * 65535 --> Maximum size of the data
32421b7895jb * -----
32521b7895jb * 66084
326da6c28aamw */
3272c2961fjose borrego#define	SMB_REQ_MAX_SIZE	66560		/* 65KB */
32821b7895jb#define	SMB_XPRT_MAX_SIZE	(SMB_REQ_MAX_SIZE + NETBIOS_HDR_SZ)
329da6c28aamw
33021b7895jb#define	SMB_TXREQ_MAGIC		0X54524251	/* 'TREQ' */
3315cdbe94jbtypedef struct {
33221b7895jb	list_node_t	tr_lnd;
333a90cf9fGordon Ross	uint32_t	tr_magic;
33421b7895jb	int		tr_len;
33521b7895jb	uint8_t		tr_buf[SMB_XPRT_MAX_SIZE];
33621b7895jb} smb_txreq_t;
3375cdbe94jb
3385cdbe94jb#define	SMB_TXLST_MAGIC		0X544C5354	/* 'TLST' */
3395cdbe94jbtypedef struct {
3405cdbe94jb	uint32_t	tl_magic;
3415cdbe94jb	kmutex_t	tl_mutex;
342a90cf9fGordon Ross	kcondvar_t	tl_wait_cv;
3435cdbe94jb	boolean_t	tl_active;
3445cdbe94jb} smb_txlst_t;
3455cdbe94jb
346da6c28aamw/*
347faa1795jb * Maximum buffer size for NT is 37KB.  If all clients are Windows 2000, this
348faa1795jb * can be changed to 64KB.  37KB must be used with a mix of NT/Windows 2000
349faa1795jb * clients because NT loses directory entries when values greater than 37KB are
350faa1795jb * used.
351faa1795jb *
352faa1795jb * Note: NBT_MAXBUF will be subtracted from the specified max buffer size to
353faa1795jb * account for the NBT header.
354da6c28aamw */
355faa1795jb#define	NBT_MAXBUF		8
356faa1795jb#define	SMB_NT_MAXBUF		(37 * 1024)
357da6c28aamw
358da6c28aamw#define	OUTBUFSIZE		(65 * 1024)
359da6c28aamw#define	SMBHEADERSIZE		32
360da6c28aamw#define	SMBND_HASH_MASK		(0xFF)
361da6c28aamw#define	MAX_IOVEC		512
362da6c28aamw#define	MAX_READREF		(8 * 1024)
363da6c28aamw
364da6c28aamw#define	SMB_WORKER_MIN		4
365da6c28aamw#define	SMB_WORKER_DEFAULT	64
366da6c28aamw#define	SMB_WORKER_MAX		1024
367da6c28aamw
368da6c28aamw/*
3699fb67eaafshin salek ardakani - Sun Microsystems - Irvine United States * Destructor object used in the locked-list delete queue.
370da6c28aamw */
3719fb67eaafshin salek ardakani - Sun Microsystems - Irvine United States#define	SMB_DTOR_MAGIC		0x44544F52	/* DTOR */
3729fb67eaafshin salek ardakani - Sun Microsystems - Irvine United States#define	SMB_DTOR_VALID(d)	\
3739fb67eaafshin salek ardakani - Sun Microsystems - Irvine United States    ASSERT(((d) != NULL) && ((d)->dt_magic == SMB_DTOR_MAGIC))
374da6c28aamw
3759fb67eaafshin salek ardakani - Sun Microsystems - Irvine United Statestypedef void (*smb_dtorproc_t)(void *);
376da6c28aamw
3779fb67eaafshin salek ardakani - Sun Microsystems - Irvine United Statestypedef struct smb_dtor {
3789fb67eaafshin salek ardakani - Sun Microsystems - Irvine United States	list_node_t	dt_lnd;
379a90cf9fGordon Ross	uint32_t	dt_magic;
3809fb67eaafshin salek ardakani - Sun Microsystems - Irvine United States	void		*dt_object;
3819fb67eaafshin salek ardakani - Sun Microsystems - Irvine United States	smb_dtorproc_t	dt_proc;
3829fb67eaafshin salek ardakani - Sun Microsystems - Irvine United States} smb_dtor_t;
383da6c28aamw
384da6c28aamwtypedef struct smb_llist {
385da6c28aamw	krwlock_t	ll_lock;
386da6c28aamw	list_t		ll_list;
387da6c28aamw	uint32_t	ll_count;
388da6c28aamw	uint64_t	ll_wrop;
3899fb67eaafshin salek ardakani - Sun Microsystems - Irvine United States	kmutex_t	ll_mutex;
3909fb67eaafshin salek ardakani - Sun Microsystems - Irvine United States	list_t		ll_deleteq;
3919fb67eaafshin salek ardakani - Sun Microsystems - Irvine United States	uint32_t	ll_deleteq_count;
392cb17486joyce mcintosh	boolean_t	ll_flushing;
393da6c28aamw} smb_llist_t;
394da6c28aamw
395811599aMatt Bardentypedef struct smb_bucket {
396811599aMatt Barden	smb_llist_t	b_list;
397811599aMatt Barden	uint32_t	b_max_seen;
398811599aMatt Barden} smb_bucket_t;
399811599aMatt Barden
400811599aMatt Bardentypedef struct smb_hash {
401811599aMatt Barden	uint32_t	rshift;
402811599aMatt Barden	uint32_t	num_buckets;
403811599aMatt Barden	smb_bucket_t	*buckets;
404811599aMatt Barden} smb_hash_t;
405811599aMatt Barden
406da6c28aamwtypedef struct smb_slist {
407da6c28aamw	kmutex_t	sl_mutex;
408da6c28aamw	kcondvar_t	sl_cv;
409da6c28aamw	list_t		sl_list;
410da6c28aamw	uint32_t	sl_count;
411da6c28aamw	boolean_t	sl_waiting;
412da6c28aamw} smb_slist_t;
413da6c28aamw
414148c5f4Alan Wright/*
415148c5f4Alan Wright * smb_avl_t State Machine
416148c5f4Alan Wright * --------------------
417148c5f4Alan Wright *
418148c5f4Alan Wright *                      +-----------------------------+
419148c5f4Alan Wright *                      |     SMB_AVL_STATE_START     |
420148c5f4Alan Wright *                      +-----------------------------+
421