nodecontrol_list.php3 17.2 KB
Newer Older
1
<?php
Leigh B. Stoller's avatar
Leigh B. Stoller committed
2
3
#
# EMULAB-COPYRIGHT
4
# Copyright (c) 2000-2010 University of Utah and the Flux Group.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
5
6
# All rights reserved.
#
7
include("defs.php3");
8
include_once("node_defs.php");
9

10
11
12
13
14
15
#
# This page is used for both admin node control, and for mere user
# information purposes. Be careful about what you do outside of
# $isadmin tests.
# 

16
17
18
#
# Only known and logged in users can do this.
#
19
20
21
$this_user = CheckLoginOrDie();
$uid       = $this_user->uid();
$isadmin   = ISADMIN();
22
23

#
24
# Verify page arguments.
25
26
27
#
$optargs = OptionalPageArguments("target_user",	PAGEARG_USER,
				 "showtype",    PAGEARG_STRING,
28
				 "typefilter",  PAGEARG_STRING,
29
30
31
32
				 "bypid",       PAGEARG_STRING);

if (isset($target_user)) {
    if (! $target_user->AccessCheck($this_user, $TB_USERINFO_READINFO)) {
33
34
35
36
	USERERROR("You do not have permission to do this!", 1);
    }
    $target_uid  = $target_user->uid();
    $target_idx  = $target_user->uid_idx();
37
38
}
else {
39
40
41
    $target_uid  = $uid;
    $target_idx  = $this_user->uid_idx();
    $target_user = $this_user;
42
}
43

44
45
46
47
48
#
# Standard Testbed Header
#
PAGEHEADER("Node Control Center");

49
echo "<b>Tabular views: <a href='nodecontrol_list.php3?showtype=summary'>summary</a>,
50
               <a href='nodecontrol_list.php3?showtype=pcs'>pcs</a>,
51
               <a href='nodecontrol_list.php3?showtype=wireless'>
52
                                                        wireless</a>,";
53
if ($TBMAINSITE) {
54
    echo "     <a href='nodecontrol_list.php3?showtype=widearea&typefilter=pcpg,pcpg-i2'>protogeni</a>,";
55
56
}
echo "         <a href='nodecontrol_list.php3?showtype=widearea'>widearea</a>";
57
58

if ($isadmin) {
59
60
    echo    ", <a href='nodeutilization.php'>utilization</a>,
               <a href='nodecontrol_list.php3?showtype=virtnodes'>virtual</a>,
61
62
63
               <a href='nodecontrol_list.php3?showtype=physical'>physical</a>,
               <a href='nodecontrol_list.php3?showtype=all'>all</a>";
}
64
echo ".</b><br>\n";
65

66
67
68
echo "<b>Map views: <a href='floormap.php3'>wireless</a>";
if ($TBMAINSITE) {
    echo ", <a href='floormap.php3?feature=usrp'>
69
70
71
              GNU USRP</a>,
            <a href='floormap.php3?feature=usrp2'>
              USRP2</a> (software defined radio),
72
73
74
75
            <a href='robotmap.php3'>robot</a>";
}
echo ".</b><br>\n";

76
if (!isset($showtype)) {
77
    $showtype='summary';
78
79
}

Chad Barb's avatar
   
Chad Barb committed
80
81
82
$additionalVariables = "";
$additionalLeftJoin  = "";

