This required moving files around in the repository and shifting from a master.adoc structure to _topic_map.yml, etc. README and Makefile modified slightly to reflect new build process
497 lines
No EOL
23 KiB
HTML
497 lines
No EOL
23 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta content="IE=edge" http-equiv="X-UA-Compatible">
|
|
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
|
<title>Defensive Coding Guide | Defensive Coding Guide | Specific Programming Tasks | File Descriptor Management</title>
|
|
|
|
<!-- Bootstrap -->
|
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
|
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css">
|
|
|
|
<!-- Overpass Font -->
|
|
<link rel="stylesheet" href="https://overpass-30e2.kxcdn.com/overpass.css">
|
|
|
|
<link href="../../../master/_stylesheets/asciibinder.css" rel="stylesheet" />
|
|
|
|
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
|
|
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
|
<!--[if lt IE 9]>
|
|
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
|
|
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
|
|
<![endif]-->
|
|
|
|
<link href="../../../master/_images/favicon32x32.png" rel="shortcut icon" type="text/css">
|
|
<!--[if IE]><link rel="shortcut icon" href="../../../master/_images/favicon.ico"><![endif]-->
|
|
<meta content="AsciiBinder" name="application-name">
|
|
</head>
|
|
<body>
|
|
<div class="navbar navbar-default" role="navigation">
|
|
<div class="container-fluid">
|
|
<div class="navbar-header">
|
|
<a class="navbar-brand" href="https://docs.fedoraproject.org/"><img alt="Fedora Documentation" src="../../../master/_images/fedora.svg"></a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="container">
|
|
<p class="toggle-nav visible-xs pull-left">
|
|
<button class="btn btn-default btn-sm" type="button" data-toggle="offcanvas">Toggle nav</button>
|
|
</p>
|
|
<ol class="breadcrumb">
|
|
<li class="sitename">
|
|
<a href="../../../index.html">Home</a>
|
|
</li>
|
|
<li class="hidden-xs active">
|
|
<a href="../../en-US/index.html">Defensive Coding Guide </a>
|
|
</li>
|
|
<li class="hidden-xs active">
|
|
<a href="../../en-US/index.html">Defensive Coding Guide</a>
|
|
</li>
|
|
<li class="hidden-xs active"><a href="../../en-US/tasks/Tasks-Library_Design.html">Specific Programming Tasks</a></li>
|
|
<li class="hidden-xs active">
|
|
File Descriptor Management
|
|
</li>
|
|
</ol>
|
|
<div class="row row-offcanvas row-offcanvas-left">
|
|
<div class="col-xs-8 col-sm-3 col-md-3 sidebar sidebar-offcanvas">
|
|
<ul class="nav nav-sidebar">
|
|
<li class="nav-header">
|
|
<a class="" href="#" data-toggle="collapse" data-target="#topicGroup0">
|
|
<span id="tgSpan0" class="fa fa-angle-down"></span>Defensive Coding Guide
|
|
</a>
|
|
<ul id="topicGroup0" class="collapse in list-unstyled">
|
|
<li><a class="" href="../../en-US/index.html">Book Information</a></li>
|
|
<li class="nav-header">
|
|
<a class="" href="#" data-toggle="collapse" data-target="#topicSubGroup-0-1">
|
|
<span id="sgSpan-0-1" class="fa fa-caret-right"></span> Programming Languages
|
|
</a>
|
|
<ul id="topicSubGroup-0-1" class="nav-tertiary list-unstyled collapse">
|
|
<li><a class="" href="../../en-US/programming-languages/C.html">The C Programming Language</a></li>
|
|
<li><a class="" href="../../en-US/programming-languages/CXX.html">The C++ Programming Language</a></li>
|
|
<li><a class="" href="../../en-US/programming-languages/Java.html">The Java Programming Language</a></li>
|
|
<li><a class="" href="../../en-US/programming-languages/Python.html">The Python Programming Language</a></li>
|
|
<li><a class="" href="../../en-US/programming-languages/Shell.html">Shell Programming and bash</a></li>
|
|
<li><a class="" href="../../en-US/programming-languages/Go.html">The Go Programming Language</a></li>
|
|
<li><a class="" href="../../en-US/programming-languages/Vala.html">The Vala Programming Language</a></li>
|
|
</ul>
|
|
</li>
|
|
<li class="nav-header">
|
|
<a class="" href="#" data-toggle="collapse" data-target="#topicSubGroup-0-2">
|
|
<span id="sgSpan-0-2" class="fa fa-caret-down"></span> Specific Programming Tasks
|
|
</a>
|
|
<ul id="topicSubGroup-0-2" class="nav-tertiary list-unstyled collapse in">
|
|
<li><a class="" href="../../en-US/tasks/Tasks-Library_Design.html">Library Design</a></li>
|
|
<li><a class=" active" href="../../en-US/tasks/Tasks-Descriptors.html">File Descriptor Management</a></li>
|
|
<li><a class="" href="../../en-US/tasks/Tasks-File_System.html">File System Manipulation</a></li>
|
|
<li><a class="" href="../../en-US/tasks/Tasks-Temporary_Files.html">Temporary Files</a></li>
|
|
<li><a class="" href="../../en-US/tasks/Tasks-Processes.html">Processes</a></li>
|
|
<li><a class="" href="../../en-US/tasks/Tasks-Serialization.html">Serialization and Deserialization</a></li>
|
|
<li><a class="" href="../../en-US/tasks/Tasks-Cryptography.html">Cryptography</a></li>
|
|
<li><a class="" href="../../en-US/tasks/Tasks-Packaging.html">RPM Packaging</a></li>
|
|
</ul>
|
|
</li>
|
|
<li class="nav-header">
|
|
<a class="" href="#" data-toggle="collapse" data-target="#topicSubGroup-0-3">
|
|
<span id="sgSpan-0-3" class="fa fa-caret-right"></span> Implementing Security Features
|
|
</a>
|
|
<ul id="topicSubGroup-0-3" class="nav-tertiary list-unstyled collapse">
|
|
<li><a class="" href="../../en-US/features/Features-Authentication.html">Authentication and Authorization</a></li>
|
|
<li><a class="" href="../../en-US/features/Features-TLS.html">Transport Layer Security (TLS)</a></li>
|
|
<li><a class="" href="../../en-US/features/Features-HSM.html">Hardware Security Modules and Smart Cards</a></li>
|
|
</ul>
|
|
</li>
|
|
<li><a class="" href="../../en-US/Revision_History.html">Revision History</a></li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<div class="col-xs-12 col-sm-9 col-md-9 main">
|
|
<div class="page-header">
|
|
<h2>File Descriptor Management</h2>
|
|
</div>
|
|
<div id="preamble">
|
|
<div class="sectionbody">
|
|
<div class="paragraph">
|
|
<p>File descriptors underlie all input/output mechanisms offered by
|
|
the system. They are used to implementation the <code>FILE
|
|
*</code>-based functions found in
|
|
<code><stdio.h></code>, and all the file and network
|
|
communication facilities provided by the Python and Java
|
|
environments are eventually implemented in them.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>File descriptors are small, non-negative integers in userspace,
|
|
and are backed on the kernel side with complicated data structures
|
|
which can sometimes grow very large.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="sect1">
|
|
<h2 id="closing-descriptors"><a class="anchor" href="#closing-descriptors"></a>Closing Descriptors</h2>
|
|
<div class="sectionbody">
|
|
<div class="paragraph">
|
|
<p>If a descriptor is no longer used by a program and is not closed
|
|
explicitly, its number cannot be reused (which is problematic in
|
|
itself, see <a href="#sect-Defensive_Coding-Tasks-Descriptors-Limit">Dealing with the <code>select</code> Limit</a>), and
|
|
the kernel resources are not freed. Therefore, it is important
|
|
to close all descriptors at the earliest point in time
|
|
possible, but not earlier.</p>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="error-handling-during-descriptor-close"><a class="anchor" href="#error-handling-during-descriptor-close"></a>Error Handling during Descriptor Close</h3>
|
|
<div class="paragraph">
|
|
<p>The <code>close</code> system call is always
|
|
successful in the sense that the passed file descriptor is
|
|
never valid after the function has been called. However,
|
|
<code>close</code> still can return an error, for
|
|
example if there was a file system failure. But this error is
|
|
not very useful because the absence of an error does not mean
|
|
that all caches have been emptied and previous writes have
|
|
been made durable. Programs which need such guarantees must
|
|
open files with <code>O_SYNC</code> or use
|
|
<code>fsync</code> or <code>fdatasync</code>, and
|
|
may also have to <code>fsync</code> the directory
|
|
containing the file.</p>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="closing-descriptors-and-race-conditions"><a class="anchor" href="#closing-descriptors-and-race-conditions"></a>Closing Descriptors and Race Conditions</h3>
|
|
<div class="paragraph">
|
|
<p>Unlike process IDs, which are recycle only gradually, the
|
|
kernel always allocates the lowest unused file descriptor when
|
|
a new descriptor is created. This means that in a
|
|
multi-threaded program which constantly opens and closes file
|
|
descriptors, descriptors are reused very quickly. Unless
|
|
descriptor closing and other operations on the same file
|
|
descriptor are synchronized (typically, using a mutex), there
|
|
will be race conditons and I/O operations will be applied to
|
|
the wrong file descriptor.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>Sometimes, it is necessary to close a file descriptor
|
|
concurrently, while another thread might be about to use it in
|
|
a system call. In order to support this, a program needs to
|
|
create a single special file descriptor, one on which all I/O
|
|
operations fail. One way to achieve this is to use
|
|
<code>socketpair</code>, close one of the descriptors,
|
|
and call <code>shutdown(fd, SHUTRDWR)</code> on the
|
|
other.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>When a descriptor is closed concurrently, the program does not
|
|
call <code>close</code> on the descriptor. Instead it
|
|
program uses <code>dup2</code> to replace the
|
|
descriptor to be closed with the dummy descriptor created
|
|
earlier. This way, the kernel will not reuse the descriptor,
|
|
but it will carry out all other steps associated with calling
|
|
a descriptor (for instance, if the descriptor refers to a
|
|
stream socket, the peer will be notified).</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>This is just a sketch, and many details are missing.
|
|
Additional data structures are needed to determine when it is
|
|
safe to really close the descriptor, and proper locking is
|
|
required for that.</p>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="lingering-state-after-close"><a class="anchor" href="#lingering-state-after-close"></a>Lingering State after Close</h3>
|
|
<div class="paragraph">
|
|
<p>By default, closing a stream socket returns immediately, and
|
|
the kernel will try to send the data in the background. This
|
|
means that it is impossible to implement accurate accounting
|
|
of network-related resource utilization from userspace.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>The <code>SO_LINGER</code> socket option alters the
|
|
behavior of <code>close</code>, so that it will return
|
|
only after the lingering data has been processed, either by
|
|
sending it to the peer successfully, or by discarding it after
|
|
the configured timeout. However, there is no interface which
|
|
could perform this operation in the background, so a separate
|
|
userspace thread is needed for each <code>close</code>
|
|
call, causing scalability issues.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>Currently, there is no application-level countermeasure which
|
|
applies universally. Mitigation is possible with
|
|
<strong class="application">iptables</strong> (the
|
|
<code>connlimit</code> match type in particular) and
|
|
specialized filtering devices for denial-of-service network
|
|
traffic.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>These problems are not related to the
|
|
<code>TIME_WAIT</code> state commonly seen in
|
|
<strong class="application">netstat</strong> output. The kernel
|
|
automatically expires such sockets if necessary.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="sect1">
|
|
<h2 id="sect-Defensive_Coding-Tasks-Descriptors-Child_Processes"><a class="anchor" href="#sect-Defensive_Coding-Tasks-Descriptors-Child_Processes"></a>Preventing File Descriptor Leaks to Child Processes</h2>
|
|
<div class="sectionbody">
|
|
<div class="paragraph">
|
|
<p>Child processes created with <code>fork</code> share
|
|
the initial set of file descriptors with their parent
|
|
process. By default, file descriptors are also preserved if
|
|
a new process image is created with <code>execve</code>
|
|
(or any of the other functions such as <code>system</code>
|
|
or <code>posix_spawn</code>).</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>Usually, this behavior is not desirable. There are two ways to
|
|
turn it off, that is, to prevent new process images from
|
|
inheriting the file descriptors in the parent process:</p>
|
|
</div>
|
|
<div class="ulist">
|
|
<ul>
|
|
<li>
|
|
<p>Set the close-on-exec flag on all newly created file
|
|
descriptors. Traditionally, this flag is controlled by the
|
|
<code>FD_CLOEXEC</code> flag, using
|
|
<code>F_GETFD</code> and <code>F_SETFD</code>
|
|
operations of the <code>fcntl</code> function.</p>
|
|
<div class="paragraph">
|
|
<p>However, in a multi-threaded process, there is a race
|
|
condition: a subprocess could have been created between the
|
|
time the descriptor was created and the
|
|
<code>FD_CLOEXEC</code> was set. Therefore, many system
|
|
calls which create descriptors (such as
|
|
<code>open</code> and <code>openat</code>)
|
|
now accept the <code>O_CLOEXEC</code> flag
|
|
(<code>SOCK_CLOEXEC</code> for
|
|
<code>socket</code> and
|
|
<code>socketpair</code>), which cause the
|
|
<code>FD_CLOEXEC</code> flag to be set for the file
|
|
descriptor in an atomic fashion. In addition, a few new
|
|
systems calls were introduced, such as
|
|
<code>pipe2</code> and <code>dup3</code>.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>The downside of this approach is that every descriptor needs
|
|
to receive special treatment at the time of creation,
|
|
otherwise it is not completely effective.</p>
|
|
</div>
|
|
</li>
|
|
<li>
|
|
<p>After calling <code>fork</code>, but before creating
|
|
a new process image with <code>execve</code>, all
|
|
file descriptors which the child process will not need are
|
|
closed.</p>
|
|
<div class="paragraph">
|
|
<p>Traditionally, this was implemented as a loop over file
|
|
descriptors ranging from <code>3</code> to
|
|
<code>255</code> and later <code>1023</code>.
|
|
But this is only an approximation because it is possible to
|
|
create file descriptors outside this range easily (see <a href="#sect-Defensive_Coding-Tasks-Descriptors-Limit">Dealing with the <code>select</code> Limit</a>).
|
|
Another approach reads <code>/proc/self/fd</code>
|
|
and closes the unexpected descriptors listed there, but this
|
|
approach is much slower.</p>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>At present, environments which care about file descriptor
|
|
leakage implement the second approach. OpenJDK 6 and 7
|
|
are among them.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="sect1">
|
|
<h2 id="sect-Defensive_Coding-Tasks-Descriptors-Limit"><a class="anchor" href="#sect-Defensive_Coding-Tasks-Descriptors-Limit"></a>Dealing with the <code>select</code> Limit</h2>
|
|
<div class="sectionbody">
|
|
<div class="paragraph">
|
|
<p>By default, a user is allowed to open only 1024 files in a
|
|
single process, but the system administrator can easily change
|
|
this limit (which is necessary for busy network servers).
|
|
However, there is another restriction which is more difficult to
|
|
overcome.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>The <code>select</code> function only supports a
|
|
maximum of <code>FD_SETSIZE</code> file descriptors
|
|
(that is, the maximum permitted value for a file descriptor
|
|
is <code>FD_SETSIZE - 1</code>, usually 1023.) If a
|
|
process opens many files, descriptors may exceed such
|
|
limits. It is impossible to query such descriptors using
|
|
<code>select</code>.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>If a library which creates many file descriptors is used in
|
|
the same process as a library which uses
|
|
<code>select</code>, at least one of them needs to
|
|
be changed.
|
|
Calls to <code>select</code> can be replaced with
|
|
calls to <code>poll</code> or another event handling
|
|
mechanism. Replacing the <code>select</code> function
|
|
is the recommended approach.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>Alternatively, the library with high descriptor usage can
|
|
relocate descriptors above the <code>FD_SETSIZE</code>
|
|
limit using the following procedure.</p>
|
|
</div>
|
|
<div class="ulist">
|
|
<ul>
|
|
<li>
|
|
<p>Create the file descriptor <code>fd</code> as
|
|
usual, preferably with the <code>O_CLOEXEC</code>
|
|
flag.</p>
|
|
</li>
|
|
<li>
|
|
<p>Before doing anything else with the descriptor
|
|
<code>fd</code>, invoke:</p>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="content">
|
|
<pre> int newfd = fcntl(fd, F_DUPFD_CLOEXEC, (long)FD_SETSIZE);</pre>
|
|
</div>
|
|
</div>
|
|
<div class="ulist">
|
|
<ul>
|
|
<li>
|
|
<p>Check that <code>newfd</code> result is
|
|
non-negative, otherwise close <code>fd</code> and
|
|
report an error, and return.</p>
|
|
</li>
|
|
<li>
|
|
<p>Close <code>fd</code> and continue to use
|
|
<code>newfd</code>.</p>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>The new descriptor has been allocated above the
|
|
<code>FD_SETSIZE</code>. Even though this algorithm
|
|
is racy in the sense that the <code>FD_SETSIZE</code>
|
|
first descriptors could fill up, a very high degree of
|
|
physical parallelism is required before this becomes a problem.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div id="bottom" class="text-muted py-3" >
|
|
<div class="foot">
|
|
<div class="container">
|
|
<div class="row footerlinks">
|
|
<div class="col-sm-3 col-xs-6 widget">
|
|
<h3 class="widget-title">About</h3>
|
|
<div class="widget-body">
|
|
<dl>
|
|
<dd><a href="https://fedoraproject.org/wiki/Overview">About Fedora</a></dd>
|
|
<dd><a href="https://getfedora.org/en/sponsors">Sponsors</a></dd>
|
|
<dd><a href="https://fedoramagazine.org">Fedora Magazine</a></dd>
|
|
<dd><a href="https://fedoraproject.org/wiki/Legal:Main#Legal">Legal</a></dd>
|
|
</dl>
|
|
<ul class="list-inline">
|
|
<li>
|
|
<a href="https://www.facebook.com/TheFedoraProject" class="btn-social btn-outline"><i class="fa fa-fw fa-facebook"></i></a>
|
|
</li>
|
|
<li>
|
|
<a href="https://plus.google.com/112917221531140868607" class="btn-social btn-outline"><i class="fa fa-fw fa-google-plus"></i></a>
|
|
</li>
|
|
<li>
|
|
<a href="https://twitter.com/fedora" class="btn-social btn-outline"><i class="fa fa-fw fa-twitter"></i></a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-sm-3 col-xs-6 widget">
|
|
<h3 class="widget-title uppercase">Download</h3>
|
|
<div class="widget-body">
|
|
<dl>
|
|
<dd><a href="https://getfedora.org/en/workstation/download">Get Fedora Workstation</a></dd>
|
|
<dd><a href="https://getfedora.org/en/server/download">Get Fedora Server</a></dd>
|
|
<dd><a href="https://getfedora.org/en/atomic/download">Get Fedora Atomic</a></dd>
|
|
<dd><a href="https://spins.fedoraproject.org">Fedora Spins</a></dd>
|
|
<dd><a href="https://labs.fedoraproject.org">Fedora Labs</a></dd>
|
|
<dd><a href="https://arm.fedoraproject.org">Fedora ARM<span class="sup">®</span></a></dd>
|
|
<dd><a href="https://alt.fedoraproject.org/">Alternative Downloads</a></dd>
|
|
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-sm-3 col-xs-6 widget">
|
|
<h3 class="widget-title">Support</h3>
|
|
<div class="widget-body">
|
|
<dl>
|
|
<dd><a href="https://fedoraproject.org/wiki/Communicating_and_getting_help">Get Help</a></dd>
|
|
<dd><a href="https://ask.fedoraproject.org/">Ask Fedora</a></dd>
|
|
<dd><a href="https://fedoraproject.org/wiki/Common_F27_bugs">Common Bugs</a></dd>
|
|
<dd><a href="https://developer.fedoraproject.org/">Fedora Developer Portal</a></dd>
|
|
<dd><a href="https://docs.fedoraproject.org/f27/install-guide/index.html">Installation Guide</a></dd>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-sm-3 col-xs-6 widget">
|
|
<h3 class="widget-title">Join</h3>
|
|
<div class="widget-body">
|
|
<dl>
|
|
<dd><a href="https://fedoraproject.org/wiki/Join">Join Fedora</a></dd>
|
|
<dd><a href="http://fedoraplanet.org">Planet Fedora</a></dd>
|
|
<dd><a href="https://fedoraproject.org/wiki/SIGs">Fedora SIGs</a></dd>
|
|
<dd><a href="https://admin.fedoraproject.org/accounts/">Fedora Account System</a></dd>
|
|
<dd><a href="https://fedoracommunity.org/">Fedora Community</a></dd>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
</div> <!-- /row of widgets -->
|
|
|
|
<div class="row">
|
|
<div class="col-md-2">
|
|
<div class="widget-body">
|
|
<a href="https://www.redhat.com/"><img class="rh-logo" src="../../../master/_images/redhat-logo.png" alt="Red Hat Logo" /></a>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-7">
|
|
<div class="widget-body">
|
|
<p class="sponsor">Fedora is sponsored by Red Hat.</p>
|
|
<p class="sponsor"><a href="https://www.redhat.com/en/technologies/linux-platforms/articles/relationship-between-fedora-and-rhel">Learn more about the relationship between Red Hat and Fedora »</a></p>
|
|
<p class="copy">© 2017 Red Hat, Inc. and others. Please send any comments or corrections to the <a href="https://pagure.io/fedora-docs/docs-fp-o">documentation team</a></p>
|
|
</div>
|
|
</div>
|
|
</div> <!-- /row of widgets -->
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
|
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
|
|
<!-- Latest compiled and minified JavaScript -->
|
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
|
|
<script src="../../../master/_javascripts/bootstrap-offcanvas.js" type="text/javascript"></script>
|
|
<script type="text/javascript">
|
|
/*<![CDATA[*/
|
|
$(document).ready(function() {
|
|
$("[id^='topicGroup']").on('show.bs.collapse', function (event) {
|
|
if (!($(event.target).attr('id').match(/^topicSubGroup/))) {
|
|
$(this).parent().find("[id^='tgSpan']").toggleClass("fa-angle-right fa-angle-down");
|
|
}
|
|
});
|
|
$("[id^='topicGroup']").on('hide.bs.collapse', function (event) {
|
|
if (!($(event.target).attr('id').match(/^topicSubGroup/))) {
|
|
$(this).parent().find("[id^='tgSpan']").toggleClass("fa-angle-right fa-angle-down");
|
|
}
|
|
});
|
|
$("[id^='topicSubGroup']").on('show.bs.collapse', function () {
|
|
$(this).parent().find("[id^='sgSpan']").toggleClass("fa-caret-right fa-caret-down");
|
|
});
|
|
$("[id^='topicSubGroup']").on('hide.bs.collapse', function () {
|
|
$(this).parent().find("[id^='sgSpan']").toggleClass("fa-caret-right fa-caret-down");
|
|
});
|
|
});
|
|
/*]]>*/
|
|
</script>
|
|
</body>
|
|
</html> |