Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
core/src/main/java/hudson/model/Computer.java
### 代码内容：
package hudson.model;

import edu.umd.cs.findbugs.annotations.OverrideMustInvoke;
import edu.umd.cs.findbugs.annotations.When;
import hudson.EnvVars;
import hudson.Extension;
import hudson.Launcher.ProcStarter;
import hudson.Util;
import hudson.cli.declarative.CLIMethod;
import hudson.cli.declarative.CLIResolver;
import hudson.console.AnnotatedLargeText;
import hudson.init.Initializer;
import hudson.model.Descriptor.FormException;
import hudson.model.Queue.FlyweightTask;
import hudson.model.labels.LabelAtom;
import hudson.model.queue.WorkUnit;
import hudson.node_monitors.NodeMonitor;
import hudson.remoting.Channel;
import hudson.remoting.VirtualChannel;
import hudson.security.ACL;
import hudson.security.AccessControlled;
import hudson.security.Permission;
import hudson.security.PermissionGroup;
import hudson.security.PermissionScope;
import hudson.slaves.AbstractCloudSlave;
import hudson.slaves.ComputerLauncher;
import hudson.slaves.ComputerListener;
import hudson.slaves.NodeProperty;
import hudson.slaves.RetentionStrategy;
import hudson.slaves.WorkspaceList;
import hudson.slaves.OfflineCause;
import hudson.slaves.OfflineCause.ByCLI;
import hudson.util.DaemonThreadFactory;
import hudson.util.EditDistance;
import hudson.util.ExceptionCatchingThreadFactory;
import hudson.util.RemotingDiagnostics;
import hudson.util.RemotingDiagnostics.HeapDump;
import hudson.util.RunList;
import hudson.util.Futures;
import hudson.util.NamingThreadFactory;
import jenkins.model.Jenkins;
import jenkins.util.ContextResettingExecutorService;
import jenkins.security.MasterToSlaveCallable;
import jenkins.security.NotReallyRoleSensitiveCallable;

import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpRedirect;
import org.kohsuke.stapler.WebMethod;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
import org.kohsuke.args4j.Option;
import org.kohsuke.stapler.interceptor.RequirePOST;

import javax.annotation.concurrent.GuardedBy;
import javax.servlet.ServletException;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ExecutionException;
import java.util.logging.LogRecord;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.nio.charset.Charset;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.Inet4Address;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import static javax.servlet.http.HttpServletResponse.*;

/**
 * Represents the running state of a remote computer that holds {@link Executor}s.
 *
 *

 * {@link Executor}s on one {@link Computer} are transparently interchangeable
 * (that is the definition of {@link Computer}).
 *
 *

 * This object is related to {@link Node} but they have some significant differences.
 * {@link Computer} primarily works as a holder of {@link Executor}s, so
 * if a {@link Node} is configured (probably temporarily) with 0 executors,
 * you won't have a {@link Computer} object for it (except for the master node,
 * which always gets its {@link Computer} in case we have no static executors and
 * we need to run a {@link FlyweightTask} - see JENKINS-7291 for more discussion.)
 *
 * Also, even if you remove a {@link Node}, it takes time for the corresponding
 * {@link Computer} to be removed, if some builds are already in progress on that
 * node. Or when the node configuration is changed, unaffected {@link Computer} object
 * remains intact, while all the {@link Node} objects will go away.
 *
 *

 * This object also serves UI (unlike {@link Node}), and can be used along with
 * {@link TransientComputerActionFactory} to add {@link Action}s to {@link Computer}s.
 *
 * @author Kohsuke Kawaguchi
 */