83
84
85
86
87
88
89
if (! strcmp($showtype, "summary")) {
    # Separate query below.
    $role   = "";
    $clause = "";
    $view   = "Free Node Summary";
}
elseif (! strcmp($showtype, "all")) {
90
91
92
    $role   = "(role='testnode' or role='virtnode')";
    $clause = "";
    $view   = "All";
93
94
}
elseif (! strcmp($showtype, "pcs")) {
95
96
97
    $role   = "(role='testnode')";
    $clause = "and (nt.class='pc')";
    $view   = "PCs";
98
}
99
100
101
102
elseif (! strcmp($showtype, "sharks")) {
    $role   = "(role='testnode')";
    $clause = "and (nt.class='shark')";
    $view   = "Sharks";
103
104
}
elseif (! strcmp($showtype, "virtnodes")) {
105
106
107
108
    $role   = "(role='virtnode')";
    $clause = "";
    $view   = "Virtual Nodes";
}
109
110
111
112
113
elseif (! strcmp($showtype, "physical")) {
    $role   = "";
    $clause = "(nt.isvirtnode=0)";
    $view   = "Physical Nodes";
}
114
115
116
elseif (! strcmp($showtype, "widearea")) {
    $role   = "(role='testnode')";
    $clause = "and (nt.isremotenode=1)";
Chad Barb's avatar
   
Chad Barb committed
117
118
119
120
121
122
123

    $additionalVariables = ",".
			   "wani.machine_type,".
			   "REPLACE(CONCAT_WS(', ',".
			   "wani.city,wani.state,wani.country,wani.zip), ".
		 	   "'USA, ','')".
			   "AS location, ".
124
	 		   "wani.connect_type, ".
125
			   "wani.hostname, " .
126
127
128
                           "wani.site, ".
	 		   "wani.latitude, ".
			   "wani.longitude";
Chad Barb's avatar
   
Chad Barb committed
129
130
131
    $additionalLeftJoin = "LEFT JOIN widearea_nodeinfo AS wani ".
			  "ON n.node_id=wani.node_id";

132
    $view   = "Widearea";
133
}
134
135
136
137
138
139
140
141
142
elseif (! strcmp($showtype, "wireless")) {
    $role   = "(role='testnode')";
    $clause = "and (loc.node_id is not null)";

    $additionalLeftJoin = "LEFT JOIN location_info AS loc ".
			  "ON n.node_id=loc.node_id";

    $view   = "Wireless";
}
143
144
145
146
147
elseif (preg_match("/^[-\w]+$/", $showtype)) {
    $role   = "(role='testnode')";
    $clause = "and (nt.type='$showtype')";
    $view   = "only <a href=shownodetype.php3?node_type=$showtype>$showtype</a>";
}
148
else {
149
150
151
    $role   = "(role='testnode')";
    $clause = "and (nt.class='pc')";
    $view   = "PCs";
152
}
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169

# If adding an additional type filter list, do that...
if (isset($typefilter)) {
    $types = explode(",",$typefilter);
    $typeclause = "and nt.type in (";
    foreach ($types as $t) {
	# Sanitize.
	if (!preg_match("/^[-\w]+$/", $t)) {
	    PAGEARGERROR("Invalid characters in typefilter argument '$t'.");
	}
	$typeclause .= "'$t',";
    }
    $typeclause = rtrim($typeclause,",");
    $typeclause .= ")";
    $clause .= " $typeclause";
}

170
171
172
173
174
# If admin or widearea, show the vname too. 
$showvnames = 0;
if ($isadmin || !strcmp($showtype, "widearea")) {
    $showvnames = 1;
}
175

