tazpanel annotate hardware.cgi @ rev 407

fix header, hardware.cgi: add "tazx auto" button
author Xander Ziiryanoff <psychomaniak@xakep.ru>
date Sun Jan 11 12:09:36 2015 +0200 (2015-01-11)
parents 106b85c1951c
children 299f204b8f1f
rev   line source
pankso@46 1 #!/bin/sh
pankso@46 2 #
pankso@46 3 # Hardware part of TazPanel - Devices, drivers, printing
pankso@46 4 #
pankso@379 5 # Copyright (C) 2011-2014 SliTaz GNU/Linux - BSD License
pankso@46 6 #
pankso@46 7
pankso@46 8 # Common functions from libtazpanel
pankso@46 9 . lib/libtazpanel
pankso@46 10 get_config
pascal@81 11 header
pankso@46 12
al@292 13 TITLE=$(gettext 'TazPanel - Hardware')
pankso@46 14
paul@385 15 # Call an optional module
pascal@373 16 lib()
pascal@373 17 {
pascal@373 18 module=lib/$1
pascal@373 19 shift
pascal@373 20 [ -s $module ] && . $module "$@"
pascal@373 21 }
pascal@373 22
al@363 23 lsusb_table()
al@363 24 {
al@363 25 cat << EOT
al@363 26 <table class="zebra outbox">
al@363 27 <thead><tr><td>Bus</td><td>Device</td><td>ID</td><td>Name</td></thead>
al@363 28 <tbody>
al@363 29 EOT
al@363 30 lsusb | sed 's|^Bus \([0-9]*\)|<tr><td>\1</td>|;
al@363 31 s|</td> Device \([0-9]*\):|</td><td>\1</td>|;
al@363 32 s|</td> ID \([^:]*:[^ ]*\)|</td><td><a href="?lsusb=\1">\1</a></td>|;
al@363 33 s| |<td>|2;
al@363 34 s|.*$|\0</td></tr>|'
al@363 35 echo "</tbody></table>"
al@363 36 }
al@363 37
al@363 38 lspci_table()
al@363 39 {
al@363 40 cat << EOT
al@363 41 <table class="zebra outbox">
al@363 42 <thead><tr><td>Slot</td><td>Device</td><td>Name</td></thead>
al@363 43 <tbody>
al@363 44 EOT
al@363 45 lspci | sed 's| |</td><td>|;
al@363 46 s|: |</td><td>|;
al@363 47 s|^\([^<]*\)|<a href="?lspci=\1">\1</a>|;
al@363 48 s|^.*$|<tr><td>\0</td></tr>|'
al@363 49 echo "</tbody></table>"
al@363 50 }
al@363 51
pankso@46 52 #
pankso@46 53 # Commands
pankso@46 54 #
pankso@46 55
pascal@81 56 case " $(GET) " in
pascal@81 57 *\ print\ *)
al@303 58 xhtml_header
al@303 59 echo "<h2>TODO</h2>" ;;
psychomaniak@407 60 *\ tazx\ *)
psychomaniak@407 61 xhtml_header
psychomaniak@407 62 cat << EOT
psychomaniak@407 63 <pre>$(tazx auto)</pre>
psychomaniak@407 64 EOT
psychomaniak@407 65 ;;
pankso@242 66 *\ detect\ *)
pankso@242 67 # Front end for Tazhw
pankso@242 68 # TODO: Add button to detect webcam, etc. Like in tazhw box.
pankso@242 69 xhtml_header
al@303 70 cat << EOT
pankso@242 71 <div id="wrapper">
al@303 72 <h2>$(gettext 'Detect hardware')</h2>
al@303 73 <p>$(gettext 'Detect PCI and USB hardware')</p>
pankso@242 74 </div>
al@303 75
al@391 76 <pre>$(tazhw detect-pci | sed 's|^>|\&gt;|g')</pre>
al@303 77
al@391 78 <pre>$(tazhw detect-usb | sed 's|^>|\&gt;|g')</pre>
pankso@242 79 EOT
al@303 80 ;;
pascal@81 81 *\ modules\ *|*\ modinfo\ *)
pankso@46 82 xhtml_header
pankso@46 83 cat << EOT
pankso@46 84 <div id="wrapper">
al@303 85 <h2>$(gettext 'Kernel modules')</h2>
al@303 86 <div class="float-right">
al@406 87 <form method="get" action="">
al@303 88 <input type="hidden" name="modules" />
al@303 89 <input type="search" placeholder="$(gettext 'Modules search')" name="search" />
al@303 90 </form>
al@303 91 </div>
al@303 92 <p>$(gettext 'Manage, search or get information about the Linux kernel modules')</p>
pankso@46 93 </div>
pankso@46 94 EOT
pankso@66 95 # Request may be modinfo output that we want in the page itself
al@303 96 get_modinfo="$(GET modinfo)"
al@303 97 if [ -n "$get_modinfo" ]; then
al@303 98 cat << EOT
al@303 99 <strong>$(eval_gettext 'Detailed information for module: $get_modinfo')</strong>
al@303 100
al@303 101 <pre>$(modinfo $get_modinfo)</pre>
al@303 102 EOT
pascal@81 103 fi
pascal@81 104 if [ -n "$(GET modprobe)" ]; then
al@303 105 echo "<pre>$(modprobe -v $(GET modprobe))</pre>"
pascal@81 106 fi
pascal@81 107 if [ -n "$(GET rmmod)" ]; then
pascal@81 108 echo "Removing"
pascal@81 109 rmmod -w $(GET rmmod)
pascal@81 110 fi
al@303 111 get_search="$(GET search)"
al@303 112 if [ -n "$get_search" ]; then
al@303 113 eval_gettext 'Matching result(s) for: $get_search'
pascal@81 114 echo '<pre>'
pascal@81 115 modprobe -l | grep "$(GET search)" | while read line
pascal@81 116 do
pascal@81 117 name=$(basename $line)
pascal@81 118 mod=${name%.ko.gz}
al@406 119 echo "$(gettext 'Module:') <a href='?modinfo=$mod'>$mod</a>"
pascal@81 120 done
pascal@81 121 echo '</pre>'
pascal@81 122 fi
pankso@46 123 cat << EOT
al@303 124 $(table_start)
pankso@46 125 <tr class="thead">
al@303 126 <td>$(gettext 'Module')</td>
al@303 127 <td>$(gettext 'Size')</td>
al@303 128 <td>$(gettext 'Used')</td>
al@303 129 <td>$(gettext 'by')</td>
pankso@46 130 </tr>
pankso@46 131 EOT
pankso@46 132 # Get the list of modules and link to modinfo
pankso@46 133 lsmod | grep ^[a-z] | while read MOD SIZE USED BY
pankso@46 134 do
pankso@46 135 cat << EOT
pankso@46 136 <tr>
al@406 137 <td><a href="?modinfo=$MOD">$MOD</a></td>
pankso@46 138 <td>$SIZE</td>
pankso@46 139 <td>$USED</td>
al@303 140 <td>$(echo $BY | sed s/","/" "/g)</td>
pankso@46 141 </tr>
pankso@46 142 EOT
pankso@46 143 done
pankso@66 144 table_end ;;
al@363 145 *\ lsusb\ *)
al@363 146 xhtml_header
al@363 147 vidpid="$(GET lsusb)"
al@363 148 cat << EOT
al@363 149 <div id="wrapper">
al@363 150 <h2>$(eval_gettext 'Information for USB Device $vidpid')</h2>
al@363 151 <p>$(gettext 'Detailed information about specified device.')</p>
al@363 152 EOT
al@363 153 lsusb_table
al@363 154 cat << EOT
al@363 155 </div>
al@363 156 <pre>$(lsusb -vd $vidpid | syntax_highlighter lsusb)</pre>
al@363 157 EOT
al@363 158 ;;
al@363 159 *\ lspci\ *)
al@363 160 xhtml_header
al@363 161 slot="$(GET lspci)"
al@363 162 cat << EOT
al@363 163 <div id="wrapper">
al@363 164 <h2>$(eval_gettext 'Information for PCI Device $slot')</h2>
al@363 165 <p>$(gettext 'Detailed information about specified device.')</p>
al@363 166 EOT
al@363 167 lspci_table
al@363 168 cat << EOT
al@363 169 </div>
al@363 170 <pre>$(lspci -vs $slot | syntax_highlighter lspci)</pre>
al@363 171 EOT
al@363 172 ;;
pankso@66 173 *)
pascal@217 174 [ -n "$(GET brightness)" ] &&
pascal@262 175 echo -n $(GET brightness) > /sys/devices/virtual/backlight/$(GET dev)/brightness
al@292 176
pankso@66 177 #
pankso@66 178 # Default to summary with mounted filesystem, loaded modules
pankso@66 179 #
pankso@66 180 xhtml_header
pankso@66 181 cat << EOT
pankso@66 182 <div id="wrapper">
al@303 183 <h2>$(gettext 'Drivers &amp; Devices')</h2>
al@303 184 <p>$(gettext 'Manage your computer hardware')</p>
pascal@219 185 </div>
pascal@219 186 <div>
al@406 187 <a class="button" href="?modules">
al@303 188 <img src="$IMAGES/tux.png" />$(gettext 'Kernel modules')</a>
al@406 189 <a class="button" href="?detect">
al@303 190 <img src="$IMAGES/monitor.png" />$(gettext 'Detect PCI/USB')</a>
psychomaniak@407 191 <a class="button" href="?tazx">
psychomaniak@407 192 <img src="$IMAGES/terminal.png" />$(gettext 'Auto-install Xorg video driver')</a>
pascal@219 193 </div>
pascal@219 194
pascal@217 195 EOT
pascal@219 196 if [ -n "$(ls /proc/acpi/battery/*/info 2> /dev/null)" ]; then
pascal@219 197 echo "<table>"
pascal@219 198 for dev in /proc/acpi/battery/*; do
pascal@219 199 grep ^present $dev/info | grep -q yes || continue
pascal@219 200 design=$(sed '/design capacity:/!d;s/[^0-9]*\([0-9]*\).*/\1/' < $dev/info)
pascal@219 201 remain=$(sed '/remaining capacity/!d;s/[^0-9]*\([0-9]*\).*/\1/' < $dev/state)
pascal@219 202 rate=$(sed '/present rate/!d;s/[^0-9]*\([0-9]*\).*/\1/' < $dev/state)
pascal@219 203 full=$(sed '/last full capacity/!d;s/[^0-9]*\([0-9]*\).*/\1/' < $dev/info)
al@303 204 warning=$(sed '/design capacity warning/!d;s/[^0-9]*\([0-9]*\).*/\1/' < $dev/info)
al@303 205 low=$(sed '/design capacity low/!d;s/[^0-9]*\([0-9]*\).*/\1/' < $dev/info)
al@303 206 state=$(sed '/charging state/!d;s/\([^:]*:[ ]\+\)\([a-z]\+\)/\2/' < $dev/state)
al@303 207
al@303 208 rempct=$(( $remain * 100 / $full ))
pascal@219 209 cat << EOT
pascal@219 210 <tr>
pascal@219 211 <td><img src="$IMAGES/battery.png" />
al@303 212 $(gettext 'Battery') $(grep "^battery type" $dev/info | sed 's/.*: *//')
pascal@219 213 $(grep "^design capacity:" $dev/info | sed 's/.*: *//') </td>
al@303 214 <td>$(gettext 'health') $(( (100*$full)/$design))%</td>
al@303 215 <td class="meter"><meter min="0" max="$full" value="$remain" low="$low"
al@303 216 high="$warning" optimum="$full"></meter>
al@303 217 <span>
pascal@219 218 EOT
al@303 219 case "$state" in
al@303 220 "discharging")
al@303 221 remtime=$(( $remain * 60 / $rate ))
al@303 222 remtimef=$(printf "%d:%02d" $(($remtime/60)) $(($remtime%60)))
al@303 223 eval_gettext 'Discharging $rempct% - $remtimef' ;;
al@303 224 "charging")
al@303 225 remtime=$(( ($full - $remain) * 60 / $rate ))
al@303 226 remtimef=$(printf "%d:%02d" $(($remtime/60)) $(($remtime%60)))
al@303 227 eval_gettext 'Charging $rempct% - $remtimef' ;;
al@303 228 "charged")
al@303 229 gettext 'Charged 100%' ;;
al@303 230 esac
al@303 231 echo '</span></td></tr>'
pascal@219 232 done
pascal@219 233 echo "</table>"
pascal@219 234 fi
al@303 235
pascal@262 236 if [ -n "$(ls /sys/devices/virtual/thermal/*/temp 2> /dev/null)" ]; then
al@303 237 echo -n '<p>'; gettext 'Temperature:'
pascal@262 238 for temp in /sys/devices/virtual/thermal/*/temp; do
pascal@262 239 awk '{ print $1/1000 }' < $temp
pascal@219 240 done
al@303 241 echo '</p>'
pascal@219 242 fi
al@303 243
pascal@262 244 if [ -n "$(ls /sys/devices/virtual/backlight/*/brightness 2> /dev/null)" ]; then
pascal@217 245 cat <<EOT
al@406 246 <form method="get" action="">
pascal@217 247 EOT
pascal@262 248 for dev in /sys/devices/virtual/backlight/*/brightness ; do
pascal@262 249 name=$(echo $dev | sed 's|.*/backlight/\([^/]*\).*|\1|')
pascal@217 250 cat <<EOT
pascal@217 251 <input type="hidden" name="dev" value="$name" />
al@303 252 $(gettext 'Brightness') \
pascal@263 253 $(sed 's/.*\.//;s/_*$//' < /sys/devices/virtual/backlight/$name/device/path):
al@405 254 <select class="button" name="brightness" onchange="submit();">
pascal@217 255 EOT
pascal@262 256 max=$(cat /sys/devices/virtual/backlight/$name/max_brightness)
pascal@262 257 for i in $(seq 0 $max); do
pascal@262 258 echo -n "<option value=\"$i\""
pascal@262 259 [ $i -eq $(cat /sys/devices/virtual/backlight/$name/actual_brightness) ] &&
pascal@262 260 echo -n " selected=\"selected\""
pascal@262 261 echo "> $(( (($i + 1) * 100) / ($max + 1) ))% </option>"
pascal@262 262 done
pascal@217 263 cat <<EOT
pascal@217 264 </select>
pascal@217 265 EOT
pascal@217 266 done
pascal@217 267 cat << EOT
pascal@217 268 </form>
pascal@217 269 EOT
pascal@217 270 fi
pascal@217 271 cat << EOT
pankso@152 272
al@363 273
al@363 274 <h3 id="disk">$(gettext 'Filesystem usage statistics')</h3>
al@363 275
pankso@152 276 <pre>
al@363 277 $(fdisk -l | fgrep Disk)
al@363 278 </pre>
pankso@66 279 EOT
al@303 280
al@303 281
pankso@153 282 #
pascal@376 283 # Loop device management actions
pascal@376 284 #
pascal@376 285 device=$(GET loopdev)
pascal@376 286 lib crypto $device
pascal@376 287 case "$device" in
pascal@376 288 /dev/loop*)
pascal@376 289 set -- $(losetup | grep ^$device:)
pascal@376 290 [ -n "$3" ] && losetup -d $device
pascal@376 291 ro=""
pascal@376 292 [ -n "$(GET readonly)" ] && ro="-r"
pascal@376 293 file="$(GET backingfile)"
pascal@376 294 [ -n "$file" ] && losetup -o $(GET offset) $ro $device $file
pascal@376 295 esac
pascal@376 296 #
al@303 297 # Disk stats and management (mount, umount, check)
pankso@153 298 #
pascal@344 299 device=$(GET device)
pascal@373 300 lib crypto $device
pascal@344 301 case "$device" in
pascal@344 302 *[\;\`\&\|\$]*) ;;
pascal@344 303 mount\ *)
pascal@375 304 ro=""
pascal@375 305 [ -n "$(GET readonly)" ] && ro="-r"
pascal@389 306 mntdir="$(GET mountpoint)"
pascal@389 307 [ -d "$mntdir" ] || mkdir -p "$mntdir"
pascal@389 308 $device $ro "$mntdir";;
pascal@344 309 umount\ *|swapon\ *|swapoff\ *)
pascal@344 310 $device ;;
pascal@344 311 esac
al@303 312 cat << EOT
al@406 313 <form method="get" action="#mount">
al@363 314 <table id="mount" class="zebra outbox nowrap">
al@303 315 EOT
pankso@153 316 df_thead
al@303 317 echo '<tbody>'
pascal@376 318 for fs in $(blkid | sed 's/:.*//')
pankso@153 319 do
pascal@373 320 set -- $(df -h | grep "^$fs ")
pascal@343 321 size=$2
pascal@343 322 used=$3
pascal@343 323 av=$4
pascal@375 324 grep "^$fs " /proc/mounts | grep -q "[, ]ro[, ]" &&
pascal@375 325 av="<del>$av</del>"
pascal@343 326 pct=$5
pascal@343 327 mp=$6
pascal@344 328 action="mount"
pascal@344 329 [ -n "$mp" ] && action="umount"
pascal@344 330 type=$(blkid $fs | sed '/TYPE=/!d;s/.*TYPE="\([^"]*\).*/\1/')
pascal@344 331 [ "$type" == "swap" ] && action="swapon"
pascal@344 332 if grep -q "^$fs " /proc/swaps; then
pascal@344 333 action="swapoff"
pascal@344 334 set -- $(grep "^$fs " /proc/swaps)
pascal@364 335 size=$(blk2h $(($3*2)))
pascal@364 336 used=$(blk2h $(($4*2)))
pascal@364 337 av=$(blk2h $((2*($3-$4))))
pascal@344 338 pct=$(((100*$4)/$3))%
pascal@344 339 mp=swap
pascal@344 340 fi
pascal@364 341 [ -z "$size" ] &&
pascal@364 342 size="$(blk2h $(cat /sys/block/${fs#/dev/}/size /sys/block/*/${fs#/dev/}/size))"
pascal@373 343 img="harddisk.png"
pascal@373 344 case "$(cat /sys/block/${fs#/dev/}/removable 2> /dev/null ||
pascal@373 345 cat /sys/block/${fs:5:3}/removable 2> /dev/null)" in
pascal@373 346 1) img="floppy.png" ;;
pascal@373 347 esac
pascal@373 348 case "$(cat /sys/block/${fs#/dev/}/ro 2> /dev/null ||
pascal@373 349 cat /sys/block/${fs:5:3}/ro 2> /dev/null)" in
pascal@373 350 1) img="tazlito.png" ;;
pascal@373 351 esac
pascal@373 352 [ -s ".$IMAGES/$img" ] || img="harddisk.png"
pascal@343 353 cat << EOT
pankso@153 354 <tr>
pascal@344 355 <td><input type="radio" name="device" value="$action $fs" />
pascal@373 356 <img src="$IMAGES/$img" />${fs#/dev/}</td>
pascal@343 357 <td>$(blkid $fs | sed '/LABEL=/!d;s/.*LABEL="\([^"]*\).*/\1/')</td>
pascal@344 358 <td>$type</td>
pankso@153 359 <td>$size</td>
pankso@153 360 <td>$av</td>
pascal@343 361 EOT
pascal@343 362 if [ -n "$pct" ]; then
pascal@343 363 cat << EOT
al@303 364 <td class="meter"><meter min="0" max="100" value="${pct%%%}" low="70"
al@303 365 high="90" optimum="10"></meter>
al@303 366 <span>$used - $pct</span>
al@303 367 </td>
pascal@343 368 EOT
pascal@343 369 else
pascal@343 370 cat << EOT
al@363 371 <td> </td>
pascal@343 372 EOT
pascal@343 373 fi
pascal@343 374 cat << EOT
pankso@153 375 <td>$mp</td>
pascal@344 376 <td>$(blkid $fs | sed '/UUID=/!d;s/.*UUID="\([^"]*\).*/\1/')</td>
pankso@153 377 </tr>
pankso@153 378 EOT
pankso@153 379 done
al@303 380 cat << EOT
al@303 381 </tbody>
al@303 382 </table>
pascal@373 383 $(lib crypto input)
pascal@344 384 <input type="submit" value="mount / umount" /> -
pascal@375 385 new mount point <input type=text" name="mountpoint" value="/media/usbdisk" /> -
pascal@375 386 <input type="checkbox" name="readonly"> read-only
pascal@344 387 </form>
al@363 388
al@363 389
pascal@345 390 <h3>$(gettext 'Filesystems table')</h3>
al@363 391 EOT
al@363 392
al@363 393 grep -v '^#' /etc/fstab | awk 'BEGIN{print "<table class=\"zebra outbox\">\
pascal@376 394 <thead><tr><td>$(gettext 'Disk')</td><td>$(gettext 'Mount point')</td><td>\
pascal@376 395 $(gettext 'Type')</td><td>$(gettext 'Options')</td><td>\
pascal@376 396 $(gettext 'Freq')</td><td>$(gettext 'Pass')</td></thead><tbody>"}\
pascal@376 397 {print "<tr><td>"$1"</td><td>"$2\
al@363 398 "</td><td>"$3"</td><td>"$4"</td><td>"$5"</td><td>"$6"</td></tr>"}
al@363 399 END{print "</tbody></table>"}'
al@363 400
al@363 401 cat << EOT
al@406 402 <a class="button" href="index.cgi?file=/etc/fstab&amp;action=edit">
pascal@345 403 <img src="$IMAGES/edit.png" />$(gettext 'Manual Edit')</a>
al@363 404
al@363 405
pascal@373 406 <h3>$(gettext 'Loop devices')</h3>
pascal@373 407 EOT
pascal@373 408 #
pascal@376 409 # Loop device management gui
pascal@373 410 #
pascal@373 411 cat << EOT
al@406 412 <form method="get" action="#loop">
pascal@373 413 <table id="loop" class="zebra outbox nowrap">
pascal@373 414 <thead>
pascal@373 415 <tr><td>Device</td><td>Backing file</td><td>Access</td><td>Offset</td></tr>
pascal@373 416 </thead>
pascal@373 417 <tbody>
pascal@373 418 EOT
pascal@373 419 for loop in $(ls /dev/loop[0-9]*); do
pascal@373 420 case "$(cat /sys/block/${loop#/dev/}/ro 2> /dev/null)" in
pascal@373 421 0) ro="read/write" ;;
pascal@373 422 1) ro="read&nbsp;only" ;;
pascal@373 423 *) ro="" ;;
pascal@373 424 esac
pascal@373 425 set -- $(losetup | grep ^$loop:) $ro
pascal@373 426 cat << EOT
pascal@373 427 <tr>
pascal@373 428 <td><input type="radio" name="loopdev" value="$loop" />
pascal@373 429 <img src="$IMAGES/harddisk.png" />${loop#/dev/}</td>
pascal@373 430 <td>$3</td><td align="center">$4</td><td align="right">$2</td>
pascal@373 431 </tr>
pascal@373 432 EOT
pascal@373 433 done
pascal@373 434 cat << EOT
pascal@373 435 </tbody>
pascal@373 436 </table>
pascal@373 437 $(lib crypto input)
pascal@373 438 <input type="submit" value="Setup" /> -
pascal@375 439 new backing file <input type="text" name="backingfile" /> -
pascal@373 440 offset in bytes <input type="text" name="offset" size="8" value="0" /> -
pascal@375 441 <input type="checkbox" name="readonly"> read-only
pascal@373 442 </form>
pascal@373 443 EOT
pascal@373 444
pascal@373 445 cat << EOT
al@363 446 <h3>$(gettext 'System memory')</h3>
al@303 447 EOT
al@303 448
al@363 449 echo "<table class=\"zebra outbox\"><thead><tr><td>&nbsp;</td><td>total</td>\
al@363 450 <td>used</td><td>free</td><td>shared</td><td>buffers</td></tr></thead><tbody>"
al@363 451 freem=$(free -m)
al@363 452 echo "$freem" | grep Mem: | awk '{print "<tr><td>"$1"</td><td>"$2"</td><td>"$3\
al@363 453 "</td><td>"$4"</td><td>"$5"</td><td>"$6"</td></tr>"}'
al@363 454 echo "$freem" | grep buffers: | awk '{print "<tr><td>"$1 $2"</td><td>&nbsp;</td>\
al@363 455 <td>"$3"</td><td>"$4"</td><td>&nbsp;</td><td>&nbsp;</td></tr>"}'
al@363 456 echo "$freem" | grep Swap: | awk '{print "<tr><td>"$1"</td><td>"$2"</td><td>"$3\
al@363 457 "</td><td>"$4"</td><td>&nbsp;</td><td>&nbsp;</td></tr></tbody></table>"}'
al@303 458
al@303 459 cat << EOT
al@303 460 <h3>lspci</h3>
al@363 461 $(lspci_table)
al@303 462
al@303 463 <h3>lsusb</h3>
al@363 464 $(lsusb_table)
al@303 465 EOT
al@363 466 ;;
pankso@46 467 esac
pankso@46 468
pankso@46 469 xhtml_footer
pankso@46 470 exit 0