@ExportedBean
public /*transient*/ abstract class Computer extends Actionable implements AccessControlled, ExecutorListener {

    private final CopyOnWriteArrayList<Executor> executors = new CopyOnWriteArrayList<Executor>();
    // TODO:
    private final CopyOnWriteArrayList<OneOffExecutor> oneOffExecutors = new CopyOnWriteArrayList<OneOffExecutor>();

    private int numExecutors;

    /**
     * Contains info about reason behind computer being offline.
     */
    protected volatile OfflineCause offlineCause;

    private long connectTime = 0;

    /**
     * True if Jenkins shouldn't start new builds on this node.
     */
    private boolean temporarilyOffline;

    /**
     * {@link Node} object may be created and deleted independently
     * from this object.
     */
    protected String nodeName;

    /**
     * @see #getHostName()
     */
    private volatile String cachedHostName;
    private volatile boolean hostNameCached;

    /**
     * @see #getEnvironment()
     */
    private volatile EnvVars cachedEnvironment;

    private final WorkspaceList workspaceList = new WorkspaceList();

    protected transient List<Action> transientActions;

    protected final Object statusChangeLock = new Object();

    /**
     * Keeps track of stack traces to track the tremination requests for this computer.
     *
     * @since 1.607
     * @see Executor#resetWorkUnit(String)
     */
    private transient final List<TerminationRequest> terminatedBy = Collections.synchronizedList(new ArrayList
            <TerminationRequest>());

    /**
     * This method captures the information of a request to terminate a computer instance. Method is public as
     * it needs to be called from {@link AbstractCloudSlave} and {@link jenkins.model.Nodes}. In general you should
     * not need to call this method directly, however if implementing a custom node type or a different path
     * for removing nodes, it may make sense to call this method in order to capture the originating request.
     *
     * @since 1.607
     */
    public void recordTermination() {
        StaplerRequest request = Stapler.getCurrentRequest();
        if (request != null) {
            terminatedBy.add(new TerminationRequest(
                    String.format("Termination requested at %s by %s [id=%d] from HTTP request for %s",
                            new Date(),
                            Thread.currentThread(),
                            Thread.currentThread().getId(),
                            request.getRequestURL()
                    )
            ));
        } else {
            terminatedBy.add(new TerminationRequest(
                    String.format("Termination requested at %s by %s [id=%d]",
                            new Date(),
                            Thread.currentThread(),
                            Thread.currentThread().getId()
                    )
            ));
        }
    }

    /**
     * Returns the list of captured termination requests for this Computer. This method is used by {@link Executor}
     * to provide details on why a Computer was removed in-between work being scheduled against the {@link Executor}
     * and the {@link Executor} starting to execute the task.
     *
     * @return the (possibly empty) list of termination requests.
     * @see Executor#resetWorkUnit(String)
     * @since 1.607
     */
    public List<TerminationRequest> getTerminatedBy() {
        return new ArrayList<TerminationRequest>(terminatedBy);
    }

    public Computer(Node node) {
        setNode(node);
    }

     /**
     * Returns list of all boxes {@link ComputerPanelBox}s.
     */
    public List<ComputerPanelBox> getComputerPanelBoxs(){
        return ComputerPanelBox.all(this);
    }

    /**
     * Returns the transient {@link Action}s associated with the computer.
     */
    @SuppressWarnings("deprecation")
    public List<Action> getActions() {
    	List<Action> result = new ArrayList<Action>();
    	result.addAll(super.getActions());
    	synchronized (this) {
    		if (transientActions == null) {
    			transientActions = TransientComputerActionFactory.createAllFor(this);
    		}
    		result.addAll(transientActions);
    	}
    	return Collections.unmodifiableList(result);
    }

    @SuppressWarnings("deprecation")
    @Override
    public void addAction(Action a) {
        if(a==null) throw new IllegalArgumentException();
        super.getActions().add(a);
    }

    /**
     * This is where the log from the remote agent goes.
     * The method also creates a log directory if required.
     * @see #getLogDir(), #relocateOldLogs()
     */
    public @Nonnull File getLogFile() {
        return new File(getLogDir(),"slave.log");
    }

    /**
     * Directory where rotated slave logs are stored.
     *
     * The method also creates a log directory if required.
     *
     * @since 1.613
     */
    protected @Nonnull File getLogDir() {
        File dir = new File(Jenkins.getInstance().getRootDir(),"logs/slaves/"+nodeName);
        if (!dir.exists() && !dir.mkdirs()) {
            LOGGER.severe("Failed to create slave log directory " + dir.getAbsolutePath());
        }
        return dir;
    }

    /**
     * Gets the object that coordinates the workspace allocation on this computer.
     */
    public WorkspaceList getWorkspaceList() {
        return workspaceList;
    }

    /**
     * Gets the string representation of the slave log.
     */
    public String getLog() throws IOException {
        return Util.loadFile(getLogFile());
    }

    /**
     * Used to URL-bind {@link AnnotatedLargeText}.
     */
    public AnnotatedLargeText<Computer> getLogText() {
        return new AnnotatedLargeText<Computer>(getLogFile(), Charset.defaultCharset(), false, this);
    }

    public ACL getACL() {
        return Jenkins.getInstance().getAuthorizationStrategy().getACL(this);
    }

    public void checkPermission(Permission permission) {
        getACL().checkPermission(permission);
    }

    public boolean hasPermission(Permission permission) {
        return getACL().hasPermission(permission);
    }

    /**
     * If the computer was offline (either temporarily or not),
     * this method will return the cause.
     *
     * @return
     *      null if the system was put offline without given a cause.
     */
    @Exported
    public OfflineCause getOfflineCause() {
        return offlineCause;
    }

    /**
     * If the computer was offline (either temporarily or not),
     * this method will return the cause as a string (without user info).
     *
     * @return
     *      empty string if the system was put offline without given a cause.
     */
    @Exported
    public String getOfflineCauseReason() {
        if (offlineCause == null) {
            return "";
        }
        // fetch the localized string for "Disconnected By"
        String gsub_base = hudson.slaves.Messages.SlaveComputer_DisconnectedBy("","");
        // regex to remove commented reason base string
        String gsub1 = "^" + gsub_base + "[\\w\\W]* \\: ";
        // regex to remove non-commented reason base string
        String gsub2 = "^" + gsub_base + "[\\w\\W]*";

        String newString = offlineCause.toString().replaceAll(gsub1, "");
        return newString.replaceAll(gsub2, "");
    }

    /**
     * Gets the channel that can be used to run a program on this computer.
     *
     * @return
     *      never null when {@link #isOffline()}==false.
     */
    public abstract @Nullable VirtualChannel getChannel();

    /**
     * Gets the default charset of this computer.
     *
     * @return
     *      never null when {@link #isOffline()}==false.
     */
    public abstract Charset getDefaultCharset();

    /**
     * Gets the logs recorded by this slave.
     */
    public abstract List<LogRecord> getLogRecords() throws IOException, InterruptedException;

    /**
     * If {@link #getChannel()}==null, attempts to relaunch the slave agent.
     */
    public abstract void doLaunchSlaveAgent( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException;

    /**
     * @deprecated since 2009-01-06.  Use {@link #connect(boolean)}
     */
    @Deprecated
    public final void launch() {
        connect(true);
    }

    /**
     * Do the same as {@link #doLaunchSlaveAgent(StaplerRequest, StaplerResponse)}
     * but outside the context of serving a request.
     *
     *

     * If already connected or if this computer doesn't support proactive launching, no-op.
     * This method may return immediately
     * while the launch operation happens asynchronously.
     *
     * @see #disconnect()
     *
     * @param forceReconnect
     *      If true and a connect activity is already in progress, it will be cancelled and
     *      the new one will be started. If false, and a connect activity is already in progress, this method
     *      will do nothing and just return the pending connection operation.
     * @return
     *      A {@link Future} representing pending completion of the task. The 'completion' includes
     *      both a successful completion and a non-successful completion (such distinction typically doesn't
     *      make much sense because as soon as {@link Computer} is connected it can be disconnected by some other threads.)
     */
    public final Future<?> connect(boolean forceReconnect) {
    	connectTime = System.currentTimeMillis();
    	return _connect(forceReconnect);
    }

    /**
     * Allows implementing-classes to provide an implementation for the connect method.
     *
     *

     * If already connected or if this computer doesn't support proactive launching, no-op.
     * This method may return immediately
     * while the launch operation happens asynchronously.
     *
     * @see #disconnect()
     *
     * @param forceReconnect
     *      If true and a connect activity is already in progress, it will be cancelled and
     *      the new one will be started. If false, and a connect activity is already in progress, this method
     *      will do nothing and just return the pending connection operation.
     * @return
     *      A {@link Future} representing pending completion of the task. The 'completion' includes
     *      both a successful completion and a non-successful completion (such distinction typically doesn't
     *      make much sense because as soon as {@link Computer} is connected it can be disconnected by some other threads.)
     */
    protected abstract Future<?> _connect(boolean forceReconnect);

    /**
     * CLI command to reconnect this node.
     */
    @CLIMethod(name="connect-node")
    public void cliConnect(@Option(name="-f",usage="Cancel any currently pending connect operation and retry from scratch") boolean force) throws ExecutionException, InterruptedException {
        checkPermission(CONNECT);
        connect(force).get();
    }

    /**
     * Gets the time (since epoch) when this computer connected.
     *
     * @return The time in ms since epoch when this computer last connected.
     */
    public final long getConnectTime() {
    	return connectTime;
    }

    /**
     * Disconnect this computer.
     *
     * If this is the master, no-op. This method may return immediately
     * while the launch operation happens asynchronously.
     *
     * @param cause
     *      Object that identifies the reason the node was disconnected.
     *
     * @return
     *      {@link Future} to track the asynchronous disconnect operation.
     * @see #connect(boolean)
     * @since 1.320
     */
    public Future<?> disconnect(OfflineCause cause) {
        recordTermination();
        offlineCause = cause;
        if (Util.isOverridden(Computer.class,getClass(),"disconnect"))
            return disconnect();    // legacy subtypes that extend disconnect().

        connectTime=0;
        return Futures.precomputed(null);
    }

    /**
     * Equivalent to {@code disconnect(null)}
     *
     * @deprecated as of 1.320.
     *      Use {@link #disconnect(OfflineCause)} and specify the cause.
     */
    @Deprecated
    public Future<?> disconnect() {
        recordTermination();
        if (Util.isOverridden(Computer.class,getClass(),"disconnect",OfflineCause.class))
            // if the subtype already derives disconnect(OfflineCause), delegate to it
            return disconnect(null);

        connectTime=0;
        return Futures.precomputed(null);
    }

    /**
     * CLI command to disconnects this node.
     */
    @CLIMethod(name="disconnect-node")
    public void cliDisconnect(@Option(name="-m",usage="Record the note about why you are disconnecting this node") String cause) throws ExecutionException, InterruptedException {
        checkPermission(DISCONNECT);
        disconnect(new ByCLI(cause)).get();
    }

    /**
     * CLI command to mark the node offline.
     */
    @CLIMethod(name="offline-node")
    public void cliOffline(@Option(name="-m",usage="Record the note about why you are disconnecting this node") String cause) throws ExecutionException, InterruptedException {
        checkPermission(DISCONNECT);
        setTemporarilyOffline(true, new ByCLI(cause));
    }

    @CLIMethod(name="online-node")
    public void cliOnline() throws ExecutionException, InterruptedException {
        checkPermission(CONNECT);
        setTemporarilyOffline(false, null);
    }

    /**
     * Number of {@link Executor}s that are configured for this computer.
     *
     *

     * When this value is decreased, it is temporarily possible
     * for {@link #executors} to have a larger number than this.
     */
    // ugly name to let EL access this
    @Exported
    public int getNumExecutors() {
        return numExecutors;
    }

    /**
     * Returns {@link Node#getNodeName() the name of the node}.
     */
    public @Nonnull String getName() {
        return nodeName != null ? nodeName : "";
    }

    /**
     * True if this computer is a Unix machine (as opposed to Windows machine).
     *
     * @since 1.624
     * @return
     *      null if the computer is disconnected and therefore we don't know whether it is Unix or not.
     */
    public abstract @CheckForNull Boolean isUnix();

    /**
     * Returns the {@link Node} that this computer represents.
     *
     * @return
     *      null if the configuration has changed and the node is removed, yet the corresponding {@link Computer}
     *      is not yet gone.
     */
    public @CheckForNull Node getNode() {
        Jenkins j = Jenkins.getInstance();
        if (j == null) {
            return null;
        }
        if (nodeName == null) {
            return j;
        }
        return j.getNode(nodeName);
    }

    @Exported
    public LoadStatistics getLoadStatistics() {
        return LabelAtom.get(nodeName != null ? nodeName : Jenkins.getInstance().getSelfLabel().toString()).loadStatistics;
    }

    public BuildTimelineWidget getTimeline() {
        return new BuildTimelineWidget(getBuilds());
    }

    /**
     * {@inheritDoc}
     */
    public void taskAccepted(Executor executor, Queue.Task task) {
        // dummy implementation
    }

    /**
     * {@inheritDoc}
     */
    public void taskCompleted(Executor executor, Queue.Task task, long durationMS) {
        // dummy implementation
    }

    /**
     * {@inheritDoc}
     */
    public void taskCompletedWithProblems(Executor executor, Queue.Task task, long durationMS, Throwable problems) {
        // dummy implementation
    }

    @Exported
    public boolean isOffline() {
        return temporarilyOffline || getChannel()==null;
    }

    public final boolean isOnline() {
        return !isOffline();
    }

    /**
     * This method is called to determine whether manual launching of the slave is allowed at this point in time.
     * @return {@code true} if manual launching of the slave is allowed at this point in time.
     */
    @Exported
    public boolean isManualLaunchAllowed() {
        return getRetentionStrategy().isManualLaunchAllowed(this);
    }

    /**
     * Is a {@link #connect(boolean)} operation in progress?
     */
    public abstract boolean isConnecting();

    /**
     * Returns true if this computer is supposed to be launched via JNLP.
     * @deprecated since 2008-05-18.
     *     See {@linkplain #isLaunchSupported()} and {@linkplain ComputerLauncher}
     */
    @Exported
    @Deprecated
    public boolean isJnlpAgent() {
        return false;
    }

    /**
     * Returns true if this computer can be launched by Hudson proactively and automatically.
     *
     *

     * For example, JNLP slaves return {@code false} from this, because the launch process
     * needs to be initiated from the slave side.
     */
    @Exported
    public boolean isLaunchSupported() {
        return true;
    }

    /**
     * Returns true if this node is marked temporarily offline by the user.
     *
     *

     * In contrast, {@link #isOffline()} represents the actual online/offline
     * state. For example, this method may return false while {@link #isOffline()}
     * returns true if the slave agent failed to launch.
     *
     * @deprecated
     *      You should almost always want {@link #isOffline()}.
     *      This method is marked as deprecated to warn people when they
     *      accidentally call this method.
     */
    @Exported
    @Deprecated
    public boolean isTemporarilyOffline() {
        return temporarilyOffline;
    }

    /**
     * @deprecated as of 1.320.
     *      Use {@link #setTemporarilyOffline(boolean, OfflineCause)}
     */
    @Deprecated
    public void setTemporarilyOffline(boolean temporarilyOffline) {
        setTemporarilyOffline(temporarilyOffline,null);
    }

    /**
     * Marks the computer as temporarily offline. This retains the underlying
     * {@link Channel} connection, but prevent builds from executing.
     *
     * @param cause
     *      If the first argument is true, specify the reason why the node is being put
     *      offline.
     */
    public void setTemporarilyOffline(boolean temporarilyOffline, OfflineCause cause) {
        offlineCause = temporarilyOffline ? cause : null;
        this.temporarilyOffline = temporarilyOffline;
        Node node = getNode();
        if (node != null) {
            node.setTemporaryOfflineCause(offlineCause);
        }
        Jenkins.getInstance().getQueue().scheduleMaintenance();
        synchronized (statusChangeLock) {
            statusChangeLock.notifyAll();
        }
        for (ComputerListener cl : ComputerListener.all()) {
            if (temporarilyOffline)     cl.onTemporarilyOffline(this,cause);
            else                        cl.onTemporarilyOnline(this);
        }
    }

    @Exported
    public String getIcon() {
        if(isOffline())
            return "computer-x.png";
        else
            return "computer.png";
    }

    @Exported
    public String getIconClassName() {
        if(isOffline())
            return "icon-computer-x";
        else
            return "icon-computer";
    }

    public String getIconAltText() {
        if(isOffline())
            return "[offline]";
        else
            return "[online]";
    }

    @Exported
    @Override public @Nonnull String getDisplayName() {
        return nodeName;
    }

    public String getCaption() {
        return Messages.Computer_Caption(nodeName);
    }

    public String getUrl() {
        return "computer/" + Util.rawEncode(getName()) + "/";
    }

    /**
     * Returns projects that are tied on this node.
     */
    public List<AbstractProject> getTiedJobs() {
        Node node = getNode();
        return (node != null) ? node.getSelfLabel().getTiedJobs() : Collections.EMPTY_LIST;
    }

    public RunList getBuilds() {
    	return new RunList(Jenkins.getInstance().getAllItems(Job.class)).node(getNode());
    }

    /**
     * Called to notify {@link Computer} that its corresponding {@link Node}
     * configuration is updated.
     */
    protected void setNode(Node node) {
        assert node!=null;
        if(node instanceof Slave)
            this.nodeName = node.getNodeName();
        else
            this.nodeName = null;

        setNumExecutors(node.getNumExecutors());
        if (this.temporarilyOffline) {
            // When we get a new node, push our current temp offline
            // status to it (as the status is not carried across
            // configuration changes that recreate the node).
            // Since this is also called the very first time this
            // Computer is created, avoid pushing an empty status
            // as that could overwrite any status that the Node
            // brought along from its persisted config data.
            node.setTemporaryOfflineCause(this.offlineCause);
        }
    }

    /**
     * Called by {@link Jenkins#updateComputerList()} to notify {@link Computer} that it will be discarded.
     *
     *

     * Note that at this point {@link #getNode()} returns null.
     *
     * @see #onRemoved()
     */
    protected void kill() {
        // On most code paths, this should already be zero, and thus this next call becomes a no-op... and more
        // importantly it will not acquire a lock on the Queue... not that the lock is bad, more that the lock
        // may delay unnecessarily
        setNumExecutors(0);
    }

    /**
     * Called by {@link Jenkins#updateComputerList()} to notify {@link Computer} that it will be discarded.
     *
     *

     * Note that at this point {@link #getNode()} returns null.
     *
     *

     * Note that the Queue lock is already held when this method is called.
     *
     * @see #onRemoved()
     */
    @Restricted(NoExternalUse.class)
    @GuardedBy("hudson.model.Queue.lock")
    /*package*/ void inflictMortalWound() {
        setNumExecutors(0);
    }

    /**
     * Called by {@link Jenkins} when this computer is removed.
     *
     *

     * This happens when list of nodes are updated (for example by {@link Jenkins#setNodes(List)} and
     * the computer becomes redundant. Such {@link Computer}s get {@linkplain #kill() killed}, then
     * after all its executors are finished, this method is called.
     *
     *

     * Note that at this point {@link #getNode()} returns null.
     *
     * @see #kill()
     * @since 1.510
     */
    protected void onRemoved(){
    }

    private synchronized void setNumExecutors(int n) {
        this.numExecutors = n;
        final int diff = executors.size()-n;

        if (diff>0) {
            // we have too many executors
            // send signal to all idle executors to potentially kill them off
            // need the Queue maintenance lock held to prevent concurrent job assignment on the idle executors
            Queue.withLock(new Runnable() {
                @Override
                public void run() {
                    for( Executor e : executors )
                        if(e.isIdle())
                            e.interrupt();
                }
            });
        }

        if (diff<0) {
            // if the number is increased, add new ones
            addNewExecutorIfNecessary();
        }
    }

    private void addNewExecutorIfNecessary() {
        Set<Integer> availableNumbers  = new HashSet<Integer>();
        for (int i = 0; i < numExecutors; i++)
            availableNumbers.add(i);

        for (Executor executor : executors)
            availableNumbers.remove(executor.getNumber());

        for (Integer number : availableNumbers) {
            /* There may be busy executors with higher index, so only
               fill up until numExecutors is reached.
               Extra executors will call removeExecutor(...) and that
               will create any necessary executors from #0 again. */
            if (executors.size() < numExecutors) {
                Executor e = new Executor(this, number);
                executors.add(e);
            }
        }

    }

    /**
     * Returns the number of idle {@link Executor}s that can start working immediately.
     */
    public int countIdle() {
        int n = 0;
        for (Executor e : executors) {
            if(e.isIdle())
                n++;
        }
        return n;
    }

    /**
     * Returns the number of {@link Executor}s that are doing some work right now.
     */
    public final int countBusy() {
        return countExecutors()-countIdle();
    }

    /**
     * Returns the current size of the executor pool for this computer.
     * This number may temporarily differ from {@link #getNumExecutors()} if there
     * are busy tasks when the configured size is decreased.  OneOffExecutors are
     * not included in this count.
     */
    public final int countExecutors() {
        return executors.size();
    }

    /**
     * Gets the read-only snapshot view of all {@link Executor}s.
     */
    @Exported
    public List<Executor> getExecutors() {
        return new ArrayList<Executor>(executors);
    }

    /**
     * Gets the read-only snapshot view of all {@link OneOffExecutor}s.
     */
    @Exported
    public List<OneOffExecutor> getOneOffExecutors() {
        return new ArrayList<OneOffExecutor>(oneOffExecutors);
    }

    /**
     * Used to render the list of executors.
     * @return a snapshot of the executor display information
     * @since 1.607
     */
    @Restricted(NoExternalUse.class)
    public List<DisplayExecutor> getDisplayExecutors() {
        // The size may change while we are populating, but let's start with a reasonable guess to minimize resizing
        List<DisplayExecutor> result = new ArrayList<DisplayExecutor>(executors.size()+oneOffExecutors.size());
        int index = 0;
        for (Executor e: executors) {
            if (e.isDisplayCell()) {
                result.add(new DisplayExecutor(Integer.toString(index + 1), String.format("executors/%d", index), e));
            }
            index++;
        }
        index = 0;
        for (OneOffExecutor e: oneOffExecutors) {
            if (e.isDisplayCell()) {
                result.add(new DisplayExecutor("", String.format("oneOffExecutors/%d", index), e));
            }
            index++;
        }
        return result;
    }

    /**
     * Returns true if all the executors of this computer are idle.
     */
    @Exported
    public final boolean isIdle() {
        if (!oneOffExecutors.isEmpty())
            return false;
        for (Executor e : executors)
            if(!e.isIdle())
                return false;
        return true;
    }

    /**
     * Returns true if this computer has some idle executors that can take more workload.
     */
    public final boolean isPartiallyIdle() {
        for (Executor e : executors)
            if(e.isIdle())
                return true;
        return false;
    }

    /**
     * Returns the time when this computer last became idle.
     *
     *

     * If this computer is already idle, the return value will point to the
     * time in the past since when this computer has been idle.
     *
     *

     * If this computer is busy, the return value will point to the
     * time in the future where this computer will be expected to become free.
     */
    public final long getIdleStartMilliseconds() {
        long firstIdle = Long.MIN_VALUE;
        for (Executor e : oneOffExecutors) {
            firstIdle = Math.max(firstIdle, e.getIdleStartMilliseconds());
        }
        for (Executor e : executors) {
            firstIdle = Math.max(firstIdle, e.getIdleStartMilliseconds());
        }
        return firstIdle;
    }

    /**
     * Returns the time when this computer first became in demand.
     */
    public final long getDemandStartMilliseconds() {
        long firstDemand = Long.MAX_VALUE;
        for (Queue.BuildableItem item : Jenkins.getInstance().getQueue().getBuildableItems(this)) {
            firstDemand = Math.min(item.buildableStartMilliseconds, firstDemand);
        }
        return firstDemand;
    }

    /**
     * Called by {@link Executor} to kill excessive executors from this computer.
     */
    /*package*/ void removeExecutor(final Executor e) {
        final Runnable task = new Runnable() {
            @Override
            public void run() {
                synchronized (Computer.this) {
                    executors.remove(e);
                    addNewExecutorIfNecessary();
                    if (!isAlive()) {
                        AbstractCIBase ciBase = Jenkins.getInstance();
                        if (ciBase != null) {
                            ciBase.removeComputer(Computer.this);
                        }
                    }
                }
            }
        };
        if (!Queue.tryWithLock(task)) {
            // JENKINS-28840 if we couldn't get the lock push the operation to a separate thread to avoid deadlocks
            threadPoolForRemoting.submit(Queue.wrapWithLock(task));
        }
    }

    /**
     * Returns true if any of the executors are {@linkplain Executor#isActive active}.
     *
     * Note that if an executor dies, we'll leave it in {@link #executors} until
     * the administrator yanks it out, so that we can see why it died.
     *
     * @since 1.509
     */
    protected boolean isAlive() {
        for (Executor e : executors)
            if (e.isActive())
                return true;
        return false;
    }

    /**
     * Interrupt all {@link Executor}s.
     * Called from {@link Jenkins#cleanUp}.
     */
    public void interrupt() {
        Queue.withLock(new Runnable() {
            @Override
            public void run() {
                for (Executor e : executors) {
                    e.interruptForShutdown();
                }
            }
        });
    }

    public String getSearchUrl() {
        return getUrl();
    }

    /**
     * {@link RetentionStrategy} associated with this computer.
     *
     * @return
     *      never null. This method return {@code RetentionStrategy<? super T>} where
     *      {@code T=this.getClass()}.
     */
    public abstract RetentionStrategy getRetentionStrategy();

    /**
     * Expose monitoring data for the remote API.
     */
    @Exported(inline=true)
    public Map<String/*monitor name*/,Object> getMonitorData() {
        Map<String,Object> r = new HashMap<String, Object>();
        if (hasPermission(CONNECT)) {
            for (NodeMonitor monitor : NodeMonitor.getAll())
                r.put(monitor.getClass().getName(), monitor.data(this));
        }
        return r;
    }

    /**
     * Gets the system properties of the JVM on this computer.
     * If this is the master, it returns the system property of the master computer.
     */
    public Map<Object,Object> getSystemProperties() throws IOException, InterruptedException {
        return RemotingDiagnostics.getSystemProperties(getChannel());
    }

    /**
     * @deprecated as of 1.292
     *      Use {@link #getEnvironment()} instead.
     */
    @Deprecated
    public Map<String,String> getEnvVars() throws IOException, InterruptedException {
        return getEnvironment();
    }

    /**
     * Returns cached environment variables (copy to prevent modification) for the JVM on this computer.
     * If this is the master, it returns the system property of the master computer.
     */
    public EnvVars getEnvironment() throws IOException, InterruptedException {
        EnvVars cachedEnvironment = this.cachedEnvironment;
        if (cachedEnvironment != null) {
            return new EnvVars(cachedEnvironment);
        }

        cachedEnvironment = EnvVars.getRemote(getChannel());
        this.cachedEnvironment = cachedEnvironment;
        return new EnvVars(cachedEnvironment);
    }

    /**
     * Creates an environment variable override to be used for launching processes on this node.
     *
     * @see ProcStarter#envs(Map)
     * @since 1.489
     */
    public @Nonnull EnvVars buildEnvironment(@Nonnull TaskListener listener) throws IOException, InterruptedException {
        EnvVars env = new EnvVars();

        Node node = getNode();
        if (node==null)     return env; // bail out

        for (NodeProperty nodeProperty: Jenkins.getInstance().getGlobalNodeProperties()) {
            nodeProperty.buildEnvVars(env,listener);
        }

        for (NodeProperty nodeProperty: node.getNodeProperties()) {
            nodeProperty.buildEnvVars(env,listener);
        }

        // TODO: hmm, they don't really belong
        String rootUrl = Jenkins.getInstance().getRootUrl();
        if(rootUrl!=null) {
            env.put("HUDSON_URL", rootUrl); // Legacy.
            env.put("JENKINS_URL", rootUrl);
        }

        return env;
    }

    /**
     * Gets the thread dump of the slave JVM.
     * @return
     *      key is the thread name, and the value is the pre-formatted dump.
     */
    public Map<String,String> getThreadDump() throws IOException, InterruptedException {
        return RemotingDiagnostics.getThreadDump(getChannel());
    }

    /**
     * Obtains the heap dump.
     */
    public HeapDump getHeapDump() throws IOException {
        return new HeapDump(this,getChannel());
    }

    /**
     * This method tries to compute the name of the host that's reachable by all the other nodes.
     *
     *

     * Since it's possible that the slave is not reachable from the master (it may be behind a firewall,
     * connecting to master via JNLP), this method may return null.
     *
     * It's surprisingly tricky for a machine to know a name that other systems can get to,
     * especially between things like DNS search suffix, the hosts file, and YP.
     *
     *

     * So the technique here is to compute possible interfaces and names on the slave,
     * then try to ping them from the master, and pick the one that worked.
     *
     *

     * The computation may take some time, so it employs caching to make the successive lookups faster.
     *
     * @since 1.300
     * @return
     *      null if the host name cannot be computed (for example because this computer is offline,
     *      because the slave is behind the firewall, etc.)
     */
    public String getHostName() throws IOException, InterruptedException {
        if(hostNameCached)
            // in the worst case we end up having multiple threads computing the host name simultaneously, but that's not harmful, just wasteful.
            return cachedHostName;

        VirtualChannel channel = getChannel();
        if(channel==null)   return null; // can't compute right now

        for( String address : channel.call(new ListPossibleNames())) {
            try {
                InetAddress ia = InetAddress.getByName(address);
                if(!(ia instanceof Inet4Address)) {
                    LOGGER.log(Level.FINE, "{0} is not an IPv4 address", address);
                    continue;
                }
                if(!ComputerPinger.checkIsReachable(ia, 3)) {
                    LOGGER.log(Level.FINE, "{0} didn't respond to ping", address);
                    continue;
                }
                cachedHostName = ia.getCanonicalHostName();
                hostNameCached = true;
                return cachedHostName;
            } catch (IOException e) {
                // if a given name fails to parse on this host, we get this error
                LogRecord lr = new LogRecord(Level.FINE, "Failed to parse {0}");
                lr.setThrown(e);
                lr.setParameters(new Object[]{address});
                LOGGER.log(lr);
            }
        }

        // allow the administrator to manually specify the host name as a fallback. HUDSON-5373
        cachedHostName = channel.call(new GetFallbackName());
        hostNameCached = true;
        return cachedHostName;
    }

    /**
     * Starts executing a fly-weight task.
     */
    /*package*/ final void startFlyWeightTask(WorkUnit p) {
        OneOffExecutor e = new OneOffExecutor(this);
        e.start(p);
        oneOffExecutors.add(e);
    }

    /*package*/ final void remove(OneOffExecutor e) {
        oneOffExecutors.remove(e);
    }

    private static class ListPossibleNames extends MasterToSlaveCallable<List<String>,IOException> {
        /**
         * In the normal case we would use {@link Computer} as the logger's name, however to
         * do that we would have to send the {@link Computer} class over to the remote classloader
         * and then it would need to be loaded, which pulls in {@link Jenkins} and loads that
         * and then that fails to load as you are not supposed to do that. Another option
         * would be to export the logger over remoting, with increased complexity as a result.
         * Instead we just use a loger based on this class name and prevent any references to
         * other classes from being transferred over remoting.
         */
        private static final Logger LOGGER = Logger.getLogger(ListPossibleNames.class.getName());

        public List<String> call() throws IOException {
            List<String> names = new ArrayList<String>();

            Enumeration<NetworkInterface> nis = NetworkInterface.getNetworkInterfaces();
            while (nis.hasMoreElements()) {
                NetworkInterface ni =  nis.nextElement();
                LOGGER.log(Level.FINE, "Listing up IP addresses for {0}", ni.getDisplayName());
                Enumeration<InetAddress> e = ni.getInetAddresses();
                while (e.hasMoreElements()) {
                    InetAddress ia =  e.nextElement();
                    if(ia.isLoopbackAddress()) {
                        LOGGER.log(Level.FINE, "{0} is a loopback address", ia);
                        continue;
                    }

                    if(!(ia instanceof Inet4Address)) {
                        LOGGER.log(Level.FINE, "{0} is not an IPv4 address", ia);
                        continue;
                    }

                    LOGGER.log(Level.FINE, "{0} is a viable candidate", ia);
                    names.add(ia.getHostAddress());
                }
            }
            return names;
        }
        private static final long serialVersionUID = 1L;
    }

    private static class GetFallbackName extends MasterToSlaveCallable<String,IOException> {
        public String call() throws IOException {
            return System.getProperty("host.name");
        }
        private static final long serialVersionUID = 1L;
    }

    public static final ExecutorService threadPoolForRemoting = new ContextResettingExecutorService(
            Executors.newCachedThreadPool(
                    new ExceptionCatchingThreadFactory(
                            new NamingThreadFactory(new DaemonThreadFactory(), "Computer.threadPoolForRemoting"))));

//
//
// UI
//
//
    public void doRssAll( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
        rss(req, rsp, " all builds", getBuilds());
    }

    public void doRssFailed(StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
        rss(req, rsp, " failed builds", getBuilds().failureOnly());
    }
    private void rss(StaplerRequest req, StaplerResponse rsp, String suffix, RunList runs) throws IOException, ServletException {
        RSS.forwardToRss(getDisplayName() + suffix, getUrl(),
                runs.newBuilds(), Run.FEED_ADAPTER, req, rsp);
    }

    @RequirePOST
    public HttpResponse doToggleOffline(@QueryParameter String offlineMessage) throws IOException, ServletException {
        if(!temporarilyOffline) {
            checkPermission(DISCONNECT);
            offlineMessage = Util.fixEmptyAndTrim(offlineMessage);
            setTemporarilyOffline(!temporarilyOffline,
                    new OfflineCause.UserCause(User.current(), offlineMessage));
        } else {
            checkPermission(CONNECT);
            setTemporarilyOffline(!temporarilyOffline,null);
        }
        return HttpResponses.redirectToDot();
    }

    @RequirePOST
    public HttpResponse doChangeOfflineCause(@QueryParameter String offlineMessage) throws IOException, ServletException {
        checkPermission(DISCONNECT);
        offlineMessage = Util.fixEmptyAndTrim(offlineMessage);
        setTemporarilyOffline(true,
                new OfflineCause.UserCause(User.current(), offlineMessage));
        return HttpResponses.redirectToDot();
    }

    public Api getApi() {
        return new Api(this);
    }

    /**
     * Dumps the contents of the export table.
     */
    public void doDumpExportTable( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, InterruptedException {
        // this is a debug probe and may expose sensitive information
        checkPermission(Jenkins.ADMINISTER);

        rsp.setContentType("text/plain");
        PrintWriter w = new PrintWriter(rsp.getCompressedWriter(req));
        VirtualChannel vc = getChannel();
        if (vc instanceof Channel) {
            w.println("Master to slave");
            ((Channel)vc).dumpExportTable(w);
            w.flush(); // flush here once so that even if the dump from the slave fails, the client gets some useful info

            w.println("\n\n\nSlave to master");
            w.print(vc.call(new DumpExportTableTask()));
        } else {
            w.println(Messages.Computer_BadChannel());
        }
        w.close();
    }

    private static final class DumpExportTableTask extends MasterToSlaveCallable<String,IOException> {
        public String call() throws IOException {
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);
            Channel.current().dumpExportTable(pw);
            pw.close();
            return sw.toString();
        }
    }

    /**
     * For system diagnostics.
     * Run arbitrary Groovy script.
     */
    public void doScript(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
        _doScript(req, rsp, "_script.jelly");
    }

    /**
     * Run arbitrary Groovy script and return result as plain text.
     */
    public void doScriptText(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
        _doScript(req, rsp, "_scriptText.jelly");
    }

    protected void _doScript(StaplerRequest req, StaplerResponse rsp, String view) throws IOException, ServletException {
        Jenkins._doScript(req, rsp, req.getView(this, view), getChannel(), getACL());
    }

    /**
     * Accepts the update to the node configuration.
     */
    @RequirePOST
    public void doConfigSubmit( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, FormException {
        checkPermission(CONFIGURE);

        String name = Util.fixEmptyAndTrim(req.getSubmittedForm().getString("name"));
        Jenkins.checkGoodName(name);

        Node node = getNode();
        if (node == null) {
            throw new ServletException("No such node " + nodeName);
        }
        Node result = node.reconfigure(req, req.getSubmittedForm());
        replaceBy(result);

        // take the user back to the slave top page.
        rsp.sendRedirect2("../" + result.getNodeName() + '/');
    }

    /**
     * Accepts <tt>config.xml</tt> submission, as well as serve it.
     */
    @WebMethod(name = "config.xml")
    public void doConfigDotXml(StaplerRequest req, StaplerResponse rsp)
            throws IOException, ServletException {

        if (req.getMethod().equals("GET")) {
            // read
            checkPermission(EXTENDED_READ);
            rsp.setContentType("application/xml");
            Node node = getNode();
            if (node == null) {
                throw HttpResponses.notFound();
            }
            Jenkins.XSTREAM2.toXMLUTF8(node, rsp.getOutputStream());
            return;
        }
        if (req.getMethod().equals("POST")) {
            // submission
            updateByXml(req.getInputStream());
            return;
        }

        // huh?
        rsp.sendError(SC_BAD_REQUEST);
    }

    /**
     * Replaces the current {@link Node} by another one.
     */
    private void replaceBy(final Node newNode) throws ServletException, IOException {
        final Jenkins app = Jenkins.getInstance();

        // use the queue lock until Nodes has a way of directly modifying a single node.
        Queue.withLock(new NotReallyRoleSensitiveCallable<Void, IOException>() {
            public Void call() throws IOException {
                List<Node> nodes = new ArrayList<Node>(app.getNodes());
                Node node = getNode();
                int i  = (node != null) ? nodes.indexOf(node) : -1;
                if(i<0) {
                    throw new IOException("This slave appears to be removed while you were editing the configuration");
                }
                nodes.set(i, newNode);
                app.setNodes(nodes);
                return null;
            }
        });
    }

    /**
     * Updates Job by its XML definition.
     *
     * @since 1.526
     */
    public void updateByXml(final InputStream source) throws IOException, ServletException {
        checkPermission(CONFIGURE);
        Node result = (Node)Jenkins.XSTREAM2.fromXML(source);
        replaceBy(result);
    }

    /**
     * Really deletes the slave.
     */
    @RequirePOST
    public HttpResponse doDoDelete() throws IOException {
        checkPermission(DELETE);
        Node node = getNode();
        if (node != null) {
            Jenkins.getInstance().removeNode(node);
        } else {
            AbstractCIBase app = Jenkins.getInstance();
            app.removeComputer(this);
        }
        return new HttpRedirect("..");
    }

    /**
     * Blocks until the node becomes online/offline.
     */
    @CLIMethod(name="wait-node-online")
    public void waitUntilOnline() throws InterruptedException {
        synchronized (statusChangeLock) {
            while (!isOnline())
                statusChangeLock.wait(1000);
        }
    }

    @CLIMethod(name="wait-node-offline")
    public void waitUntilOffline() throws InterruptedException {
        synchronized (statusChangeLock) {
            while (!isOffline())
                statusChangeLock.wait(1000);
        }
    }

    /**
     * Handles incremental log.
     */
    public void doProgressiveLog( StaplerRequest req, StaplerResponse rsp) throws IOException {
        getLogText().doProgressText(req, rsp);
    }

    /**
     * Gets the current {@link Computer} that the build is running.
     * This method only works when called during a build, such as by
     * {@link hudson.tasks.Publisher}, {@link hudson.tasks.BuildWrapper}, etc.
     * @return the {@link Computer} associated with {@link Executor#currentExecutor}, or (consistently as of 1.591) null if not on an executor thread
     */
    public static @Nullable Computer currentComputer() {
        Executor e = Executor.currentExecutor();
        return e != null ? e.getOwner() : null;
    }

    /**
     * Returns {@code true} if the computer is accepting tasks. Needed to allow slaves programmatic suspension of task
     * scheduling that does not overlap with being offline.
     *
     * @return {@code true} if the computer is accepting tasks
     * @see hudson.slaves.RetentionStrategy#isAcceptingTasks(Computer)
     * @see hudson.model.Node#isAcceptingTasks()
     */
    @OverrideMustInvoke(When.ANYTIME)
    public boolean isAcceptingTasks() {
        final Node node = getNode();
        return getRetentionStrategy().isAcceptingTasks(this) && (node == null || node.isAcceptingTasks());
    }

    /**
     * Used for CLI binding.
     */
    @CLIResolver
    public static Computer resolveForCLI(
            @Argument(required=true,metaVar="NAME",usage="Slave name, or empty string for master") String name) throws CmdLineException {
        Jenkins h = Jenkins.getInstance();
        Computer item = h.getComputer(name);
        if (item==null) {
            List<String> names = new ArrayList<String>();
            for (Computer c : h.getComputers())
                if (c.getName().length()>0)
                    names.add(c.getName());
            throw new CmdLineException(null,Messages.Computer_NoSuchSlaveExists(name,EditDistance.findNearest(name,names)));
        }
        return item;
    }

    /**
     * Relocate log files in the old location to the new location.
     *
     * Files were used to be $JENKINS_ROOT/slave-NAME.log (and .1, .2, ...)
     * but now they are at $JENKINS_ROOT/logs/slaves/NAME/slave.log (and .1, .2, ...)
     *
     * @see #getLogFile()
     */
    @Initializer
    public static void relocateOldLogs() {
        relocateOldLogs(Jenkins.getInstance().getRootDir());
    }

    /*package*/ static void relocateOldLogs(File dir) {
        final Pattern logfile = Pattern.compile("slave-(.*)\\.log(\\.[0-9]+)?");
        File[] logfiles = dir.listFiles(new FilenameFilter() {
            public boolean accept(File dir, String name) {
                return logfile.matcher(name).matches();
            }
        });
        if (logfiles==null)     return;

        for (File f : logfiles) {
            Matcher m = logfile.matcher(f.getName());
            if (m.matches()) {
                File newLocation = new File(dir, "logs/slaves/" + m.group(1) + "/slave.log" + Util.fixNull(m.group(2)));
                newLocation.getParentFile().mkdirs();
                boolean relocationSuccessfull=f.renameTo(newLocation);
                if (relocationSuccessfull) { // The operation will fail if mkdir fails
                    LOGGER.log(Level.INFO, "Relocated log file {0} to {1}",new Object[] {f.getPath(),newLocation.getPath()});
                } else {
                    LOGGER.log(Level.WARNING, "Cannot relocate log file {0} to {1}",new Object[] {f.getPath(),newLocation.getPath()});
                }
            } else {
                assert false;
            }
        }
    }

    /**
     * A value class to provide a consistent snapshot view of the state of an executor to avoid race conditions
     * during rendering of the executors list.
     *
     * @since 1.607
     */
    @Restricted(NoExternalUse.class)
    public static class DisplayExecutor implements ModelObject {

        @Nonnull
        private final String displayName;
        @Nonnull
        private final String url;
        @Nonnull
        private final Executor executor;

        public DisplayExecutor(@Nonnull String displayName, @Nonnull String url, @Nonnull Executor executor) {
            this.displayName = displayName;
            this.url = url;
            this.executor = executor;
        }

        @Override
        @Nonnull
        public String getDisplayName() {
            return displayName;
        }

        @Nonnull
        public String getUrl() {
            return url;
        }

        @Nonnull
        public Executor getExecutor() {
            return executor;
        }

        @Override
        public String toString() {
            final StringBuilder sb = new StringBuilder("DisplayExecutor{");
            sb.append("displayName='").append(displayName).append('\'');
            sb.append(", url='").append(url).append('\'');
            sb.append(", executor=").append(executor);
            sb.append('}');
            return sb.toString();
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }

            DisplayExecutor that = (DisplayExecutor) o;

            if (!executor.equals(that.executor)) {
                return false;
            }

            return true;
        }

        @Extension(ordinal = Double.MAX_VALUE)
        @Restricted(DoNotUse.class)
        public static class InternalComputerListener extends ComputerListener {
            @Override
            public void onOnline(Computer c, TaskListener listener) throws IOException, InterruptedException {
                c.cachedEnvironment = null;
            }
        }

        @Override
        public int hashCode() {
            return executor.hashCode();
        }
    }

    /**
     * Used to trace requests to terminate a computer.
     *
     * @since 1.607
     */
    public static class TerminationRequest extends RuntimeException {
        private final long when;
        public TerminationRequest(String message) {
            super(message);
            this.when = System.currentTimeMillis();
        }

        /**
         * Returns the when the termination request was created.
         *
         * @return the difference, measured in milliseconds, between
         * the time of the termination request and midnight, January 1, 1970 UTC.
         */
        public long getWhen() {
            return when;
        }
    }

    public static final PermissionGroup PERMISSIONS = new PermissionGroup(Computer.class,Messages._Computer_Permissions_Title());
    public static final Permission CONFIGURE = new Permission(PERMISSIONS,"Configure", Messages._Computer_ConfigurePermission_Description(), Permission.CONFIGURE, PermissionScope.COMPUTER);
    /**
     * @since 1.532
     */
    public static final Permission EXTENDED_READ = new Permission(PERMISSIONS,"ExtendedRead", Messages._Computer_ExtendedReadPermission_Description(), CONFIGURE, Boolean.getBoolean("hudson.security.ExtendedReadPermission"), new PermissionScope[]{PermissionScope.COMPUTER});
    public static final Permission DELETE = new Permission(PERMISSIONS,"Delete", Messages._Computer_DeletePermission_Description(), Permission.DELETE, PermissionScope.COMPUTER);
    public static final Permission CREATE = new Permission(PERMISSIONS,"Create", Messages._Computer_CreatePermission_Description(), Permission.CREATE, PermissionScope.COMPUTER);
    public static final Permission DISCONNECT = new Permission(PERMISSIONS,"Disconnect", Messages._Computer_DisconnectPermission_Description(), Jenkins.ADMINISTER, PermissionScope.COMPUTER);
    public static final Permission CONNECT = new Permission(PERMISSIONS,"Connect", Messages._Computer_ConnectPermission_Description(), DISCONNECT, PermissionScope.COMPUTER);
    public static final Permission BUILD = new Permission(PERMISSIONS, "Build", Messages._Computer_BuildPermission_Description(),  Permission.WRITE, PermissionScope.COMPUTER);

    private static final Logger LOGGER = Logger.getLogger(Computer.class.getName());
}

Output:
【漏洞类型】权限检查缺失
【严重性】中等
【漏洞摘要】在`getMonitorData`方法中，`NodeMonitor`数据的暴露没有进行权限检查。这意味着任何用户只要能访问到此接口，就可以获取节点监视器的数据，这可能包含敏感信息，如系统性能监控数据。
【关键代码模式】`getMonitorData`方法，`NodeMonitor.getAll()`，`monitor.data(this)`。
【修复方法说明】修复方法通过在返回`NodeMonitor`数据之前添加权限检查`hasPermission(CONNECT)`来解决问题。


Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
core/src/main/java/hudson/model/ParameterValue.java
### 代码内容：
package hudson.model;

import hudson.EnvVars;
import hudson.Util;
import hudson.model.queue.SubTask;
import hudson.scm.SCM;
import hudson.tasks.BuildWrapper;
import hudson.tasks.Builder;
import hudson.util.VariableResolver;
import java.io.IOException;

import java.io.Serializable;
import java.util.Map;
import java.util.logging.Logger;
import jenkins.model.Jenkins;

import net.sf.json.JSONObject;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;

import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;

/**
 * A value for a parameter in a build.
 *
 * Created by {@link ParameterDefinition#createValue(StaplerRequest, JSONObject)} for
 * a particular build (although this 'owner' build object is passed in for every method
 * call as a parameter so that the parameter won't have to persist it.)
 *
 *
Persistence

 *

 * Instances of {@link ParameterValue}s are persisted into build's <tt>build.xml</tt>
 * through XStream (via {@link ParametersAction}), so instances need to be persistable.
 *
 *
Associated Views

 *
value.jelly

 * The <tt>value.jelly</tt> view contributes a UI fragment to display the parameter
 * values used for a build.
 *
 *
Notes

 *

 *
{@link ParameterValue} is used to record values of the past build, but
 *     {@link ParameterDefinition} used back then might be gone already, or represent
 *     a different parameter now. So don't try to use the name to infer
 *     {@link ParameterDefinition} is.
 *

 * @see ParameterDefinition
 * @see ParametersAction
 */
@ExportedBean(defaultVisibility=3)
public abstract class ParameterValue implements Serializable {

    private static final Logger LOGGER = Logger.getLogger(ParameterValue.class.getName());

    protected final String name;

    private String description;

    protected ParameterValue(String name, String description) {
        this.name = name;
        this.description = description;
    }

    protected ParameterValue(String name) {
        this(name, null);
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    @Restricted(DoNotUse.class) // for value.jelly
    public String getFormattedDescription() {
        try {
            return Jenkins.getInstance().getMarkupFormatter().translate(description);
        } catch (IOException e) {
            LOGGER.warning("failed to translate description using configured markup formatter");
            return "";
        }
    }

    /**
     * Name of the parameter.
     *
     * This uniquely distinguishes {@link ParameterValue} among other parameters
     * for the same build. This must be the same as {@link ParameterDefinition#getName()}.
     */
    @Exported
    public final String getName() {
        return name;
    }

    /**
     * Adds environmental variables for the builds to the given map.
     *
     *

     * This provides a means for a parameter to pass the parameter
     * values to the build to be performed.
     *
     *

     * When this method is invoked, the map already contains the
     * current "planned export" list. The implementation is
     * expected to add more values to this map (or do nothing)
     *
     *

     * Environment variables should be by convention all upper case.
     * (This is so that a Windows/Unix heterogeneous environment
     * won't get inconsistent result depending on which platform to
     * execute.) (see {@link EnvVars} why upper casing is a bad idea.)
     *
     * @param env
     *      never null.
     * @param build
     *      The build for which this parameter is being used. Never null.
     * @deprecated as of 1.344
     *      Use {@link #buildEnvironment(Run, EnvVars)} instead.
     */
    @Deprecated
    public void buildEnvVars(AbstractBuild<?,?> build, Map<String,String> env) {
        if (env instanceof EnvVars) {
            if (Util.isOverridden(ParameterValue.class, getClass(), "buildEnvironment", Run.class, EnvVars.class)) {
                // if the subtype already derives buildEnvironment, then delegate to it
                buildEnvironment(build, (EnvVars) env);
            } else if (Util.isOverridden(ParameterValue.class, getClass(), "buildEnvVars", AbstractBuild.class, EnvVars.class)) {
                buildEnvVars(build, (EnvVars) env);
            }
        }
        // otherwise no-op by default
    }

    /** @deprecated Use {@link #buildEnvironment(Run, EnvVars)} instead. */
    @Deprecated
    public void buildEnvVars(AbstractBuild<?,?> build, EnvVars env) {
        if (Util.isOverridden(ParameterValue.class, getClass(), "buildEnvironment", Run.class, EnvVars.class)) {
            buildEnvironment(build, env);
        } else {
            // for backward compatibility
            buildEnvVars(build,(Map<String,String>)env);
        }
    }

    /**
     * Adds environmental variables for the builds to the given map.
     *
     *

     * This provides a means for a parameter to pass the parameter
     * values to the build to be performed.
     *
     *

     * When this method is invoked, the map already contains the
     * current "planned export" list. The implementation is
     * expected to add more values to this map (or do nothing)
     *
     * @param env
     *      never null.
     * @param build
     *      The build for which this parameter is being used. Never null.
     * @since 1.556
     */
    public void buildEnvironment(Run<?,?> build, EnvVars env) {
        if (build instanceof AbstractBuild) {
            buildEnvVars((AbstractBuild) build, env);
        }
        // else do not know how to do it
    }

    /**
     * Called at the beginning of a build (but after {@link SCM} operations
     * have taken place) to let a {@link ParameterValue} contributes a
     * {@link BuildWrapper} to the build.
     *
     *

     * This provides a means for a parameter to perform more extensive
     * set up / tear down during a build.
     *
     * @param build
     *      The build for which this parameter is being used. Never null.
     * @return
     *      null if the parameter has no {@link BuildWrapper} to contribute to.
     */
    public BuildWrapper createBuildWrapper(AbstractBuild<?,?> build) {
        return null;
    }

    /**
     * Returns a {@link VariableResolver} so that other components like {@link Builder}s
     * can perform variable substitution to reflect parameter values into the build process.
     *
     * <p.
     * This is yet another means in which a {@link ParameterValue} can influence
     * a build.
     *
     * @param build
     *      The build for which this parameter is being used. Never null.
     * @return
     *      if the parameter value is not interested in participating to the
     *      variable replacement process, return {@link VariableResolver#NONE}.
     */
    public VariableResolver<String> createVariableResolver(AbstractBuild<?,?> build) {
        return VariableResolver.NONE;
    }

    // TODO should there be a Run overload of this?

    /**
     * Accessing {@link ParameterDefinition} is not a good idea.
     *
     * @deprecated since 2008-09-20.
     *    parameter definition may change any time. So if you find yourself
     *    in need of accessing the information from {@link ParameterDefinition},
     *    instead copy them in {@link ParameterDefinition#createValue(StaplerRequest, JSONObject)}
     *    into {@link ParameterValue}.
     */
    @Deprecated
    public ParameterDefinition getDefinition() {
        throw new UnsupportedOperationException();
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        ParameterValue other = (ParameterValue) obj;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }

    /**
     * Computes a human-readable possible-localized one-line description of the parameter value.
     *
     *

     * This message is used as a tooltip to describe jobs in the queue. The text should be one line without
     * new line. No HTML allowed (the caller will perform necessary HTML escapes, so any text can be returend.)
     *
     * @since 1.323
     */
    public String getShortDescription() {
        return toString();
    }

    /**
     * Returns whether the information contained in this ParameterValue is
     * sensitive or security related. Used to determine whether the value
     * provided by this object should be masked in output.
     *
     *

     * Subclasses can override this to control the return value.
     *
     * @since 1.378
     */
    public boolean isSensitive() {
        return false;
    }

    /**
     * Returns the most natural Java object that represents the actual value, like
     * boolean, string, etc.
     *
     * If there's nothing that really fits the bill, the callee can return {@code this}.
     * @since 1.568
     */
    public Object getValue() {
        return null;
    }

    /**
     * Controls where the build (that this parameter is submitted to) will happen.
     *
     * @return
     *      null to run the build where it normally runs. If non-null, this will
     *      override {@link AbstractProject#getAssignedLabel()}. If a build is
     *      submitted with multiple parameters, the first one that returns non-null
     *      from this method will win, and all others won't be consulted.
     *
     *
     * @since 1.414
     */
    public Label getAssignedLabel(SubTask task) {
        return null;
    }
}

## 内容2
### 文件路径：
core/src/main/resources/hudson/model/BooleanParameterDefinition/index.jelly
### 代码内容：

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define"
	xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form"
	xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project">
        <j:set var="escapeEntryTitleAndDescription" value="false"/>
        <f:entry title="${h.escape(it.name)}" description="${it.formattedDescription}">

			<input type="hidden" name="name" value="${it.name}" />
			<f:checkbox name="value" checked="${it.defaultValue}" />

	</f:entry>
</j:jelly>
## 内容3
### 文件路径：
core/src/main/resources/hudson/model/BooleanParameterValue/value.jelly
### 代码内容：

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define"
	xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form"
	xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project">
        <j:set var="escapeEntryTitleAndDescription" value="false"/>
        <f:entry title="${h.escape(it.name)}" description="${it.formattedDescription}">
		<f:checkbox name="value" checked="${it.value}" readonly="true" />
	</f:entry>
</j:jelly>
## 内容4
### 文件路径：
core/src/main/resources/hudson/model/ChoiceParameterDefinition/index.jelly
### 代码内容：

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define"
	xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form"
	xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project">
        <j:set var="escapeEntryTitleAndDescription" value="false"/>
        <f:entry title="${h.escape(it.name)}" description="${it.formattedDescription}">

			<input type="hidden" name="name" value="${it.name}" />
            <select name="value">
              <j:forEach var="value" items="${it.choices}">
                <f:option selected="${it.defaultParameterValue.value==value}">${value}</f:option>
              </j:forEach>
            </select>

	</f:entry>
</j:jelly>

## 内容5
### 文件路径：
core/src/main/resources/hudson/model/FileParameterDefinition/index.jelly
### 代码内容：

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define"
	xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form"
	xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project">
        <j:set var="escapeEntryTitleAndDescription" value="false"/>
        <f:entry title="${h.escape(it.name)}" description="${it.formattedDescription}">

			<input type="hidden" name="name" value="${it.name}" />
			<input name="file" type="file" jsonAware="true" />

	</f:entry>
</j:jelly>
## 内容6
### 文件路径：
core/src/main/resources/hudson/model/FileParameterValue/value.jelly
### 代码内容：

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define"
	xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form"
	xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project">
        <j:set var="escapeEntryTitleAndDescription" value="false"/>
        <f:entry title="${h.escape(it.name)}" description="${it.formattedDescription}">
        <j:if test="${it.originalFileName != null}">
            <j:invokeStatic var="encodedName" className="hudson.Util" method="rawEncode">
                <j:arg value="${it.name}" />
            </j:invokeStatic>
            <j:set var="path" value="parameter/${encodedName}/${it.originalFileName}"/>
            ${it.originalFileName}
            <st:nbsp/>
            ${%view}
        </j:if>
    </f:entry>
</j:jelly>
## 内容7
### 文件路径：
core/src/main/resources/hudson/model/ParametersAction/index.jelly
### 代码内容：

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define"
    xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form"
    xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project">
  <l:layout title="${it.displayName}">
    <j:invokeStatic var="currentThread" className="java.lang.Thread" method="currentThread" />
    <j:invoke var="buildClass" on="${currentThread.contextClassLoader}" method="loadClass">
      <j:arg value="hudson.model.Run"/>
    </j:invoke>
    <j:set var="build" value="${request.findAncestorObject(buildClass)}" />
		<st:include page="sidepanel.jelly" it="${build}" />
		<l:main-panel>

${%Build} ${build.displayName}

			<l:pane title="${%Parameters}" width="3">
                        <j:set var="escapeEntryTitleAndDescription" value="true"/>
			<j:forEach var="parameterValue" items="${it.parameters}">
				<st:include it="${parameterValue}"
					page="value.jelly" />
			</j:forEach>
			</l:pane>
		</l:main-panel>
  </l:layout>
</j:jelly>

## 内容8
### 文件路径：
core/src/main/resources/hudson/model/ParametersDefinitionProperty/index.jelly
### 代码内容：

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define"
	xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form"
	xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project">

  <st:statusCode value="405" />
  <l:layout title="${it.displayName}" norefresh="true">
    <st:include page="sidepanel.jelly" it="${it.job}"/>
    <l:main-panel>

${%LOADING}

${it.job.pronoun} ${it.job.displayName}

${%description}

      <j:set var="delay" value="${request.getParameter('delay')}" />
      <f:form method="post" action="build${empty(delay)?'':'?delay='+delay}" name="parameters"
              tableClass="parameters">
        <j:forEach var="parameterDefinition" items="${it.parameterDefinitions}">
          <j:set var="escapeEntryTitleAndDescription" value="true"/>

            <st:include it="${parameterDefinition}"
                        page="${parameterDefinition.descriptor.valuePage}" />

        </j:forEach>
        <f:block>

          <input type="hidden" name="statusCode" value="303" />
          <input type="hidden" name="redirectTo" value="." />
          <f:submit value="${%Build}" />
        </f:block>
      </f:form>
    </l:main-panel>
  </l:layout>
</j:jelly>

## 内容9
### 文件路径：
core/src/main/resources/hudson/model/PasswordParameterDefinition/index.jelly
### 代码内容：

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define"
	xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form"
	xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project">
        <j:set var="escapeEntryTitleAndDescription" value="false"/>
        <f:entry title="${h.escape(it.name)}" description="${it.formattedDescription}">

			<input type="hidden" name="name" value="${it.name}" />
			<f:password name="value" value="${it.defaultValueAsSecret}" />

	</f:entry>
</j:jelly>
## 内容10
### 文件路径：
core/src/main/resources/hudson/model/PasswordParameterValue/value.jelly
### 代码内容：

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define"
	xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form"
	xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project">
        <j:set var="escapeEntryTitleAndDescription" value="false"/>
        <f:entry title="${h.escape(it.name)}" description="${it.formattedDescription}">
		<f:password name="value" value="********" readonly="true" />
	</f:entry>
</j:jelly>
## 内容11
### 文件路径：
core/src/main/resources/hudson/model/RunParameterDefinition/index.jelly
### 代码内容：

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define"
	xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form"
	xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project">
        <j:set var="escapeEntryTitleAndDescription" value="false"/>
        <f:entry title="${h.escape(it.name)}" description="${it.formattedDescription}">

			<input type="hidden" name="name" value="${it.name}" />
            <select name="runId">
              <j:forEach var="run" items="${it.builds}">
                <option value="${run.externalizableId}">${run.fullDisplayName}</option>
              </j:forEach>
            </select>

	</f:entry>
</j:jelly>

## 内容12
### 文件路径：
core/src/main/resources/hudson/model/RunParameterValue/value.jelly
### 代码内容：

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define"
	xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form"
	xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project">
        <j:set var="escapeEntryTitleAndDescription" value="false"/>
        <f:entry title="${h.escape(it.name)}" description="${it.formattedDescription}">
		${it.run.fullDisplayName}
	</f:entry>
</j:jelly>

## 内容13
### 文件路径：
core/src/main/resources/hudson/model/StringParameterDefinition/index.jelly
### 代码内容：

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define"
	xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form"
	xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project">
        <j:set var="escapeEntryTitleAndDescription" value="false"/>
        <f:entry title="${h.escape(it.name)}" description="${it.formattedDescription}">

			<input type="hidden" name="name" value="${it.name}" />
			<f:textbox name="value" value="${it.defaultValue}" />

	</f:entry>
</j:jelly>
## 内容14
### 文件路径：
core/src/main/resources/hudson/model/StringParameterValue/value.jelly
### 代码内容：

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define"
	xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form"
	xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project">
        <j:set var="escapeEntryTitleAndDescription" value="false"/>
        <f:entry title="${h.escape(it.name)}" description="${it.formattedDescription}">
		<f:textbox name="value" value="${it.value}" readonly="true" />
	</f:entry>
</j:jelly>
## 内容15
### 文件路径：
core/src/main/resources/hudson/model/TextParameterDefinition/index.jelly
### 代码内容：
<?xml version="1.0"?>

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project">
    <j:set var="escapeEntryTitleAndDescription" value="false"/>
    <f:entry title="${h.escape(it.name)}" description="${it.formattedDescription}">

      <input type="hidden" name="name" value="${it.name}"/>
      <f:textarea name="value" value="${it.defaultValue}"/>

  </f:entry>
</j:jelly>

## 内容16
### 文件路径：
core/src/main/resources/hudson/model/TextParameterValue/value.jelly
### 代码内容：

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define"
	xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form"
	xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project">
        <j:set var="escapeEntryTitleAndDescription" value="false"/>
        <f:entry title="${h.escape(it.name)}" description="${it.formattedDescription}">
		<f:textarea name="value" value="${it.value}" readonly="readonly" />
	</f:entry>
</j:jelly>
## 内容17
### 文件路径：
core/src/main/resources/lib/form/entry.jelly
### 代码内容：

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
  <st:documentation>
    An entry of the <f:form>, which is one logical row (that consists of
    several <TR> tags.

    One entry normally host one control.

    <st:attribute name="title">
      Name of the entry. Think of this like a label for the control.

      This content is HTML (unless the boolean variable escapeEntryTitleAndDescription is set). Use h.escape if necessary.
    </st:attribute>
    <st:attribute name="field">
      Used for the databinding. TBD. When this attribute
      is specified, @help is inferred, and nested input controls don't need
      the @field nor @name.
    </st:attribute>
    <st:attribute name="description">
      If it's not obvious to the user as to what the control expects,
      specify some description text (which currently gets rendered as
      small text under the control, but that may change.)

      This text shouldn't get too long, and in recent Hudson, this feature
      is somewhat de-emphasized, in favor of the inline foldable help page
      specified via @help.

      This content is HTML (unless the boolean variable escapeEntryTitleAndDescription is set). Use h.escape if necessary.
    </st:attribute>
    <st:attribute name="help">
      URL to the HTML page. When this attribute is specified, the entry gets
      a (?) icon on the right, and if the user clicks it, the contents of the
      given URL is rendered as a box below the entry.

      The URL should return an HTML document wrapped in a <div> tag.
      The URL is interpreted to be rooted at the context path of Hudson,
      so it's normally something like "/plugin/foobar/help/abc.html".
    </st:attribute>
  </st:documentation>
  <j:if test="${attrs.help==null and attrs.field!=null}">

    <j:set target="${attrs}" property="help"
           value="${descriptor.getHelpFile(attrs.field)}" />
  </j:if>

  <j:set var="entry" value="${attrs}" />

    <st:nbsp/>

      <j:out value="${escapeEntryTitleAndDescription ? h.escape(attrs.title) : attrs.title}" />

      <d:invokeBody />

    <f:helpLink url="${attrs.help}" featureName="${title}"/>

  <j:if test="${!empty(attrs.description)}">
    <f:description>
      <j:out value="${escapeEntryTitleAndDescription ? h.escape(attrs.description) : attrs.description}"/>
    </f:description>
  </j:if>
  <j:if test="${attrs.help!=null}">
    <f:helpArea />
  </j:if>
</j:jelly>