176
177
178
179
180
181
182
183
#
# Summary info very different.
# 
if (! strcmp($showtype, "summary")) {
    # Get permissions table so as not to show nodes the user is not allowed
    # to see.
    $perms = array();
    
184
    if (!$isadmin || isset($bypid)) {
185
186
187
188
189
190
	$query_result =
	    DBQueryFatal("select type from nodetypeXpid_permissions");

	while ($row = mysql_fetch_array($query_result)) {
	    $perms{$row[0]} = 0;
	}
191
192
193
194
195
196

	$pidclause = "";
	if (isset($bypid)) {
	    if ($bypid == "" || !TBvalid_pid($bypid)) {
		PAGEARGERROR("Invalid characters in 'bypid' argument!");
	    }
197
	    if (! ($target_project = Project::Lookup($bypid))) {
198
199
		PAGEARGERROR("No such project '$bypid'!");
	    }
200
201
	    if (!$target_project->AccessCheck($this_user,
					      $TB_PROJECT_READINFO)){
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
		USERERROR("You are not a member of project '$bypid!", 1);
	    }
	    $pidclause = "and g.pid='$bypid'";
	}
	if ($isadmin) {
	    $query_result =
		DBQueryFatal("select distinct type ".
			     "  from nodetypeXpid_permissions ".
			     "where pid='$bypid'");
	}
	else {
	    $query_result =
		DBQueryFatal("select distinct type from group_membership as g ".
			     "left join nodetypeXpid_permissions as p ".
			     "     on g.pid=p.pid ".
217
			     "where uid_idx='$target_idx' $pidclause");
218
	}
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
	
	while ($row = mysql_fetch_array($query_result)) {
	    $perms{$row[0]} = 1;
	}
    }
    
    # Get totals by type.
    $query_result =
	DBQueryFatal("select n.type,count(*) from nodes as n ".
		     "left join node_types as nt on n.type=nt.type ".
		     "where (role='testnode') and ".
		     "      (nt.class!='shark' and nt.class!='pcRemote' ".
		     "      and nt.class!='pcplabphys') ".
		     "group BY n.type");

234
235
    $alltotal  = 0;
    $allfree   = 0;
Timothy Stack's avatar
   
Timothy Stack committed
236
    $allunknown = 0;
237
    $totals    = array();
Timothy Stack's avatar
   
Timothy Stack committed
238
239
    $freecounts = array();
    $unknowncounts = array();
240
241
242
243
244
245
246

    while ($row = mysql_fetch_array($query_result)) {
	$type  = $row[0];
	$count = $row[1];

	$totals[$type]    = $count;
	$freecounts[$type] = 0;
Timothy Stack's avatar
   
Timothy Stack committed
247
	$unknowncounts[$type] = 0;
248
249
    }

250
251
    # Get free totals by type.  Note we also check that the physical node
    # is free, see note on non-summary query for why.
252
    $query_result =
Timothy Stack's avatar
   
Timothy Stack committed
253
	DBQueryFatal("select n.eventstate,n.type,count(*) from nodes as n ".
254
		     "left join nodes as np on np.node_id=n.phys_nodeid ".
255
256
		     "left join node_types as nt on n.type=nt.type ".
		     "left join reserved as r on r.node_id=n.node_id ".
257
258
		     "left join reserved as rp on rp.node_id=n.phys_nodeid ".
		     "where (n.role='testnode') and ".
259
260
		     "      (nt.class!='shark' and nt.class!='pcRemote' ".
		     "      and nt.class!='pcplabphys') ".
261
262
		     "      and r.pid is null and rp.pid is null ".
		     "      and n.reserved_pid is null and np.reserved_pid is null ".
Timothy Stack's avatar
   
Timothy Stack committed
263
		     "group BY n.eventstate,n.type");
264
265

    while ($row = mysql_fetch_array($query_result)) {
Timothy Stack's avatar
   
Timothy Stack committed
266
267
268
269
270
271
272
273
274
275
276
277
	$type  = $row[1];
	$count = $row[2];
        # XXX Yeah, I'm a doofus and can't figure out how to do this in SQL.
	if (($row[0] == TBDB_NODESTATE_ISUP) ||
	    ($row[0] == TBDB_NODESTATE_PXEWAIT) ||
	    ($row[0] == TBDB_NODESTATE_ALWAYSUP) ||
	    ($row[0] == TBDB_NODESTATE_POWEROFF)) {
	    $freecounts[$type] += $count;
	}
	else {
	    $unknowncounts[$type] += $count;
	}
278
279
    }

280
    $projlist = $target_user->ProjectAccessList($TB_PROJECT_CREATEEXPT);
281
282
283
284
285
286
287
288
289
290
291
292
    if (count($projlist) > 1) {
	echo "<b>By Project Permission: ";
	while (list($project) = each($projlist)) {
	    echo "<a href='nodecontrol_list.php3?".
		"showtype=summary&bypid=$project'>$project</a>,\n";
	}
	echo "<a href='nodecontrol_list.php3?showtype=summary'>".
	    "combined membership</a>.\n";
	echo "</b><br>\n";
    }

    echo "<br><center>
293
          <b>Free Node Summary</b>
294
295
296
297
298
          <br>\n";
    if (isset($bypid)) {
	echo "($bypid)<br><br>\n";
    }
    echo "<table>
299
300
301
302
303
304
305
306
307
308
309
310
          <tr>
             <th>Type</th>
             <th align=center>Free<br>Nodes</th>
             <th align=center>Total<br>Nodes</th>
          </tr>\n";

    foreach($totals as $key => $value) {
	$freecount = $freecounts[$key];

	# Check perm entry.
	if (isset($perms[$key]) && !$perms[$key])
	    continue;
311
312

	$allfree   += $freecount;
Timothy Stack's avatar
   
Timothy Stack committed
313
	$allunknown += $unknowncounts[$key];
314
	$alltotal  += $value;
Timothy Stack's avatar
   
Timothy Stack committed
315
316
317
318
319

	if ($unknowncounts[$key])
	    $ast = "*";
	else
	    $ast = "";
320
	
321
322
323
324
325
326
	echo "<tr>\n";
	if ($isadmin)
	    echo "<td><a href=editnodetype.php3?node_type=$key>\n";
	else
	    echo "<td><a href=shownodetype.php3?node_type=$key>\n";
	echo "           $key</a></td>
Timothy Stack's avatar
   
Timothy Stack committed
327
              <td align=center>${freecount}${ast}</td>
328
329
330
              <td align=center>$value</td>
              </tr>\n";
    }
331
332
333
334
335
336
    echo "<tr></tr>\n";
    echo "<tr>
            <td><b>Totals</b></td>
              <td align=center>$allfree</td>
              <td align=center>$alltotal</td>
              </tr>\n";
337
    echo "</table>\n";
338
339
340

    echo "<center><a href=shownodetype_list.php>Node Type List</a></center>";

Timothy Stack's avatar
   
Timothy Stack committed
341
342
343
344
    if ($allunknown > 0) {
	    echo "<br><font size=-1><b>*</b> - Some nodes ($allunknown) are ".
		    "free, but currently in an unallocatable state.</font>";
    }
345
346
347
348
    PAGEFOOTER();
    exit();
}

