提交 5a828cc7 编写于 作者: K kohsuke

separating ZFS installation into two steps so that Hudson can ask for the super user password

git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@15262 71c3de6d-444a-0410-be80-ed276b4c234a
上级 76da6ff2
......@@ -635,7 +635,12 @@ THE SOFTWARE.
<dependency>
<groupId>org.jvnet.libzfs</groupId>
<artifactId>libzfs</artifactId>
<version>0.2</version>
<version>0.3</version>
</dependency>
<dependency>
<groupId>com.sun.solaris</groupId>
<artifactId>embedded_su4j</artifactId>
<version>1.0</version>
</dependency>
<!-- offline profiler API to put in the classpath if we need it -->
......
......@@ -345,15 +345,26 @@ public abstract class Launcher {
public Channel launchChannel(String[] cmd, OutputStream out, FilePath workDir, Map<String,String> envVars) throws IOException {
printCommandLine(cmd, workDir);
ProcessBuilder pb = new ProcessBuilder(cmd);
pb.directory(toFile(workDir));
return launchChannel(out, pb);
}
/**
* @param out
* Where the stderr from the launched process will be sent.
*/
public Channel launchChannel(OutputStream out, ProcessBuilder pb) throws IOException {
final EnvVars cookie = ProcessTreeKiller.createCookie();
EnvVars map = Launcher.inherit(envVars);
map.putAll(cookie);
final Process proc = Runtime.getRuntime().exec(cmd, Util.mapToEnv(map), toFile(workDir));
pb.environment().putAll(cookie);
final Process proc = pb.start();
final Thread t2 = new StreamCopyThread(Arrays.asList(cmd)+": stderr copier", proc.getErrorStream(), out);
final Thread t2 = new StreamCopyThread(pb.command()+": stderr copier", proc.getErrorStream(), out);
t2.start();
return new Channel("locally launched channel on "+ Arrays.toString(cmd),
return new Channel("locally launched channel on "+ pb.command(),
Computer.threadPoolForRemoting, proc.getInputStream(), proc.getOutputStream(), out) {
/**
......
......@@ -25,30 +25,41 @@ package hudson.lifecycle;
import com.sun.akuma.Daemon;
import com.sun.akuma.JavaVMArguments;
import com.sun.solaris.EmbeddedSu;
import hudson.FilePath;
import hudson.Launcher.LocalLauncher;
import hudson.Util;
import hudson.model.AdministrativeMonitor;
import hudson.model.Computer;
import hudson.model.Hudson;
import hudson.model.TaskListener;
import hudson.remoting.Callable;
import hudson.remoting.Channel;
import hudson.remoting.Launcher;
import hudson.remoting.Which;
import hudson.util.ForkOutputStream;
import hudson.util.HudsonIsRestarting;
import hudson.util.StreamTaskListener;
import hudson.util.ForkOutputStream;
import static hudson.util.jna.GNUCLibrary.*;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.jvnet.libpam.impl.CLibrary.passwd;
import org.jvnet.solaris.libzfs.ACLBuilder;
import org.jvnet.solaris.libzfs.LibZFS;
import org.jvnet.solaris.libzfs.ZFSException;
import org.jvnet.solaris.libzfs.ZFSFileSystem;
import org.jvnet.solaris.libzfs.ZFSPool;
import org.jvnet.solaris.libzfs.ZFSType;
import org.jvnet.solaris.libzfs.ZFSException;
import org.jvnet.solaris.libzfs.ErrorCode;
import org.jvnet.solaris.mount.MountFlags;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.kohsuke.stapler.QueryParameter;
import javax.servlet.ServletException;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.io.Serializable;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
......@@ -59,7 +70,7 @@ import java.util.logging.Logger;
* @author Kohsuke Kawaguchi
* @since 1.283
*/
public class ZFSInstaller extends AdministrativeMonitor {
public class ZFSInstaller extends AdministrativeMonitor implements Serializable {
/**
* True if $HUDSON_HOME is a ZFS file system by itself.
*/
......@@ -126,14 +137,135 @@ public class ZFSInstaller extends AdministrativeMonitor {
rsp.sendRedirect2("confirm");
}
/**
* Creates a ZFS file system to migrate the data to.
*
* <p>
* This has to be done while we still have an interactive access with the user, since it involves the password.
*
* <p>
* An exception will be thrown if the operation fails. A normal completion means a success.
*
* @return
* The ZFS dataset name to migrate the data to.
*/
private String createZfsFileSystem(final TaskListener listener, String rootUsername, String rootPassword) throws IOException, InterruptedException, ZFSException {
// capture the UID that Hudson runs under
// so that we can allow this user to do everything on this new partition
int uid = LIBC.geteuid();
passwd pwd = LIBC.getpwuid(uid);
if(pwd==null)
throw new IOException("Failed to obtain the current user information for "+uid);
final String userName = pwd.pw_name;
final File home = Hudson.getInstance().getRootDir();
// this is the actual creation of the file system.
// return true indicating a success
Callable<String,IOException> task = new Callable<String,IOException>() {
public String call() throws IOException {
PrintStream out = listener.getLogger();
LibZFS zfs = new LibZFS();
ZFSFileSystem existing = zfs.getFileSystemByMountPoint(home);
if(existing!=null) {
// no need for migration
out.println(home+" is already on ZFS. Doing nothing");
return existing.getName();
}
String name = computeHudsonFileSystemName(zfs, zfs.roots().get(0));
out.println("Creating "+name);
ZFSFileSystem hudson = zfs.create(name, ZFSFileSystem.class);
try {
hudson.setProperty("hudson:managed-by","hudson"); // mark this file system as "managed by Hudson"
ACLBuilder acl = new ACLBuilder();
acl.user(userName).withEverything();
hudson.allow(acl);
} catch (ZFSException e) {
// revert the file system creation
try {
hudson.destory();
} catch (Exception _) {
// but ignore the error and let the original error thrown
}
throw e;
}
return hudson.getName();
}
};
// if we are the root user already, we can just do it here.
// if that fails, no amount of pfexec and embedded_sudo would do.
if(uid==0)
return task.call();
String javaExe = System.getProperty("java.home") + "/bin/java";
String slaveJar = Which.jarFile(Launcher.class).getAbsolutePath();
// otherwise first attempt pfexec, as that doesn't require password
Channel channel;
Process proc=null;
if(rootPassword==null) {
// try pfexec, in the hope that the user has the permission
channel = new LocalLauncher(listener).launchChannel(
new String[]{"/usr/bin/pfexec", javaExe, "-jar", slaveJar},
listener.getLogger(), null, Collections.<String, String>emptyMap());
} else {
// try sudo with the given password
ProcessBuilder pb = new ProcessBuilder(javaExe,"-jar",slaveJar);
proc = EmbeddedSu.startWithSu(rootUsername, rootPassword, pb);
channel = new Channel("zfs migration thread", Computer.threadPoolForRemoting,
proc.getInputStream(), proc.getOutputStream(), listener.getLogger());
}
try {
return channel.call(task);
} finally {
channel.close();
if(proc!=null)
proc.destroy();
}
}
/**
* Called from the confirmation screen to actually initiate the migration.
*/
public void doStart(StaplerRequest req, StaplerResponse rsp) throws ServletException, IOException {
requirePOST();
public void doStart(StaplerRequest req, StaplerResponse rsp, @QueryParameter String username, @QueryParameter String password) throws ServletException, IOException {
requirePOST();
Hudson hudson = Hudson.getInstance();
hudson.checkPermission(Hudson.ADMINISTER);
final String datasetName;
ByteArrayOutputStream log = new ByteArrayOutputStream();
StreamTaskListener listener = new StreamTaskListener(log);
try {
datasetName = createZfsFileSystem(listener,username,password);
} catch (Exception e) {
e.printStackTrace(listener.error(e.getMessage()));
if (e instanceof ZFSException) {
ZFSException ze = (ZFSException) e;
if(ze.getCode()==ErrorCode.EZFS_PERM) {
// permission problem. ask the user to give us the root password
req.setAttribute("message",log.toString());
rsp.forward(this,"askRootPassword",req);
return;
}
}
// for other kinds of problems, report and bail out
req.setAttribute("pre",true);
sendError(log.toString(),req,rsp);
return;
}
// file system creation successful, so restart
hudson.servletContext.setAttribute("app",new HudsonIsRestarting());
// redirect the user to the manage page
rsp.sendRedirect2(req.getContextPath()+"/manage");
......@@ -153,8 +285,10 @@ public class ZFSInstaller extends AdministrativeMonitor {
LIBC.fcntl(i, F_SETFD,flags| FD_CLOEXEC);
}
// re-exec with the system property to indicate where to migrate the data to.
// the 2nd phase starts in the init method.
JavaVMArguments args = JavaVMArguments.current();
args.setSystemProperty(ZFSInstaller.class.getName(),"migrate");
args.setSystemProperty(ZFSInstaller.class.getName()+".migrate",datasetName);
Daemon.selfExec(args);
} catch (InterruptedException e) {
LOGGER.log(Level.SEVERE, "Restart failed",e);
......@@ -169,11 +303,12 @@ public class ZFSInstaller extends AdministrativeMonitor {
public static void init() {
List<AdministrativeMonitor> monitors = Hudson.getInstance().administrativeMonitors;
if("migrate".equals(System.getProperty(ZFSInstaller.class.getName()))) {
String migrationTarget = System.getProperty(ZFSInstaller.class.getName() + ".migrate");
if(migrationTarget!=null) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamTaskListener listener = new StreamTaskListener(new ForkOutputStream(System.out, out));
try {
if(migrate(listener)) {
if(migrate(listener,migrationTarget)) {
// completed successfully
monitors.add(new MigrationCompleteNotice());
return;
......@@ -199,10 +334,12 @@ public class ZFSInstaller extends AdministrativeMonitor {
*
* @param listener
* Log of migration goes here.
* @param target
* Dataset to move the data to.
* @return
* false if a migration failed.
*/
private static boolean migrate(TaskListener listener) throws IOException, InterruptedException {
private static boolean migrate(TaskListener listener, String target) throws IOException, InterruptedException {
PrintStream out = listener.getLogger();
File home = Hudson.getInstance().getRootDir();
......@@ -217,9 +354,8 @@ public class ZFSInstaller extends AdministrativeMonitor {
File tmpDir = Util.createTempDir();
// mount a new file system to a temporary location
String name = computeHudsonFileSystemName(zfs, zfs.roots().get(0));
out.println("Creating "+name);
ZFSFileSystem hudson = (ZFSFileSystem)zfs.create(name, ZFSType.FILESYSTEM);
out.println("Opening "+target);
ZFSFileSystem hudson = zfs.open(target, ZFSFileSystem.class);
hudson.setMountPoint(tmpDir);
hudson.setProperty("hudson:managed-by","hudson"); // mark this file system as "managed by Hudson"
hudson.mount();
......@@ -232,7 +368,7 @@ public class ZFSInstaller extends AdministrativeMonitor {
}
// unmount
out.println("Unmounting "+name);
out.println("Unmounting "+target);
hudson.unmount(MountFlags.MS_FORCE);
// move the original directory to the side
......@@ -247,11 +383,11 @@ public class ZFSInstaller extends AdministrativeMonitor {
if(!home.mkdir())
throw new IOException("Failed to create mount point "+home);
out.println("Mounting "+name);
out.println("Mounting "+target);
hudson.setMountPoint(home);
hudson.mount();
out.println("Sharing "+name);
out.println("Sharing "+target);
hudson.setProperty("sharesmb","on");
hudson.setProperty("sharenfs","on");
hudson.share();
......
......@@ -28,6 +28,7 @@ import com.sun.jna.StringArray;
import com.sun.jna.Pointer;
import com.sun.jna.Native;
import com.sun.jna.ptr.IntByReference;
import org.jvnet.libpam.impl.CLibrary.passwd;
/**
* GNU C library.
......@@ -51,6 +52,8 @@ public interface GNUCLibrary extends Library {
void perror(String msg);
String strerror(int errno);
passwd getpwuid(int uid);
int fcntl(int fd, int command);
int fcntl(int fd, int command, int flags);
......
<!--
The MIT License
Copyright (c) 2004-2009, Sun Microsystems, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<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">
<l:layout title="${%Permission Denied}">
<l:header />
<l:side-panel />
<l:main-panel>
<h1>
<img src="${imagesURL}/48x48/secure.gif" width="48" height="48" alt=""/>
${%Permission Denied}
</h1>
<div>
${%blurb}
<f:form method="post" action="start" enctype="multipart/form-data">
<f:entry title="${%Username}">
<f:textbox name="username" value="root" />
</f:entry>
<f:entry title="${%Password}">
<input type="password" name="password" class="setting-input" />
</f:entry>
<f:block>
<f:submit value="${%OK}" style="margin-top:1em;" />
</f:block>
</f:form>
<input type="button" value="${%See errors...}" onclick="showDetails()" id="button"/>
<pre id="details" style="display:none"><st:out value="${message}"/></pre>
<script>
function showDetails() {
$('button').style.display="none";
$('details').style.display="block";
}
</script>
</div>
</l:main-panel>
</l:layout>
</j:jelly>
\ No newline at end of file
# The MIT License
#
# Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
blurb=It appears that the current user account lacks necessary permissions to create a ZFS file system. \
Please provide the username and the password that's capable of doing this, such as root.
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册