349
350
#
# Suck out info for all the nodes.
351
352
353
354
355
356
#
# If a node is free we check to make sure that that the physical node
# is also.  This is based on the assumption that if a physical node is
# not available, neither is the node, such as the case with netpga2.
# This may not be true for virtual nodes, such as PlanetLab slices,
# but virtual nodes are allocated on demand, and thus are never free.
357
# 
Leigh B. Stoller's avatar
Leigh B. Stoller committed
358
$query_result =
359
    DBQueryFatal("select distinct n.node_id,n.phys_nodeid,n.type,ns.status, ".
360
361
362
363
364
		 "   n.def_boot_osid, ".
		 "   if(r.pid is not null,r.pid,rp.pid) as pid, ".
	         "   if(r.pid is not null,r.eid,rp.eid) as eid, ".
		 "   nt.class, ".
	 	 "   if(r.pid is not null,r.vname,rp.vname) as vname ".
Chad Barb's avatar
   
Chad Barb committed
365
366
		 "$additionalVariables ".
		 "from nodes as n ".
367
		 "left join node_types as nt on n.type=nt.type ".
368
		 "left join node_status as ns on n.node_id=ns.node_id ".
369
		 "left join reserved as r on n.node_id=r.node_id ".
370
		 "left join reserved as rp on n.phys_nodeid=rp.node_id ".
Chad Barb's avatar
   
Chad Barb committed
371
		 "$additionalLeftJoin ".
372
373
		 "where $role $clause ".
		 "ORDER BY priority");
374
375
376
377

if (mysql_num_rows($query_result) == 0) {
    echo "<center>Oops, no nodes to show you!</center>";
    PAGEFOOTER();
378
    exit();
379
}
380

381
#
382
# First count up free nodes as well as status counts.
383
#
384
385
386
387
388
$num_free = 0;
$num_up   = 0;
$num_pd   = 0;
$num_down = 0;
$num_unk  = 0;
389
$freetypes= array();
390

391
while ($row = mysql_fetch_array($query_result)) {
392
393
394
    $pid                = $row["pid"];
    $status             = $row["status"];
    $type               = $row["type"];
395

396
397
398
    if (! isset($freetypes[$type])) {
	$freetypes[$type] = 0;
    }
399
400
401
    if (!$pid) {
	$num_free++;
	$freetypes[$type]++;
402
	continue;
403
    }
404
405
406
407
408
409
410
411
412
413
414
415
416
417
    switch ($status) {
    case "up":
	$num_up++;
	break;
    case "possibly down":
    case "unpingable":
	$num_pd++;
	break;
    case "down":
	$num_down++;
	break;
    default:
	$num_unk++;
	break;
418
419
    }
}
420
$num_total = ($num_free + $num_up + $num_down + $num_pd + $num_unk);
421
422
mysql_data_seek($query_result, 0);

423
if (! strcmp($showtype, "widearea")) {
424
    echo "<a href='$WIKIDOCURL/widearea'>
425
426
427
428
             Widearea Usage Notes</a>\n";
}

echo "<br><center><b>
429
       View: $view\n";
430
431
432

if (! strcmp($showtype, "widearea")) {
    echo "<br>
433
          <a href=widearea_nodeinfo.php3>(Widearea Link Metrics)</a><br>
Leigh B. Stoller's avatar
Leigh B. Stoller committed
434
          <a href=plabmetrics.php3>(PlanetLab Node Metrics)</a>\n";
435
436
437
}

echo "</b></center><br>\n";
438

439
440
441
SUBPAGESTART();

echo "<table>
442
443
444
       <tr><td align=right>
           <img src='/autostatus-icons/greenball.gif' alt=up>
           <b>Up</b></td>
445
446
           <td align=left>$num_up</td>
       </tr>
447
448
449
       <tr><td align=right nowrap>
           <img src='/autostatus-icons/yellowball.gif' alt='possibly down'>
           <b>Possibly Down</b></td>
450
451
           <td align=left>$num_pd</td>
       </tr>
452
453
454
       <tr><td align=right>
           <img src='/autostatus-icons/blueball.gif' alt=unknown>
           <b>Unknown</b></td>
455
456
           <td align=left>$num_unk</td>
       </tr>
457
458
459
       <tr><td align=right>
           <img src='/autostatus-icons/redball.gif' alt=down>
           <b>Down</b></td>
460
461
           <td align=left>$num_down</td>
       </tr>
462
463
464
       <tr><td align=right>
           <img src='/autostatus-icons/whiteball.gif' alt=free>
           <b>Free</b></td>
465
466
           <td align=left>$num_free</td>
       </tr>
467
468
469
       <tr><td align=right><b>Total</b></td>
           <td align=left>$num_total</td>
       </tr>
470
471
472
473
474
       <tr><td colspan=2 nowrap align=center>
               <b>Free Subtotals</b></td></tr>\n";

foreach($freetypes as $key => $value) {
    echo "<tr>
475
           <td align=right><a href=shownodetype.php3?node_type=$key>
476
                           $key</a></td>
477
478
479
480
           <td align=left>$value</td>
          </tr>\n";
}
echo "</table>\n";
481
482
SUBMENUEND_2B();

483
echo "<table border=2 cellpadding=2 cellspacing=2 id='nodelist'>\n";
484

485
echo "<thead class='sort'>";
486
echo "<tr>
487
488
489
490
491
492
493
          <th align=center>ID</th>\n";

if ($showvnames) {
    echo "<th align=center>Name</th>\n";
}

echo "    <th align=center>Type (Class)</th>
494
          <th align=center class='sorttable_nosort'>Up?</th>\n";
495
496

if ($isadmin) {
Chad Barb's avatar
   
Chad Barb committed
497
498
499
    echo "<th align=center>PID</th>
          <th align=center>EID</th>
          <th align=center>Default<br>OSID</th>\n";
500
}
501
502
elseif (strcmp($showtype, "widearea")) {
    # Widearea nodes are always "free"
Chad Barb's avatar
   
Chad Barb committed
503
    echo "<th align=center>Free?</th>\n";
Chad Barb's avatar
   
Chad Barb committed
504
505
506
}

if (!strcmp($showtype, "widearea")) {
507
    echo "<th align=center>Site</th>
Chad Barb's avatar
   
Chad Barb committed
508
	  <th align=center>Connection</th>
509
510
511
	  <th align=center>Location</th>
	  <th align=center>Latitude</th>
	  <th align=center>Longitude</th>";
Chad Barb's avatar
   
Chad Barb committed
512
513
}
    
514
echo "</tr></thead>\n";
515

516
while ($row = mysql_fetch_array($query_result)) {
517
518
519
    $node_id            = $row["node_id"]; 
    $phys_nodeid        = $row["phys_nodeid"]; 
    $type               = $row["type"];
520
    $class              = $row["class"];
521
522
523
524
525
    $def_boot_osid      = $row["def_boot_osid"];
    $pid                = $row["pid"];
    $eid                = $row["eid"];
    $vname              = $row["vname"];
    $status             = $row["status"];
526

Chad Barb's avatar
   
Chad Barb committed
527
    if (!strcmp($showtype, "widearea")) {	
528
529
530
531
532
	$site         = $row["site"];
	$machine_type = $row["machine_type"];
	$location     = $row["location"];
	$connect_type = $row["connect_type"];
	$vname        = $row["hostname"];
533
534
	$latitude     = $row["latitude"];
	$longitude    = $row["longitude"];
Chad Barb's avatar
   
Chad Barb committed
535
536
    } 

537
    echo "<tr>";
538

539
    # Admins get a link to expand the node.
540
541
    if ($isadmin ||
	(OPSGUY() && (!$pid || $pid == $TBOPSPID))) {
542
543
544
545
	echo "<td><A href='shownode.php3?node_id=$node_id'>$node_id</a> " .
	    (!strcmp($node_id, $phys_nodeid) ? "" :
	     "(<A href='shownode.php3?node_id=$phys_nodeid'>$phys_nodeid</a>)")
	    . "</td>\n";
546
547
    }
    else {
548
549
550
	echo "<td>$node_id " .
  	      (!strcmp($node_id, $phys_nodeid) ? "" : "($phys_nodeid)") .
	      "</td>\n";
551
    }
552
553
554
555
556
557
558

    if ($showvnames) {
	if ($vname)
	    echo "<td>$vname</td>\n";
	else
	    echo "<td>--</td>\n";
    }
559
    
560
561
    echo "   <td>$type ($class)</td>\n";

562
563
564
565
    if (!$pid)
	echo "<td align=center>
                  <img src='/autostatus-icons/whiteball.gif' alt=free></td>\n";
    elseif (!$status)
566
567
568
	echo "<td align=center>
                  <img src='/autostatus-icons/blueball.gif' alt=unk></td>\n";
    elseif ($status == "up")
569
570
	echo "<td align=center>
                  <img src='/autostatus-icons/greenball.gif' alt=up></td>\n";
571
    elseif ($status == "down")
572
573
	echo "<td align=center>
                  <img src='/autostatus-icons/redball.gif' alt=down></td>\n";
574
575
576
    else
	echo "<td align=center>
                  <img src='/autostatus-icons/yellowball.gif' alt=unk></td>\n";
577
578
579
580

    # Admins get pid/eid/vname, but mere users yes/no.
    if ($isadmin) {
	if ($pid) {
Mike Hibler's avatar
Mike Hibler committed
581
582
	    echo "<td><a href=showproject.php3?pid=$pid>$pid</a></td>
                  <td><a href=showexp.php3?pid=$pid&eid=$eid>$eid</a></td>\n";
583
584
585
586
587
	}
	else {
	    echo "<td>--</td>
   	          <td>--</td>\n";
	}
588
589
590
	if ($def_boot_osid &&
	    ($osinfo = OSinfo::Lookup($def_boot_osid))) {
	    $osname = $osinfo->osname();
591
	    echo "<td>$osname</td>\n";
592
	}
593
594
595
	else
	    echo "<td>&nbsp</td>\n";
    }
596
    elseif (strcmp($showtype, "widearea")) {
597
	if ($pid)
598
	    echo "<td>--</td>\n";
599
600
601
	else
	    echo "<td>Yes</td>\n";
    }
Chad Barb's avatar
   
Chad Barb committed
602
603

    if (!strcmp($showtype, "widearea")) {	
604
	echo "<td>$site</td>
Chad Barb's avatar
   
Chad Barb committed
605
	      <td>$connect_type</td>
606
607
608
	      <td><font size='-1'>$location</font></td>
	      <td><font size='-1'>$latitude</font></td>
	      <td><font size='-1'>$longitude</font></td>\n";
Chad Barb's avatar
   
Chad Barb committed
609
    }
610
    
611
    echo "</tr>\n";
612
613
614
}

echo "</table>\n";
615
616
617
echo "<script type='text/javascript' language='javascript'>
         sorttable.makeSortable(getObjbyName('nodelist'));
      </script>\n";
618
SUBPAGEEND();
619
620
621
622
623

#
# Standard Testbed Footer
# 
PAGEFOOTER();
624
625
626
?>