├── .gitignore ├── .project ├── LICENSE ├── README.md ├── forker-assembly ├── .classpath ├── .gitignore ├── notes │ └── CHANGES ├── pom.xml └── src │ └── main │ └── assembly │ └── bin │ ├── README.txt │ └── bin.xml ├── forker-client ├── .gitignore ├── .project ├── README.md ├── pom.xml └── src │ └── main │ └── java │ ├── com │ └── sshtools │ │ └── forker │ │ └── client │ │ ├── AbstractOSProcess.java │ │ ├── DefaultNonBlockingProcessListener.java │ │ ├── EffectiveUser.java │ │ ├── EffectiveUserFactory.java │ │ ├── Forker.java │ │ ├── ForkerBuilder.java │ │ ├── ForkerConfiguration.java │ │ ├── ForkerProcess.java │ │ ├── ForkerProcessFactory.java │ │ ├── ForkerProcessListener.java │ │ ├── IEventProcessor.java │ │ ├── NonBlockingProcess.java │ │ ├── NonBlockingProcessFactory.java │ │ ├── NonBlockingProcessListener.java │ │ ├── OSCommand.java │ │ ├── PowerShellBuilder.java │ │ ├── ShellBuilder.java │ │ ├── impl │ │ ├── DefaultProcessFactory.java │ │ ├── LocalProcess.java │ │ ├── LocalProcessFactory.java │ │ ├── POpenProcess.java │ │ ├── POpenProcessFactory.java │ │ ├── SystemProcess.java │ │ ├── SystemProcessFactory.java │ │ ├── jna │ │ │ ├── osx │ │ │ │ └── LibKevent.java │ │ │ ├── posix │ │ │ │ ├── LibC.java │ │ │ │ ├── LibEpoll.java │ │ │ │ ├── LibJava10.java │ │ │ │ └── LibJava8.java │ │ │ └── win32 │ │ │ │ ├── Kernel32.java │ │ │ │ ├── WindowsAuthenticationTokens.java │ │ │ │ └── WindowsHelper.java │ │ ├── nonblocking │ │ │ ├── BaseEventProcessor.java │ │ │ ├── EpollEvent.java │ │ │ ├── NonBlockingBasePosixProcess.java │ │ │ ├── NonBlockingLinuxProcess.java │ │ │ ├── NonBlockingOsxProcess.java │ │ │ ├── NonBlockingWindowsProcess.java │ │ │ ├── ProcessCompletions.java │ │ │ ├── ProcessEpoll.java │ │ │ ├── ProcessKqueue.java │ │ │ ├── ReferenceCountedFileDescriptor.java │ │ │ └── WindowsCreateProcessEscape.java │ │ └── package-info.java │ │ ├── package-info.java │ │ └── ui │ │ ├── AskPass.java │ │ ├── AskPassConsole.java │ │ ├── WinRunAs.java │ │ └── package-info.java │ └── module-info.java ├── forker-common ├── .gitignore ├── .project ├── pom.xml └── src │ └── main │ └── java │ ├── com │ └── sshtools │ │ └── forker │ │ └── common │ │ ├── CSystem.java │ │ ├── Command.java │ │ ├── IO.java │ │ ├── OS.java │ │ ├── Priority.java │ │ ├── States.java │ │ ├── Userenv.java │ │ ├── Util.java │ │ ├── XAdvapi32.java │ │ ├── XKernel32.java │ │ ├── XWinsvc.java │ │ └── package-info.java │ └── module-info.java ├── forker-examples ├── .gitignore ├── .project ├── app.args ├── pom.xml ├── src │ └── main │ │ └── java │ │ ├── com │ │ └── sshtools │ │ │ └── forker │ │ │ └── examples │ │ │ ├── AdministratorElevate.java │ │ │ ├── ForkerWrapperHelper.java │ │ │ ├── NativeForkWrappedTest.java │ │ │ ├── NativeForkWrapperTest.java │ │ │ ├── NonBlocking.java │ │ │ ├── NonBlockingBiDir.java │ │ │ ├── NonBlockingShell.java │ │ │ ├── OSCommandElevate.java │ │ │ ├── PowerShellCommand.java │ │ │ ├── RestartAsAdministrator.java │ │ │ ├── RunAsUser.java │ │ │ ├── Shell.java │ │ │ ├── ShellAsUser.java │ │ │ ├── SimpleElevate.java │ │ │ ├── WrappedProcessTest.java │ │ │ ├── WrappedTest.java │ │ │ ├── WrapperJavascriptTest.java │ │ │ └── WrapperTest.java │ │ └── module-info.java └── wrapper-javascript-test.cfg.js ├── forker-pipes ├── .gitignore ├── .project ├── pom.xml └── src │ ├── main │ └── java │ │ ├── com │ │ └── sshtools │ │ │ └── forker │ │ │ └── pipes │ │ │ ├── DefaultPipeFactory.java │ │ │ ├── NamedPipeServerSocket.java │ │ │ ├── NamedPipeSocket.java │ │ │ ├── PipeFactory.java │ │ │ ├── Unix.java │ │ │ ├── UnixDomainServerSocket.java │ │ │ ├── UnixDomainSocket.java │ │ │ ├── package-info.java │ │ │ └── package.html │ │ └── module-info.java │ └── test │ └── java │ └── com │ └── sshtools │ └── forker │ └── pipes │ └── DefaultPipeFactoryTest.java ├── forker-pty ├── .gitignore ├── .project ├── README.md ├── pom.xml └── src │ └── main │ ├── java │ ├── com │ │ └── sshtools │ │ │ └── forker │ │ │ └── pty │ │ │ ├── PTYIO.java │ │ │ ├── PTYProcess.java │ │ │ ├── PTYProcessFactory.java │ │ │ └── package-info.java │ └── module-info.java │ └── resources │ └── META-INF │ └── services │ ├── com.sshtools.forker.client.ForkerProcessFactory │ └── com.sshtools.forker.common.IO ├── forker-services ├── .gitignore ├── .project ├── README.md ├── pom.xml └── src │ └── main │ └── java │ ├── com │ └── sshtools │ │ └── forker │ │ └── services │ │ ├── AbstractService.java │ │ ├── DefaultContext.java │ │ ├── ExtendedServiceStatus.java │ │ ├── Service.java │ │ ├── ServiceService.java │ │ ├── ServiceState.java │ │ ├── Services.java │ │ ├── ServicesContext.java │ │ ├── ServicesListener.java │ │ └── impl │ │ ├── AbstractServiceService.java │ │ ├── AutoServiceService.java │ │ ├── CompoundServicesService.java │ │ ├── InitctlServiceService.java │ │ ├── SysVServiceService.java │ │ ├── SystemDServiceService.java │ │ └── Win32ServiceService.java │ └── module-info.java ├── forker-updater-console ├── .gitignore ├── pom.xml └── src │ └── main │ ├── java │ ├── com │ │ └── sshtools │ │ │ └── forker │ │ │ └── updater │ │ │ └── console │ │ │ ├── AbstractConsoleHandler.java │ │ │ ├── ConsoleInstallHandler.java │ │ │ ├── ConsoleSystem.java │ │ │ └── ConsoleUpdateHandler.java │ └── module-info.java │ └── resources │ └── META-INF │ └── services │ ├── com.sshtools.forker.updater.InstallHandler │ └── com.sshtools.forker.updater.UpdateHandler ├── forker-updater-example ├── .gitignore ├── pom.xml └── src │ └── main │ ├── installer │ └── left-banner.png │ └── java │ ├── hello │ └── world │ │ └── HelloWorld.java │ └── module-info.java ├── forker-updater-maven-plugin ├── .gitignore ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── sshtools │ └── forker │ └── updater │ └── maven │ └── plugin │ ├── App.java │ ├── AppFile.java │ ├── BootstrapFile.java │ ├── ForkerUpdaterMojo.java │ └── SelfExtractingExecutableBuilder.java ├── forker-updater-swt ├── .gitignore ├── pom.xml └── src │ └── main │ ├── .gitignore │ ├── java │ ├── com │ │ └── sshtools │ │ │ └── forker │ │ │ └── updater │ │ │ └── swt │ │ │ ├── AbstractSWTHandler.java │ │ │ ├── SWTInstallHandler.java │ │ │ ├── SWTUninstallHandler.java │ │ │ └── SWTUpdateHandler.java │ └── module-info.java │ └── resources │ ├── META-INF │ └── services │ │ ├── com.sshtools.forker.updater.InstallHandler │ │ ├── com.sshtools.forker.updater.UninstallHandler │ │ └── com.sshtools.forker.updater.UpdateHandler │ └── com │ └── sshtools │ └── forker │ └── updater │ └── swt │ ├── Bootstrap.properties │ ├── Install.properties │ └── Update.properties ├── forker-updater ├── .gitignore ├── README.md ├── pom.xml └── src │ └── main │ └── java │ ├── com │ ├── sshtools │ │ └── forker │ │ │ └── updater │ │ │ ├── AbstractHandler.java │ │ │ ├── AbstractSession.java │ │ │ ├── AppManifest.java │ │ │ ├── DefaultConsoleInstallHandler.java │ │ │ ├── DefaultConsoleUninstallHandler.java │ │ │ ├── DefaultConsoleUpdateHandler.java │ │ │ ├── DesktopShortcut.java │ │ │ ├── Entry.java │ │ │ ├── Handler.java │ │ │ ├── InstallHandler.java │ │ │ ├── InstallSession.java │ │ │ ├── InstallerResults.java │ │ │ ├── Launcher.java │ │ │ ├── NotFatalException.java │ │ │ ├── Session.java │ │ │ ├── ThrottledInputStream.java │ │ │ ├── ThrottledOutputStream.java │ │ │ ├── UndoableOp.java │ │ │ ├── UninstallHandler.java │ │ │ ├── UninstallSession.java │ │ │ ├── Uninstaller.java │ │ │ ├── UpdateHandler.java │ │ │ ├── UpdateSession.java │ │ │ ├── Updater.java │ │ │ └── test │ │ │ ├── InstallTest.java │ │ │ ├── UninstallTest.java │ │ │ └── UpdateTest.java │ └── sshtoos │ │ └── forker │ │ └── updater │ │ └── rpc │ │ ├── UpdateServicesMBean.java │ │ └── UpdaterServices.java │ └── module-info.java ├── forker-wrapped ├── .gitignore ├── pom.xml └── src │ └── main │ └── java │ ├── com │ └── sshtools │ │ └── forker │ │ └── wrapped │ │ ├── Wrapped.java │ │ └── WrappedMXBean.java │ └── module-info.java ├── forker-wrapper-plugin-scripts ├── .gitignore ├── README.md ├── pom.xml └── src │ └── main │ ├── java │ ├── com │ │ └── sshtools │ │ │ └── forker │ │ │ └── wrapper │ │ │ └── plugin │ │ │ └── scripts │ │ │ └── ScriptWrapperPlugin.java │ └── module-info.java │ └── resources │ └── META-INF │ └── services │ └── com.sshtools.forker.wrapper.WrapperPlugin ├── forker-wrapper ├── .gitignore ├── .project ├── README.md ├── pom.xml └── src │ └── main │ ├── java │ ├── com │ │ └── sshtools │ │ │ └── forker │ │ │ └── wrapper │ │ │ ├── AbstractWrapper.java │ │ │ ├── ArgMode.java │ │ │ ├── ArgfileMode.java │ │ │ ├── Argument.java │ │ │ ├── ArgumentType.java │ │ │ ├── Configuration.java │ │ │ ├── DisplayMode.java │ │ │ ├── ForkerWrapper.java │ │ │ ├── ForkerWrapperMXBean.java │ │ │ ├── JVM.java │ │ │ ├── KeyValuePair.java │ │ │ ├── LazyLogStream.java │ │ │ ├── Replace.java │ │ │ ├── SinkOutputStream.java │ │ │ ├── Win32ServiceBase.java │ │ │ ├── WrappedApplication.java │ │ │ ├── WrapperIO.java │ │ │ ├── WrapperPlugin.java │ │ │ ├── WrapperProcessFactory.java │ │ │ └── package-info.java │ └── module-info.java │ └── resources │ └── META-INF │ └── services │ ├── com.sshtools.forker.client.ForkerProcessFactory │ └── com.sshtools.forker.common.IO ├── pom.xml ├── src └── site │ └── site.xml └── templates └── APACHE-2.txt /.gitignore: -------------------------------------------------------------------------------- 1 | /.settings/ 2 | /.metadata/ 3 | /target/ 4 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | forker 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.m2e.core.maven2Builder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.m2e.core.maven2Nature 16 | 17 | 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Forker 2 | 3 | Forker is a set of utilities and helpers for executing operating system commands from Java. It can be used in a number of ways :- 4 | 5 | ## Forker Client 6 | 7 | Forker Client provides a set of utilities, *OSCommand*, and the ProcessBuilder replacement *ForkerBuilder*. See [forker-client/README.md](forker-client/README.md) for more information. 8 | 9 | ### OSCommand 10 | 11 | Generally this is a simply case of a single call and Forker will deal with checking the exit code and redirecting or capturing standard output and error output. OS commands can be run either as the current user or as an administrator. 12 | 13 | ### ForkerBuilder 14 | The replacement to ProcessBuilder, ForkerBuilder uses different methods depending on the type of I/O used, and also allows processes to be run as an administrator (or any other user). Depending on whether input, output, or I/O is needed (which should be provided as hint to the API), popen, system or a standard process will be used. 15 | 16 | A very useful feature is the ability to use non-blocking 17 | I/O on supported platforms, which can make for cleaner code and much better memory usage when launching lots of processes. 18 | This feature also uses *vfork* on Linux, which doesn't have the high fork cost associated with standard Java. 19 | 20 | ### Pseudo Terminal Support 21 | 22 | Execute commands and shells with a pseudo terminal (or 'pty'), providing command line editing and full interactive I/O. This is achieved using Pty4J. This could be used for example to create a Java based telnet or SSH terminal server. 23 | 24 | See [forker-pty/README.md](forker-pty/README.md) 25 | 26 | ## Forker Wrapper 27 | 28 | A 'wrapper' to execute services in Java. Similar to JSW (Java Service Wrapper) and YAJSW, Forker Wrapper can be used to launch processes in the background, track the process ID, capture output to log, automatically restart a hung or crashed JVM and more. 29 | Forker Wrapper is lightweight and powerful. 30 | 31 | See [forker-wrapper/README.md](forker-wrapper/README.md) 32 | 33 | ## Forker Updater 34 | 35 | Builds on Forker Wrapper to provide an Install / Update system. Currently Linux only for all features, Windows support in progress. 36 | 37 | See [forker-updater/README.md](forker-updater/README.md) 38 | 39 | There are several modules that cover Updater's functionality. 40 | 41 | * forker-updater - The core 42 | * forker-updater-console - Plugin for console install / updates 43 | * forker-updater-swt - Plugin for GUI install / updates (WIP) 44 | * forker-updater-example - An example installable and updateable application. 45 | * forker-updater-maven-plugin - Build updater images and bootstrap installers. 46 | 47 | ## Forker Services 48 | 49 | This allows you to control local system services in a cross platform way. Support is provided for Linux and Windows 50 | currently, and allows enumerating of services and their states, as well as control services and configuring their start on boot setting. 51 | 52 | See [forker-services/README.md](forker-services/README.md). 53 | 54 | ## Forker Pipes 55 | 56 | Cross platform API to pipe-like OS specific streams (i.e. Unix Sockets on Linux, Named Pipes on 57 | Windows). 58 | 59 | See [forker-pipes/README.md](forker-pipes/README.md). 60 | -------------------------------------------------------------------------------- /forker-assembly/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /forker-assembly/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | *.project 3 | /bin/ 4 | /.settings/ 5 | -------------------------------------------------------------------------------- /forker-assembly/notes/CHANGES: -------------------------------------------------------------------------------- 1 | ================= 2 | = CHANGE LOG = 3 | ================= 4 | 5 | Forker 1.0 - Initial release 6 | 7 | Features 8 | o 'Forker Daemon' to provide a small run-time environment for launching processes. 9 | o Support for POpen and System process types. 10 | o Process execution utilities. 11 | o Rudimentary support for privilege escalation. 12 | 13 | ----------------- 14 | 15 | Forker 1.1 - Privilege escalation 16 | 17 | Features 18 | o Better support for privilege escalation using OS specific methods such as gksudo, 19 | sudo. Privilege escalation front end for systems that don't expose one (OS X) 20 | o Better PTY support using Pty4J. 21 | o Expanded utilities. 22 | 23 | ----------------- 24 | 25 | Forker 1.2 - Wrapper 26 | 27 | Features 28 | o Introduction of Forker Wrapper, a replacement for other Java wrapper libraries. 29 | o More secure Forker Daemon, allowing secured isolated instances. 30 | o Now completely documented using JavaDoc. 31 | o Pluggable architecture 32 | 33 | ----------------- 34 | 35 | Forker 1.3 - TBC 36 | 37 | Bug Fixes 38 | o setProcname a.k.a prctl is not available on OSX so don't try and call it. 39 | o setProcname throws UnsupportedOperationException breaking cross-platform compatibility. 40 | o ForkerWrapper main should capture any exception and log. 41 | o Ensure all sockets are connected on 127.0.0.1 loopback as previous use of getLocalHost appears inconsistent and caused execution failures. 42 | o Privilege escalation left 'sapaXXXXX.sh' scripts in /tmp everytime sudo/su/gksudo/gksu 43 | might be used until the JVM exits. These could fill /tmp entirely on long running systems. 44 | 45 | ================= 46 | = HISTORY = 47 | ================= 48 | 49 | -------------------------------------------------------------------------------- /forker-assembly/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | forker-assembly 4 | Forker Assembly 5 | This module creates a distributable assembly of Forker. 6 | 7 | 8 | com.sshtools 9 | forker 10 | 1.8 11 | .. 12 | 13 | 14 | 15 | 16 | com.sshtools 17 | forker-common 18 | ${project.version} 19 | jar 20 | compile 21 | 22 | 23 | com.sshtools 24 | forker-common 25 | ${project.version} 26 | javadoc 27 | 28 | 29 | 30 | com.sshtools 31 | forker-client 32 | ${project.version} 33 | jar 34 | compile 35 | 36 | 37 | com.sshtools 38 | forker-client 39 | ${project.version} 40 | javadoc 41 | 42 | 43 | 44 | com.sshtools 45 | forker-wrapper 46 | ${project.version} 47 | jar 48 | compile 49 | 50 | 51 | com.sshtools 52 | forker-wrapper 53 | ${project.version} 54 | javadoc 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | maven-assembly-plugin 63 | 2.2.1 64 | 65 | false 66 | gnu 67 | 68 | src/main/assembly/bin/bin.xml 69 | 70 | forker-${project.version} 71 | 72 | 73 | 74 | make-assembly 75 | package 76 | 77 | single 78 | 79 | 80 | 81 | 82 | 83 | org.apache.maven.plugins 84 | maven-site-plugin 85 | ${maven.site.plugin} 86 | 87 | true 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /forker-assembly/src/main/assembly/bin/README.txt: -------------------------------------------------------------------------------- 1 | ==== 2 | Copyright © 2015 - 2021 SSHTOOLS Limited (support@sshtools.com) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ==== 16 | 17 | Forker 18 | ====== 19 | 20 | See https://github.com/sshtools/forker -------------------------------------------------------------------------------- /forker-assembly/src/main/assembly/bin/bin.xml: -------------------------------------------------------------------------------- 1 | 18 | 22 | bin 23 | 24 | zip 25 | 26 | 27 | 28 | false 29 | 30 | 31 | forker-${project.version} 32 | 33 | ../LICENSE 34 | 35 | 36 | 37 | 38 | 39 | forker-${project.version}/examples 40 | ../forker-examples/src/main/java/ 41 | 42 | *.java 43 | 44 | 45 | 46 | 47 | forker-${project.version} 48 | notes 49 | 50 | * 51 | 52 | 53 | 54 | 55 | 56 | 57 | src/main/assembly/bin/README.txt 58 | forker-${project.version} 59 | 60 | 61 | 62 | 63 | 64 | 65 | *:javadoc 66 | 67 | true 68 | true 69 | false 70 | forker-${project.version}/apidocs/${artifact.artifactId} 71 | 72 | 73 | 74 | com.sshtools:forker-assembly 75 | *:javadoc 76 | 77 | false 78 | true 79 | true 80 | forker-${project.version} 81 | 82 | 83 | -------------------------------------------------------------------------------- /forker-client/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /.settings/ 3 | *.classpath 4 | /bin/ 5 | -------------------------------------------------------------------------------- /forker-client/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | forker-client 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.wst.common.project.facet.core.builder 10 | 11 | 12 | 13 | 14 | org.eclipse.jdt.core.javabuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.wst.validation.validationbuilder 20 | 21 | 22 | 23 | 24 | org.eclipse.m2e.core.maven2Builder 25 | 26 | 27 | 28 | 29 | 30 | org.eclipse.jem.workbench.JavaEMFNature 31 | org.eclipse.wst.common.modulecore.ModuleCoreNature 32 | org.eclipse.jdt.core.javanature 33 | org.eclipse.m2e.core.maven2Nature 34 | org.eclipse.wst.common.project.facet.core.nature 35 | 36 | 37 | -------------------------------------------------------------------------------- /forker-client/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | forker-client 6 | Forker Client 7 | This is client side library for forker, and the one you most likely want to use in your own applications. 8 | 9 | It provides the ProcessBuilder replacement, ForkerBuilder, as well the helpers in OSCommand. ForkerBuilder is 10 | capable of launching processes using non-blocking I/O or attached to a PTY. It has many additional features 11 | not available in the standard process builder including permission elevation, daemonize, processor affinity 12 | and more. 13 | 14 | Parts of the non-blocking API are based on NuProcess (https://github.com/brettwooldridge/NuProcess) 15 | 16 | com.sshtools 17 | forker 18 | 1.8 19 | .. 20 | 21 | 22 | 23 | src/main/java 24 | src/test/java 25 | target/classes 26 | target/test-classes 27 | 28 | 29 | . 30 | src/main/resources 31 | 32 | 33 | 34 | 35 | . 36 | src/test/resources 37 | 38 | 39 | 40 | 41 | 42 | 43 | ${project.groupId} 44 | forker-common 45 | ${project.version} 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /forker-client/src/main/java/com/sshtools/forker/client/DefaultNonBlockingProcessListener.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 - 2021 SSHTOOLS Limited (support@sshtools.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sshtools.forker.client; 17 | 18 | import java.nio.ByteBuffer; 19 | 20 | /** 21 | * Default implementation of a {@link NonBlockingProcessListener}. 22 | */ 23 | public class DefaultNonBlockingProcessListener implements NonBlockingProcessListener { 24 | @Override 25 | public void onError(Exception exception, NonBlockingProcess process, boolean existing) { 26 | } 27 | 28 | @Override 29 | public void onExit(int exitCode, NonBlockingProcess process) { 30 | } 31 | 32 | @Override 33 | public void onStdout(NonBlockingProcess process, ByteBuffer buffer, boolean closed) { 34 | } 35 | 36 | @Override 37 | public void onStderr(NonBlockingProcess process, ByteBuffer buffer, boolean closed) { 38 | } 39 | 40 | @Override 41 | public boolean onStdinReady(NonBlockingProcess process, ByteBuffer buffer) { 42 | return false; 43 | } 44 | 45 | @Override 46 | public void onStart(NonBlockingProcess process) { 47 | } 48 | 49 | @Override 50 | public void onStarted(NonBlockingProcess process) { 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /forker-client/src/main/java/com/sshtools/forker/client/EffectiveUser.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 - 2021 SSHTOOLS Limited (support@sshtools.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sshtools.forker.client; 17 | 18 | import com.sshtools.forker.common.Command; 19 | 20 | /** 21 | * In order to be able to raise privileges to administrator, or to run a process 22 | * as another user, an instance of an "Effective User" must be created. 23 | * Implementations are responsible for setting up the process to be elevated. 24 | * Forker provides a set a default implementations that should be suitable for 25 | * most needs in {@link EffectiveUserFactory.DefaultEffectiveUserFactory}. 26 | * 27 | * @see EffectiveUserFactory 28 | * 29 | */ 30 | public interface EffectiveUser { 31 | /** 32 | * De-configure the command and/or process such that it will no longer be 33 | * run as an administrator or different, but will run as the current user. 34 | * 35 | * @param builder 36 | * builder 37 | * @param process 38 | * process 39 | * @param command 40 | * command 41 | */ 42 | void descend(ForkerBuilder builder, Process process, Command command); 43 | 44 | /** 45 | * Alter the command and/or process such that it will be launched using the 46 | * user this object represents. 47 | * 48 | * @param builder 49 | * builder 50 | * @param process 51 | * process 52 | * @param command 53 | * command 54 | */ 55 | void elevate(ForkerBuilder builder, Process process, Command command); 56 | } -------------------------------------------------------------------------------- /forker-client/src/main/java/com/sshtools/forker/client/ForkerProcessFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 - 2021 SSHTOOLS Limited (support@sshtools.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sshtools.forker.client; 17 | 18 | import java.io.IOException; 19 | 20 | /** 21 | * Interface to be implemented by pluggable elements that handle actual process 22 | * creation. Each factory will be called in turn until one creates a process. 23 | * New factories are registered as a standard Java server (META-INF/services). * 24 | */ 25 | public interface ForkerProcessFactory { 26 | 27 | /** 28 | * Create a new process. If this factory is not appropriate for the builder 29 | * configuration it should return null. If an error occurs it 30 | * should throw an exception. 31 | * 32 | * @param builder builder 33 | * @param listener listener 34 | * @return process 35 | * @throws IOException on any error 36 | */ 37 | ForkerProcess createProcess(ForkerBuilder builder, ForkerProcessListener listener) throws IOException; 38 | } 39 | -------------------------------------------------------------------------------- /forker-client/src/main/java/com/sshtools/forker/client/ForkerProcessListener.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 - 2021 SSHTOOLS Limited (support@sshtools.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sshtools.forker.client; 17 | 18 | /** 19 | * Interface to be implemented by listeners. Currently only used for 20 | * {@link NonBlockingProcess}. 21 | * 22 | */ 23 | public interface ForkerProcessListener { 24 | } 25 | -------------------------------------------------------------------------------- /forker-client/src/main/java/com/sshtools/forker/client/IEventProcessor.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 - 2021 SSHTOOLS Limited (support@sshtools.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sshtools.forker.client; 17 | 18 | import java.util.concurrent.CyclicBarrier; 19 | 20 | import com.sshtools.forker.client.impl.nonblocking.NonBlockingBasePosixProcess; 21 | 22 | /** 23 | * This class is internal. 24 | * 25 | * @author Brett Wooldridge 26 | * 27 | * @param a subclass of {@link NonBlockingBasePosixProcess} 28 | */ 29 | public interface IEventProcessor extends Runnable { 30 | /** 31 | * Check whether the processor instance is currently running, and if not set 32 | * it to the running state. 33 | * 34 | * @return true if the processor was already running, false otherwise 35 | */ 36 | boolean checkAndSetRunning(); 37 | 38 | /** 39 | * Get the CyclicBarrier that this thread should join, along with the 40 | * NuProcess start thread that is starting this processor. Used to cause the 41 | * NonBlockingOsxProcess to wait until the processor is up and running before returning 42 | * from start() to the user. 43 | * 44 | * @return the CyclicBarrier to join to ensure the processor is running 45 | * before registering processes with it 46 | */ 47 | CyclicBarrier getSpawnBarrier(); 48 | 49 | /** 50 | * Register a process for handling by the event processor. 51 | * 52 | * @param process the process to register 53 | */ 54 | void registerProcess(T process); 55 | 56 | /** 57 | * Express that the client desires to write data into the STDIN stream as 58 | * soon as possible. 59 | * 60 | * @param process the process that wants to write to STDIN 61 | */ 62 | void queueWrite(T process); 63 | 64 | /** 65 | * Called by the event-loop to process asynchronous I/O events. 66 | * 67 | * @return true if events were processed, false if an idle timeout occurred 68 | */ 69 | boolean process(); 70 | 71 | /** 72 | * Cleanly shutdown the processors and cleanup all resources. 73 | */ 74 | void shutdown(); 75 | 76 | /** 77 | * Want write 78 | * 79 | * @param process process 80 | */ 81 | void wantWrite(NonBlockingProcess process); 82 | } 83 | -------------------------------------------------------------------------------- /forker-client/src/main/java/com/sshtools/forker/client/PowerShellBuilder.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 - 2021 SSHTOOLS Limited (support@sshtools.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sshtools.forker.client; 17 | 18 | import java.io.IOException; 19 | import java.util.List; 20 | 21 | import com.sshtools.forker.common.IO; 22 | import com.sun.jna.Platform; 23 | 24 | /** 25 | * Specialised version of {@link ShellBuilder} that builds a process appropriate 26 | * for running PowerShell commands. 27 | */ 28 | public class PowerShellBuilder extends ShellBuilder { 29 | { 30 | if (Platform.isWindows()) 31 | shell("powershell.exe"); 32 | else 33 | shell("pwsh"); 34 | 35 | io(IO.IO); 36 | redirectErrorStream(true); 37 | } 38 | 39 | /** 40 | * Constructor. 41 | * 42 | * @param configuration configuration 43 | * @param command command 44 | */ 45 | public PowerShellBuilder(ForkerConfiguration configuration, List command) { 46 | super(configuration, command); 47 | } 48 | 49 | /** 50 | * Constructor. 51 | * 52 | * @param configuration configuration 53 | * @param command command 54 | */ 55 | public PowerShellBuilder(ForkerConfiguration configuration, String... command) { 56 | super(configuration, command); 57 | } 58 | 59 | /** 60 | * Constructor. 61 | * 62 | * @param command command 63 | */ 64 | public PowerShellBuilder(List command) { 65 | super(command); 66 | } 67 | 68 | /** 69 | * Constructor. 70 | * 71 | * @param command command 72 | */ 73 | public PowerShellBuilder(String... command) { 74 | super(command); 75 | } 76 | 77 | @Override 78 | public

P start(ForkerProcessListener listener) throws IOException { 79 | List a = command(); 80 | if(a.size() > 0) { 81 | a.add(0, "-Command"); 82 | } 83 | return super.start(listener); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /forker-client/src/main/java/com/sshtools/forker/client/impl/DefaultProcessFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 - 2021 SSHTOOLS Limited (support@sshtools.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sshtools.forker.client.impl; 17 | 18 | import java.io.IOException; 19 | 20 | import com.sshtools.forker.client.ForkerBuilder; 21 | import com.sshtools.forker.client.ForkerProcess; 22 | import com.sshtools.forker.client.ForkerProcessFactory; 23 | import com.sshtools.forker.client.ForkerProcessListener; 24 | import com.sshtools.forker.client.NonBlockingProcessListener; 25 | import com.sshtools.forker.common.IO; 26 | 27 | /** 28 | * Create a {@link LocalProcess} if requested to explicity do so. 29 | * 30 | */ 31 | public class DefaultProcessFactory implements ForkerProcessFactory { 32 | 33 | @Override 34 | public ForkerProcess createProcess(ForkerBuilder builder, ForkerProcessListener listener) throws IOException { 35 | if (builder.io() == IO.DEFAULT) { 36 | if(listener instanceof NonBlockingProcessListener) { 37 | throw new IllegalArgumentException(String.format("%s is not supported by %s, is your I/O mode set correctly (see %s.io(%s))", listener.getClass(), getClass(), ForkerBuilder.class, IO.class)); 38 | } 39 | return new LocalProcess(builder); 40 | } 41 | return null; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /forker-client/src/main/java/com/sshtools/forker/client/impl/LocalProcessFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 - 2021 SSHTOOLS Limited (support@sshtools.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sshtools.forker.client.impl; 17 | 18 | import java.io.IOException; 19 | 20 | import com.sshtools.forker.client.ForkerBuilder; 21 | import com.sshtools.forker.client.ForkerProcess; 22 | import com.sshtools.forker.client.ForkerProcessFactory; 23 | import com.sshtools.forker.client.ForkerProcessListener; 24 | 25 | /** 26 | * Creates {@link LocalProcess} as a fallback. Usually this factory comes last 27 | * in the list. 28 | */ 29 | public class LocalProcessFactory implements ForkerProcessFactory { 30 | 31 | @Override 32 | public ForkerProcess createProcess(ForkerBuilder builder, ForkerProcessListener listener) throws IOException { 33 | // Finally always fallback to a standard local process 34 | LocalProcess localProcess = new LocalProcess(builder); 35 | return localProcess; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /forker-client/src/main/java/com/sshtools/forker/client/impl/POpenProcessFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 - 2021 SSHTOOLS Limited (support@sshtools.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sshtools.forker.client.impl; 17 | 18 | import java.io.IOException; 19 | 20 | import com.sshtools.forker.client.ForkerBuilder; 21 | import com.sshtools.forker.client.ForkerProcess; 22 | import com.sshtools.forker.client.ForkerProcessFactory; 23 | import com.sshtools.forker.client.ForkerProcessListener; 24 | import com.sshtools.forker.client.NonBlockingProcessListener; 25 | import com.sshtools.forker.common.IO; 26 | import com.sshtools.forker.common.OS; 27 | 28 | /** 29 | * Creates {@link POpenProcess}. 30 | */ 31 | public class POpenProcessFactory implements ForkerProcessFactory { 32 | 33 | @Override 34 | public ForkerProcess createProcess(ForkerBuilder builder, ForkerProcessListener listener) throws IOException { 35 | if (OS.isUnix() && (builder.io() == IO.INPUT || builder.io() == IO.OUTPUT)) { 36 | // We need either input, or output, but not both, so use popen 37 | if(listener instanceof NonBlockingProcessListener) { 38 | throw new IllegalArgumentException(String.format("%s is not supported by %s, is your I/O mode set correctly (see %s.io(%s))", listener.getClass(), getClass(), ForkerBuilder.class, IO.class)); 39 | } 40 | return new POpenProcess(builder); 41 | } 42 | return null; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /forker-client/src/main/java/com/sshtools/forker/client/impl/SystemProcess.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 - 2021 SSHTOOLS Limited (support@sshtools.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sshtools.forker.client.impl; 17 | 18 | import java.io.InputStream; 19 | import java.io.OutputStream; 20 | 21 | import com.sshtools.forker.client.AbstractOSProcess; 22 | import com.sshtools.forker.client.ForkerBuilder; 23 | import com.sshtools.forker.common.CSystem; 24 | 25 | /** 26 | * Uses the C call system(command). This method doesn't support any I/O 27 | * streams, and internally may still cause a fork. 28 | */ 29 | public class SystemProcess extends AbstractOSProcess { 30 | 31 | private Thread thread; 32 | private int exitValue; 33 | 34 | /** 35 | * Constructor 36 | * 37 | * @param builder 38 | * builder 39 | */ 40 | public SystemProcess(final ForkerBuilder builder) { 41 | thread = new Thread("SystemProcess" + builder.command()) { 42 | public void run() { 43 | exitValue = CSystem.INSTANCE.system(buildCommand(builder)); 44 | }; 45 | }; 46 | thread.start(); 47 | } 48 | 49 | @Override 50 | public OutputStream getOutputStream() { 51 | throw new UnsupportedOperationException(); 52 | } 53 | 54 | @Override 55 | public InputStream getInputStream() { 56 | throw new UnsupportedOperationException(); 57 | } 58 | 59 | @Override 60 | public InputStream getErrorStream() { 61 | throw new UnsupportedOperationException(); 62 | } 63 | 64 | @Override 65 | public int waitFor() throws InterruptedException { 66 | thread.join(); 67 | return exitValue; 68 | } 69 | 70 | @Override 71 | public int exitValue() { 72 | return exitValue; 73 | } 74 | 75 | @Override 76 | public void destroy() { 77 | throw new UnsupportedOperationException(); 78 | } 79 | 80 | } -------------------------------------------------------------------------------- /forker-client/src/main/java/com/sshtools/forker/client/impl/SystemProcessFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 - 2021 SSHTOOLS Limited (support@sshtools.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sshtools.forker.client.impl; 17 | 18 | import java.io.IOException; 19 | 20 | import com.sshtools.forker.client.ForkerBuilder; 21 | import com.sshtools.forker.client.ForkerProcess; 22 | import com.sshtools.forker.client.ForkerProcessFactory; 23 | import com.sshtools.forker.client.ForkerProcessListener; 24 | import com.sshtools.forker.client.NonBlockingProcessListener; 25 | import com.sshtools.forker.common.IO; 26 | import com.sshtools.forker.common.OS; 27 | 28 | /** 29 | * Creates a {@link SystemProcess}. 30 | * 31 | */ 32 | public class SystemProcessFactory implements ForkerProcessFactory { 33 | 34 | @Override 35 | public ForkerProcess createProcess(ForkerBuilder builder, ForkerProcessListener listener) throws IOException { 36 | if (OS.isUnix() && builder.io() == IO.SINK) { 37 | 38 | if(listener instanceof NonBlockingProcessListener) { 39 | throw new IllegalArgumentException(String.format("%s is not supported by %s, is your I/O mode set correctly (see %s.io(%s))", listener.getClass(), getClass(), ForkerBuilder.class, IO.class)); 40 | } 41 | /* 42 | * We don't need any input or output, so can just start using 43 | * 'system' call which just blocks 44 | */ 45 | return new SystemProcess(builder); 46 | } 47 | return null; 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /forker-client/src/main/java/com/sshtools/forker/client/impl/jna/posix/LibEpoll.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 - 2021 SSHTOOLS Limited (support@sshtools.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sshtools.forker.client.impl.jna.posix; 17 | 18 | import com.sun.jna.Native; 19 | import com.sun.jna.Platform; 20 | import com.sun.jna.Pointer; 21 | 22 | /** 23 | * @author Brett Wooldridge 24 | */ 25 | public class LibEpoll { 26 | static { 27 | Native.register(Platform.C_LIBRARY_NAME); 28 | } 29 | 30 | /** 31 | * @param signal 32 | * @return status 33 | */ 34 | public static native int sigignore(int signal); 35 | 36 | /** 37 | * @param size 38 | * @return status 39 | */ 40 | public static native int epoll_create(int size); 41 | 42 | /** 43 | * @param epfd 44 | * @param op 45 | * @param fd 46 | * @param event 47 | * @return status 48 | */ 49 | public static native int epoll_ctl(int epfd, int op, int fd, Pointer event); 50 | 51 | /** 52 | * We only ever call this API with maxevents=1. However, if calling with 53 | * maxevents > 1, care must be taken to ensure that the "events" Pointer 54 | * actually points to a contiguous block of memory large enough to handle 55 | * maxevents number of EpollEvent mappings. 56 | * 57 | * EpollEvent would likely need to be updated to add a convenience method 58 | * that allocates a block of memory and returns an array of EpollEvents 59 | * mapped into it. The EpollEvent.getPointer() of the first array element 60 | * could then be passed to this API. 61 | * 62 | * @param epfd epfd 63 | * @param events events 64 | * @param maxevents max events 65 | * @param timeout 66 | * @return status 67 | */ 68 | public static native int epoll_wait(int epfd, Pointer events, int maxevents, int timeout); 69 | 70 | /** 71 | * 72 | */ 73 | public static final int SIGPIPE = 13; 74 | /** 75 | * Add a file descriptor to the interface. from /usr/include/sys/epoll.h 76 | */ 77 | public static final int EPOLL_CTL_ADD = 1; 78 | /** 79 | * Remove a file descriptor from the interface. 80 | */ 81 | public static final int EPOLL_CTL_DEL = 2; 82 | /** 83 | * Change file descriptor epoll_event structure. 84 | */ 85 | public static final int EPOLL_CTL_MOD = 3; 86 | /** 87 | * 88 | */ 89 | public static final int EPOLLIN = 0x001; 90 | /** 91 | * 92 | */ 93 | public static final int EPOLLOUT = 0x004; 94 | /** 95 | * 96 | */ 97 | public static final int EPOLLERR = 0x008; 98 | /** 99 | * 100 | */ 101 | public static final int EPOLLHUP = 0x010; 102 | /** 103 | * 104 | */ 105 | public static final int EPOLLRDHUP = 0x2000; 106 | /** 107 | * 108 | */ 109 | public static final int EPOLLONESHOT = (1 << 30); 110 | } 111 | -------------------------------------------------------------------------------- /forker-client/src/main/java/com/sshtools/forker/client/impl/jna/posix/LibJava10.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 - 2021 SSHTOOLS Limited (support@sshtools.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sshtools.forker.client.impl.jna.posix; 17 | 18 | import java.util.HashMap; 19 | import java.util.Map; 20 | 21 | import com.sshtools.forker.client.impl.nonblocking.NonBlockingBasePosixProcess; 22 | import com.sun.jna.JNIEnv; 23 | import com.sun.jna.Library; 24 | import com.sun.jna.Native; 25 | import com.sun.jna.NativeLibrary; 26 | import com.sun.jna.Platform; 27 | 28 | /** 29 | */ 30 | public class LibJava10 { 31 | static { 32 | Map options = new HashMap<>(); 33 | options.put(Library.OPTION_ALLOW_OBJECTS, Boolean.TRUE); 34 | if (Platform.isMac()) { 35 | Native.register(NativeLibrary.getProcess(options)); 36 | } else { 37 | Native.register(NativeLibrary.getInstance("java", options)); 38 | } 39 | Java_java_lang_ProcessImpl_init(JNIEnv.CURRENT, NonBlockingBasePosixProcess.class); 40 | } 41 | 42 | /** 43 | * @param jniEnv 44 | * @param clazz 45 | */ 46 | public static native void Java_java_lang_ProcessImpl_init(JNIEnv jniEnv, Object clazz); 47 | 48 | /** 49 | * JNIEXPORT jint JNICALL Java_java_lang_ProcessImpl_forkAndExec(JNIEnv 50 | * *env, jobject process, jint mode, jbyteArray helperpath, jbyteArray prog, 51 | * jbyteArray argBlock, jint argc, jbyteArray envBlock, jint envc, 52 | * jbyteArray dir, jintArray std_fds, jboolean redirectErrorStream) 53 | * 54 | * @param jniEnv 55 | * @param process 56 | * @param mode 57 | * @param helperpath 58 | * @param prog 59 | * @param argBlock 60 | * @param argc 61 | * @param envBlock 62 | * @param envc 63 | * @param dir 64 | * @param fds 65 | * @param redirectErrorStream 66 | * @return the PID of the process 67 | */ 68 | public static native int Java_java_lang_ProcessImpl_forkAndExec(JNIEnv jniEnv, Object process, int mode, Object helperpath, 69 | Object prog, Object argBlock, int argc, Object envBlock, int envc, Object dir, Object fds, byte redirectErrorStream); 70 | } 71 | -------------------------------------------------------------------------------- /forker-client/src/main/java/com/sshtools/forker/client/impl/jna/posix/LibJava8.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 - 2021 SSHTOOLS Limited (support@sshtools.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sshtools.forker.client.impl.jna.posix; 17 | 18 | import java.util.HashMap; 19 | import java.util.Map; 20 | 21 | import com.sshtools.forker.client.impl.nonblocking.NonBlockingBasePosixProcess; 22 | import com.sun.jna.JNIEnv; 23 | import com.sun.jna.Library; 24 | import com.sun.jna.Native; 25 | import com.sun.jna.NativeLibrary; 26 | import com.sun.jna.Platform; 27 | 28 | /** 29 | */ 30 | public class LibJava8 { 31 | static { 32 | Map options = new HashMap<>(); 33 | options.put(Library.OPTION_ALLOW_OBJECTS, Boolean.TRUE); 34 | if (Platform.isMac()) { 35 | Native.register(NativeLibrary.getProcess(options)); 36 | } else { 37 | Native.register(NativeLibrary.getInstance("java", options)); 38 | } 39 | Java_java_lang_UNIXProcess_init(JNIEnv.CURRENT, NonBlockingBasePosixProcess.class); 40 | } 41 | 42 | /** 43 | * @param jniEnv 44 | * @param clazz 45 | */ 46 | public static native void Java_java_lang_UNIXProcess_init(JNIEnv jniEnv, Object clazz); 47 | 48 | /** 49 | * JNIEXPORT jint JNICALL Java_java_lang_UNIXProcess_forkAndExec(JNIEnv 50 | * *env, jobject process, jint mode, jbyteArray helperpath, jbyteArray prog, 51 | * jbyteArray argBlock, jint argc, jbyteArray envBlock, jint envc, 52 | * jbyteArray dir, jintArray std_fds, jboolean redirectErrorStream) 53 | * 54 | * @param jniEnv 55 | * @param process 56 | * @param mode 57 | * @param helperpath 58 | * @param prog 59 | * @param argBlock 60 | * @param argc 61 | * @param envBlock 62 | * @param envc 63 | * @param dir 64 | * @param fds 65 | * @param redirectErrorStream 66 | * 67 | * @return the PID of the process 68 | */ 69 | public static native int Java_java_lang_UNIXProcess_forkAndExec(JNIEnv jniEnv, Object process, int mode, Object helperpath, 70 | Object prog, Object argBlock, int argc, Object envBlock, int envc, Object dir, Object fds, byte redirectErrorStream); 71 | } 72 | -------------------------------------------------------------------------------- /forker-client/src/main/java/com/sshtools/forker/client/impl/jna/win32/WindowsAuthenticationTokens.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 - 2021 SSHTOOLS Limited (support@sshtools.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sshtools.forker.client.impl.jna.win32; 17 | 18 | import com.sun.jna.platform.win32.WinNT.HANDLE; 19 | 20 | /** 21 | * TODO 22 | */ 23 | public class WindowsAuthenticationTokens { 24 | 25 | HANDLE hProfile; 26 | HANDLE hToken; 27 | } 28 | -------------------------------------------------------------------------------- /forker-client/src/main/java/com/sshtools/forker/client/impl/nonblocking/ReferenceCountedFileDescriptor.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 - 2021 SSHTOOLS Limited (support@sshtools.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sshtools.forker.client.impl.nonblocking; 17 | 18 | import java.io.Closeable; 19 | 20 | import com.sshtools.forker.client.impl.jna.posix.LibC; 21 | import com.sun.jna.LastErrorException; 22 | 23 | /** 24 | * Encapsulates a file descriptor plus a reference count to ensure close 25 | * requests only close the file descriptor once the last reference to the file 26 | * descriptor is released. 27 | * 28 | * If not explicitly closed, the file descriptor will be closed when this object 29 | * is finalized. 30 | */ 31 | public class ReferenceCountedFileDescriptor implements Closeable { 32 | private int fd; 33 | private int fdRefCount; 34 | private boolean closePending; 35 | 36 | /** 37 | * Constructor 38 | * 39 | * @param fd file description 40 | */ 41 | public ReferenceCountedFileDescriptor(int fd) { 42 | this.fd = fd; 43 | this.fdRefCount = 0; 44 | this.closePending = false; 45 | } 46 | 47 | protected void finalize() { 48 | close(); 49 | } 50 | 51 | /** 52 | * Acquire a reference. 53 | * 54 | * @return file description 55 | */ 56 | public synchronized int acquire() { 57 | fdRefCount++; 58 | return fd; 59 | } 60 | 61 | /** 62 | * Release a reference. 63 | */ 64 | public synchronized void release() { 65 | fdRefCount--; 66 | if (fdRefCount == 0 && closePending && fd != -1) { 67 | doClose(); 68 | } 69 | } 70 | 71 | public synchronized void close() { 72 | if (fd == -1 || closePending) { 73 | return; 74 | } 75 | if (fdRefCount == 0) { 76 | doClose(); 77 | } else { 78 | // Another thread has the FD. We'll close it when they release the 79 | // reference. 80 | closePending = true; 81 | } 82 | } 83 | 84 | private void doClose() { 85 | try { 86 | LibC.close(fd); 87 | fd = -1; 88 | } catch (LastErrorException e) { 89 | throw new RuntimeException(e); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /forker-client/src/main/java/com/sshtools/forker/client/impl/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 - 2021 SSHTOOLS Limited (support@sshtools.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /** 17 | * This packages contains the implementations of the processes 18 | * {@link com.sshtools.forker.client.ForkerBuilder} creates. 19 | *

20 | * There would usually be no need to use the classes directly. 21 | */ 22 | package com.sshtools.forker.client.impl; -------------------------------------------------------------------------------- /forker-client/src/main/java/com/sshtools/forker/client/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 - 2021 SSHTOOLS Limited (support@sshtools.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /** 17 | * This packages contains the main classes that will be used in client code 18 | * making use of the Forker framework. 19 | *

20 | * The three classes you are most likely to want to use include :- 21 | *

    22 | *
  • {@link com.sshtools.forker.client.ForkerBuilder} - which is an API 23 | * compatible replacement for {@link java.lang.ProcessBuilder}.
  • 24 | *
  • {@link com.sshtools.forker.client.OSCommand} - which contains a set of 25 | * static helper methods to make running processes easy, taking care of checking 26 | * exit codes and redirecting I/O
  • 27 | *
  • {@link com.sshtools.forker.client.ShellBuilder} - which is a 28 | * specialisation of ForkerBuilder and can be used to create an interactive 29 | * shell (often used with the Forker Pty module). 30 | *
31 | */ 32 | package com.sshtools.forker.client; -------------------------------------------------------------------------------- /forker-client/src/main/java/com/sshtools/forker/client/ui/AskPassConsole.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 - 2021 SSHTOOLS Limited (support@sshtools.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sshtools.forker.client.ui; 17 | 18 | import java.io.BufferedReader; 19 | import java.io.Console; 20 | import java.io.InputStreamReader; 21 | 22 | /** 23 | * Simple console based helper application that asks for a password (input on 24 | * stdin, message on stderr) and prints it on stdout. 25 | */ 26 | public class AskPassConsole { 27 | 28 | /** 29 | * Entry point. 30 | * 31 | * @param args 32 | * command line arguments 33 | * @throws Exception 34 | * on any error 35 | */ 36 | public static void main(String[] args) throws Exception { 37 | Console console = System.console(); 38 | if (console == null) 39 | System.err.println("WARNING: Not on a console, password will be visible"); 40 | 41 | // Title 42 | String title = System.getenv("ASKPASS_TITLE"); 43 | if (title == null) { 44 | title = "Administrator Password Required"; 45 | } 46 | 47 | // Text 48 | String text = System.getenv("ASKPASS_TEXT"); 49 | if (text == null) { 50 | text = "This application requires elevated privileges. Please\n"; 51 | text += "enter the administrator password to continue."; 52 | } 53 | 54 | System.err.println(title); 55 | System.err.println(); 56 | System.err.println(text); 57 | System.err.println(); 58 | 59 | System.err.print("Enter a password:"); 60 | String pw = null; 61 | if (console == null) { 62 | BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 63 | pw = br.readLine(); 64 | } else { 65 | char[] c = console.readPassword(""); 66 | pw = c == null ? null : new String(c); 67 | } 68 | if (pw != null) 69 | System.out.println(pw); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /forker-client/src/main/java/com/sshtools/forker/client/ui/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 - 2021 SSHTOOLS Limited (support@sshtools.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /** 17 | * This packages contains some helper applications such as 'Ask Pass' programs 18 | * and launchers that may be used by some operating system commands for 19 | * privilege escalation and user switching. 20 | *

21 | * There would usually be no need to use the classes directly. 22 | */ 23 | package com.sshtools.forker.client.ui; -------------------------------------------------------------------------------- /forker-client/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Common module 3 | * 4 | * @uses com.sshtools.forker.client.ForkerProcessFactory 5 | */ 6 | module com.sshtools.forker.client { 7 | exports com.sshtools.forker.client; 8 | exports com.sshtools.forker.client.impl; 9 | exports com.sshtools.forker.client.impl.nonblocking; 10 | exports com.sshtools.forker.client.impl.jna.posix; 11 | exports com.sshtools.forker.client.impl.jna.win32; 12 | exports com.sshtools.forker.client.impl.jna.osx; 13 | exports com.sshtools.forker.client.ui; 14 | 15 | requires transitive com.sshtools.forker.common; 16 | requires static java.desktop; 17 | requires java.logging; 18 | 19 | uses com.sshtools.forker.client.ForkerProcessFactory; 20 | } -------------------------------------------------------------------------------- /forker-common/.gitignore: -------------------------------------------------------------------------------- 1 | /.settings/ 2 | /target/ 3 | *.classpath 4 | /bin/ 5 | -------------------------------------------------------------------------------- /forker-common/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | forker-common 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.wst.common.project.facet.core.builder 10 | 11 | 12 | 13 | 14 | org.eclipse.jdt.core.javabuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.wst.validation.validationbuilder 20 | 21 | 22 | 23 | 24 | org.eclipse.m2e.core.maven2Builder 25 | 26 | 27 | 28 | 29 | 30 | org.eclipse.jem.workbench.JavaEMFNature 31 | org.eclipse.wst.common.modulecore.ModuleCoreNature 32 | org.eclipse.jdt.core.javanature 33 | org.eclipse.m2e.core.maven2Nature 34 | org.eclipse.wst.common.project.facet.core.nature 35 | 36 | 37 | -------------------------------------------------------------------------------- /forker-common/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | forker-common 6 | Forker Common 7 | Common classes for the forker system. 8 | 9 | com.sshtools 10 | forker 11 | 1.8 12 | .. 13 | 14 | 15 | 16 | src/main/java 17 | src/test/java 18 | target/classes 19 | target/test-classes 20 | 21 | 22 | . 23 | src/main/resources 24 | 25 | 26 | 27 | 28 | . 29 | src/test/resources 30 | 31 | 32 | 33 | 34 | 35 | 40 | 41 | net.java.dev.jna 42 | jna 43 | 5.9.0 44 | 45 | 46 | net.java.dev.jna 47 | jna-platform 48 | 5.9.0 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /forker-common/src/main/java/com/sshtools/forker/common/Priority.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 - 2021 SSHTOOLS Limited (support@sshtools.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sshtools.forker.common; 17 | 18 | /** 19 | * Represents the priority of a process. Note, the OS may support more less 20 | * priorities, but currently a least common denominator approach has been taken 21 | * with this feature. This may change in the future. 22 | * 23 | */ 24 | public enum Priority { 25 | /** 26 | * Low priority 27 | */ 28 | LOW, 29 | /** 30 | * Normal priority, i.e. as decided by OS when priority is not explicitly 31 | * set 32 | */ 33 | NORMAL, 34 | /** 35 | * High priority 36 | */ 37 | HIGH, 38 | /** 39 | * Realtime (when supported) 40 | */ 41 | REALTIME 42 | } 43 | -------------------------------------------------------------------------------- /forker-common/src/main/java/com/sshtools/forker/common/States.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 - 2021 SSHTOOLS Limited (support@sshtools.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sshtools.forker.common; 17 | 18 | /** 19 | * Various constants used by the Forker Daemon protocol. 20 | * 21 | */ 22 | public class States { 23 | 24 | /** 25 | * Remote command or operation executed OK 26 | */ 27 | public final static int OK = 0; 28 | /** 29 | * Remote command or operation failed to execute OK 30 | */ 31 | public final static int FAILED = 1; 32 | /** 33 | * Apply operation to input stream 34 | */ 35 | public final static int IN = 2; 36 | /** 37 | * Apply operation to output stream 38 | */ 39 | public final static int ERR = 3; 40 | /** 41 | * End of stream 42 | */ 43 | public final static int END = 4; 44 | /** 45 | * Apply operation to output stream 46 | */ 47 | public final static int OUT = 5; 48 | /** 49 | * Kill process 50 | */ 51 | public final static int KILL = 6; 52 | /** 53 | * Close output stream 54 | */ 55 | public final static int CLOSE_OUT = 7; 56 | /** 57 | * Close error stream 58 | */ 59 | public final static int CLOSE_ERR = 8; 60 | /** 61 | * Close input stream 62 | */ 63 | public final static int CLOSE_IN = 9; 64 | /** 65 | * Flush output stream 66 | */ 67 | public final static int FLUSH_OUT = 10; 68 | /** 69 | * Window size changed (either direction) 70 | */ 71 | public final static int WINDOW_SIZE = 11; 72 | } 73 | -------------------------------------------------------------------------------- /forker-common/src/main/java/com/sshtools/forker/common/XKernel32.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.common; 2 | 3 | import com.sun.jna.Native; 4 | import com.sun.jna.platform.win32.Kernel32; 5 | import com.sun.jna.win32.W32APIOptions; 6 | 7 | public interface XKernel32 extends Kernel32 { 8 | /** 9 | * Instance 10 | */ 11 | XKernel32 INSTANCE = Native.load("Kernel32", XKernel32.class, W32APIOptions.UNICODE_OPTIONS); 12 | 13 | int SetCurrentDirectoryW(String path); 14 | } 15 | -------------------------------------------------------------------------------- /forker-common/src/main/java/com/sshtools/forker/common/XWinsvc.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.common; 2 | 3 | import com.sun.jna.Pointer; 4 | import com.sun.jna.Structure; 5 | import com.sun.jna.Structure.FieldOrder; 6 | import com.sun.jna.platform.win32.WinDef.DWORD; 7 | import com.sun.jna.platform.win32.Winsvc; 8 | import com.sun.jna.win32.W32APITypeMapper; 9 | 10 | public interface XWinsvc extends Winsvc { 11 | 12 | @FieldOrder({ "fDelayedAutostart" }) 13 | public class SERVICE_DELAYED_AUTO_START_INFO extends ChangeServiceConfig2Info { 14 | public static class ByReference extends SERVICE_DELAYED_AUTO_START_INFO implements Structure.ByReference { 15 | } 16 | 17 | public boolean fDelayedAutostart; 18 | } 19 | 20 | public final static DWORD SERVICE_SID_TYPE_NONE = new DWORD(0x00000000); 21 | public final static DWORD SERVICE_SID_TYPE_RESTRICTED = new DWORD(0x00000003); 22 | public final static DWORD SERVICE_SID_TYPE_UNRESTRICTED = new DWORD(0x00000001); 23 | 24 | @FieldOrder({ "dwServiceSidType" }) 25 | public class SERVICE_SID_INFO extends ChangeServiceConfig2Info { 26 | public static class ByReference extends SERVICE_SID_INFO implements Structure.ByReference { 27 | } 28 | 29 | public DWORD dwServiceSidType; 30 | } 31 | 32 | @FieldOrder({ "lpDescription" }) 33 | public class SERVICE_DESCRIPTION extends ChangeServiceConfig2Info { 34 | public static class ByReference extends SERVICE_DESCRIPTION implements Structure.ByReference { 35 | } 36 | 37 | public String lpDescription; 38 | } 39 | 40 | @FieldOrder({ "lpServiceName", "lpDisplayName", "ServiceStatusProcess" }) 41 | public static class ENUM_SERVICE_STATUS_PROCESS extends Structure { 42 | public Pointer lpServiceName; 43 | public Pointer lpDisplayName; 44 | public SERVICE_STATUS_PROCESS ServiceStatusProcess; 45 | 46 | public ENUM_SERVICE_STATUS_PROCESS() { 47 | super(W32APITypeMapper.DEFAULT); 48 | } 49 | 50 | public ENUM_SERVICE_STATUS_PROCESS(Pointer pointer) { 51 | super(pointer); 52 | read(); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /forker-common/src/main/java/com/sshtools/forker/common/package-info.java: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Contains the Forker Daemon, used to run processes from a separate 4 | * (smaller JVM) and provide other features, such as the PTY plugin that 5 | * provides a way to launch real interactive shells. 6 | *

7 | * Unless you are extending Forker, for most cases, you do not have to worry 8 | * about either directly running or configuring the daemon, it is all handled 9 | * more or less automatically. 10 | */ 11 | package com.sshtools.forker.common; -------------------------------------------------------------------------------- /forker-common/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Common module 3 | * @uses com.sshtools.forker.common.IO 4 | */ 5 | module com.sshtools.forker.common { 6 | requires transitive com.sun.jna; 7 | requires transitive com.sun.jna.platform; 8 | exports com.sshtools.forker.common; 9 | uses com.sshtools.forker.common.IO; 10 | } -------------------------------------------------------------------------------- /forker-examples/.gitignore: -------------------------------------------------------------------------------- 1 | /.settings/ 2 | /target/ 3 | *.classpath 4 | /bin/ 5 | *.cfg 6 | /tmp/ 7 | -------------------------------------------------------------------------------- /forker-examples/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | forker-examples 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.m2e.core.maven2Builder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.m2e.core.maven2Nature 22 | 23 | 24 | -------------------------------------------------------------------------------- /forker-examples/app.args: -------------------------------------------------------------------------------- 1 | -classpath 2 | /home/tanktarta/.m2/repository/org/jetbrains/pty4j/purejavacomm/0.0.11.1/purejavacomm-0.0.11.1.jar:/home/tanktarta/.m2/repository/org/jetbrains/annotations/16.0.2/annotations-16.0.2.jar:/home/tanktarta/.m2/repository/com/google/guava/guava/25.1-jre/guava-25.1-jre.jar:/home/tanktarta/.m2/repository/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar:/home/tanktarta/.m2/repository/org/checkerframework/checker-qual/2.0.0/checker-qual-2.0.0.jar:/home/tanktarta/.m2/repository/com/google/errorprone/error_prone_annotations/2.1.3/error_prone_annotations-2.1.3.jar:/home/tanktarta/.m2/repository/com/google/j2objc/j2objc-annotations/1.1/j2objc-annotations-1.1.jar:/home/tanktarta/.m2/repository/org/codehaus/mojo/animal-sniffer-annotations/1.14/animal-sniffer-annotations-1.14.jar:/home/tanktarta/.m2/repository/log4j/log4j/1.2.14/log4j-1.2.14.jar:/apps/eclipse-2020-12-M2/configuration/org.eclipse.osgi/1793/0/.cp/lib/javaagent-shaded.jar:/home/tanktarta/.m2/repository/org/jetbrains/pty4j/purejavacomm/0.0.11.1/purejavacomm-0.0.11.1.jar:/home/tanktarta/.m2/repository/org/jetbrains/annotations/16.0.2/annotations-16.0.2.jar:/home/tanktarta/.m2/repository/com/google/guava/guava/25.1-jre/guava-25.1-jre.jar:/home/tanktarta/.m2/repository/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar:/home/tanktarta/.m2/repository/org/checkerframework/checker-qual/2.0.0/checker-qual-2.0.0.jar:/home/tanktarta/.m2/repository/com/google/errorprone/error_prone_annotations/2.1.3/error_prone_annotations-2.1.3.jar:/home/tanktarta/.m2/repository/com/google/j2objc/j2objc-annotations/1.1/j2objc-annotations-1.1.jar:/home/tanktarta/.m2/repository/org/codehaus/mojo/animal-sniffer-annotations/1.14/animal-sniffer-annotations-1.14.jar:/home/tanktarta/.m2/repository/log4j/log4j/1.2.14/log4j-1.2.14.jar:/apps/eclipse-2020-12-M2/configuration/org.eclipse.osgi/1793/0/.cp/lib/javaagent-shaded.jar 3 | -p 4 | /home/tanktarta/Documents/Git/forker-develop/forker-examples/target/classes:/home/tanktarta/Documents/Git/forker-develop/forker-client/target/classes:/home/tanktarta/Documents/Git/forker-develop/forker-common/target/classes:/home/tanktarta/.m2/repository/org/apache/commons/commons-lang3/3.11/commons-lang3-3.11.jar:/home/tanktarta/Documents/Git/forker-develop/forker-pty/target/classes:/home/tanktarta/.m2/repository/net/java/dev/jna/jna/5.6.0/jna-5.6.0.jar:/home/tanktarta/.m2/repository/net/java/dev/jna/jna-platform/5.6.0/jna-platform-5.6.0.jar:/home/tanktarta/.m2/repository/org/jetbrains/pty4j/pty4j/0.9.8/pty4j-0.9.8.jar:/home/tanktarta/Documents/Git/forker-develop/forker-wrapper/target/classes:/home/tanktarta/.m2/repository/info/picocli/picocli/4.6.1/picocli-4.6.1.jar:/home/tanktarta/Documents/Git/forker-develop/forker-wrapped/target/classes 5 | -Dforker.info.attempts=1 6 | --add-modules 7 | com.sshtools.forker.examples 8 | -------------------------------------------------------------------------------- /forker-examples/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | forker-examples 4 | Forker Examples 5 | Examples for the forker system. 6 | 7 | com.sshtools 8 | forker 9 | 1.8 10 | .. 11 | 12 | 13 | 14 | src/main/java 15 | src/test/java 16 | target/classes 17 | target/test-classes 18 | 19 | 20 | . 21 | src/main/resources 22 | 23 | 24 | 25 | 26 | . 27 | src/test/resources 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | ${project.groupId} 37 | forker-client 38 | ${project.version} 39 | 40 | 41 | ${project.groupId} 42 | forker-pty 43 | ${project.version} 44 | 45 | 46 | ${project.groupId} 47 | forker-wrapper 48 | ${project.version} 49 | 50 | 51 | ${project.groupId} 52 | forker-wrapped 53 | ${project.version} 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /forker-examples/src/main/java/com/sshtools/forker/examples/AdministratorElevate.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.examples; 2 | 3 | import com.sshtools.forker.client.EffectiveUser; 4 | import com.sshtools.forker.client.EffectiveUserFactory; 5 | import com.sshtools.forker.client.ForkerBuilder; 6 | import com.sshtools.forker.common.IO; 7 | import com.sshtools.forker.common.Util; 8 | import com.sun.jna.Platform; 9 | 10 | /** 11 | * Demonstrates elevating to administrator using {@link ForkerBuilder}. 12 | */ 13 | public class AdministratorElevate { 14 | public static void main(String[] args) throws Exception { 15 | 16 | /* Get the user object for admnistrator */ 17 | EffectiveUser administrator = EffectiveUserFactory.getDefault().administrator(); 18 | 19 | /* Create the builder */ 20 | ForkerBuilder builder = new ForkerBuilder().effectiveUser(administrator).io(IO.IO); 21 | if (Platform.isLinux()) { 22 | /* The linux example tries to list the shadow password file */ 23 | builder.redirectErrorStream(true).io(IO.IO).command("cat", "/etc/shadow"); 24 | } else if (Platform.isWindows()) { 25 | /* 26 | * Windows try to create a filename protected by WRP 27 | * builder.command("dir", ">", "c:\\forker-test.exe"); 28 | * builder.command("C:\\windows\\system32\\cmd.exe", "/c", "dir 29 | * \\"); 30 | */ 31 | builder.command("C:\\windows\\system32\\xcopy.exe"); 32 | builder.io(IO.SINK); 33 | } else { 34 | throw new UnsupportedOperationException(); 35 | } 36 | 37 | Process p = builder.start(); 38 | Util.copy(p.getInputStream(), System.out); 39 | System.out.println(" (" + p.waitFor() + ")"); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /forker-examples/src/main/java/com/sshtools/forker/examples/ForkerWrapperHelper.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.examples; 2 | 3 | import com.sshtools.forker.wrapper.ForkerWrapper; 4 | 5 | /** 6 | * This is just here to help with Eclipse launching a mixed JPMS/non-JPMS 7 | * project. It may be deleted in the future, is not actually an example, 8 | * and has no other use. 9 | */ 10 | public class ForkerWrapperHelper extends ForkerWrapper { 11 | 12 | public static void main(String[] args) { 13 | ForkerWrapper.main(args); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /forker-examples/src/main/java/com/sshtools/forker/examples/NativeForkWrappedTest.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.examples; 2 | 3 | /** This is the application that {@link NativeForkWrapperTest} will launch */ 4 | public class NativeForkWrappedTest { 5 | 6 | public NativeForkWrappedTest() { 7 | } 8 | 9 | public static void main(String[] args) throws Exception { 10 | System.out.println("Running as " + System.getProperty("user.name")); 11 | 12 | for (int i = 0; i < 1000; i++) { 13 | System.out.println("Running " + i); 14 | Thread.sleep(1000); 15 | } 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /forker-examples/src/main/java/com/sshtools/forker/examples/NativeForkWrapperTest.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.examples; 2 | 3 | import com.sshtools.forker.wrapper.ForkerWrapper; 4 | 5 | /** 6 | * Shows you can embed {@link ForkerWrapper}. 7 | */ 8 | public class NativeForkWrapperTest { 9 | 10 | public static void main(String[] args) throws Exception { 11 | // OSCommand.restartAsAdministrator(NativeForkWrapperTest.class.getPackageName(), NativeForkWrapperTest.class, args); 12 | 13 | System.out.println("Running as " + System.getProperty("user.name")); 14 | ForkerWrapper fw = new ForkerWrapper(); 15 | 16 | // fw.setProperty("quiet", true); 17 | // fw.setProperty("level", "SEVERE"); 18 | 19 | fw.getWrappedApplication().setModule(NativeForkWrappedTest.class.getPackageName()); 20 | fw.getWrappedApplication().setClassname(NativeForkWrappedTest.class.getName()); 21 | fw.getConfiguration().setRemaining("arg1"); 22 | fw.getConfiguration().setProperty("level", "FINE"); 23 | fw.getConfiguration().setProperty("native-fork", "true"); 24 | fw.getConfiguration().setProperty("daemon", "true"); 25 | 26 | // Start and wait for wrapper to exit 27 | System.out.println("Wrapped process returned: " + fw.start()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /forker-examples/src/main/java/com/sshtools/forker/examples/NonBlocking.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.examples; 2 | 3 | import java.nio.ByteBuffer; 4 | 5 | import com.sshtools.forker.client.DefaultNonBlockingProcessListener; 6 | import com.sshtools.forker.client.ForkerBuilder; 7 | import com.sshtools.forker.client.NonBlockingProcess; 8 | import com.sshtools.forker.common.IO; 9 | import com.sshtools.forker.common.OS; 10 | 11 | /** 12 | * Simple non-blocking I/O example that reads the output of a command. 13 | */ 14 | public class NonBlocking { 15 | public static void main(String[] args) throws Exception { 16 | ForkerBuilder builder = new ForkerBuilder().io(IO.NON_BLOCKING).redirectErrorStream(true); 17 | if (OS.isUnix()) { 18 | // The unix example tries to list the root directory 19 | builder.command("ls", "-al", "/"); 20 | } else { 21 | builder.command("DIR", "C:\\"); 22 | } 23 | NonBlockingProcess process = new ForkerBuilder("ls", "-al", "/").io(IO.NON_BLOCKING).redirectErrorStream(true) 24 | .start(new DefaultNonBlockingProcessListener() { 25 | @Override 26 | public void onStdout(NonBlockingProcess process, ByteBuffer buffer, boolean closed) { 27 | if (!closed) { 28 | byte[] bytes = new byte[buffer.remaining()]; 29 | /* Consume bytes from buffer (so position is updated) */ 30 | buffer.get(bytes); 31 | System.out.println(new String(bytes)); 32 | } 33 | } 34 | }); 35 | /* 36 | * Not strictly required, this is just to hold up the example thread until the 37 | * command is finished, your use case may or may not need to wait for the 38 | * command to finish. 39 | */ 40 | System.out.println("Done: " + process.waitFor()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /forker-examples/src/main/java/com/sshtools/forker/examples/NonBlockingShell.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 - 2021 SSHTOOLS Limited (support@sshtools.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sshtools.forker.examples; 17 | 18 | import java.nio.ByteBuffer; 19 | 20 | import com.sshtools.forker.client.DefaultNonBlockingProcessListener; 21 | import com.sshtools.forker.client.NonBlockingProcess; 22 | import com.sshtools.forker.client.ShellBuilder; 23 | import com.sshtools.forker.common.IO; 24 | import com.sshtools.forker.common.OS; 25 | import com.sshtools.forker.common.Util; 26 | 27 | /** 28 | * This example shows how to create an interactive shell. 29 | * 30 | */ 31 | public class NonBlockingShell { 32 | 33 | public static void main(String[] args) throws Exception { 34 | /* 35 | * This example reads from stdin (i.e. the console), so stdin needs to be unbuffered with 36 | * no local echoing at this end of the pipe, the following function 37 | * attempts to do this. 38 | */ 39 | OS.unbufferedStdin(); 40 | 41 | /* ShellBuilder is a specialisation of ForkerBuilder */ 42 | ShellBuilder shell = new ShellBuilder(); 43 | shell.io(IO.NON_BLOCKING); 44 | shell.redirectErrorStream(true); 45 | 46 | /* Demonstrate we are actually in a different shell by setting PS1 */ 47 | shell.environment().put("MYENV", "An environment variable"); 48 | 49 | final NonBlockingProcess p = shell.start(new DefaultNonBlockingProcessListener() { 50 | @Override 51 | public void onStdout(NonBlockingProcess process, ByteBuffer buffer, boolean closed) { 52 | if (!closed) { 53 | byte[] bytes = new byte[buffer.remaining()]; 54 | /* Consume bytes from buffer (so position is updated) */ 55 | buffer.get(bytes); 56 | System.out.println(new String(bytes)); 57 | } 58 | } 59 | }); 60 | 61 | /* While using non-blocking, it is still convenient in this case to use 62 | * the OutputStream provided by the process as we are just joining 63 | * it to stdin of this process 64 | */ 65 | Util.copy(System.in, p.getOutputStream()); 66 | 67 | /* When this processes stdin closes, close the shells stdin too and 68 | * wait for it to finish 69 | */ 70 | p.getOutputStream().close(); 71 | int ret = p.waitFor(); 72 | System.err.println("Exited with code: " + ret); 73 | System.exit(ret); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /forker-examples/src/main/java/com/sshtools/forker/examples/OSCommandElevate.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.examples; 2 | 3 | import static com.sshtools.forker.client.OSCommand.admin; 4 | import static com.sshtools.forker.client.OSCommand.elevate; 5 | import static com.sshtools.forker.client.OSCommand.restrict; 6 | import static com.sshtools.forker.client.OSCommand.run; 7 | 8 | /** 9 | * Demonstrates using elevated commands 10 | * 11 | */ 12 | public class OSCommandElevate { 13 | 14 | public static void main(String[] args) throws Exception { 15 | /* 16 | * First run an admin command, each time such a command is run 17 | * the password will be required. The 'admin' method is a shortcut for elevating 18 | * permissions then using 'run' 19 | */ 20 | 21 | // The shorthand version 22 | admin("ifconfig"); 23 | 24 | // The longhand version 25 | elevate(); 26 | try { 27 | run("ifconfig"); 28 | } finally { 29 | restrict(); 30 | } 31 | 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /forker-examples/src/main/java/com/sshtools/forker/examples/PowerShellCommand.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.examples; 2 | 3 | import com.sshtools.forker.client.PowerShellBuilder; 4 | import com.sshtools.forker.common.Util; 5 | 6 | /** 7 | * Demonstrates running a PowerShell command using {@link PowerShellBuilder}. 8 | */ 9 | public class PowerShellCommand { 10 | public static void main(String[] args) throws Exception { 11 | /* Create the builder */ 12 | Process p = new PowerShellBuilder("$PSVersionTable").start(); 13 | Util.copy(p.getInputStream(), System.out); 14 | System.out.println(" (" + p.waitFor() + ")"); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /forker-examples/src/main/java/com/sshtools/forker/examples/RestartAsAdministrator.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.examples; 2 | 3 | import static com.sshtools.forker.client.OSCommand.run; 4 | 5 | import com.sshtools.forker.client.OSCommand; 6 | import com.sshtools.forker.common.OS; 7 | 8 | /** 9 | * Restart this application as an administrator. 10 | */ 11 | public class RestartAsAdministrator { 12 | public static void main(String[] args) throws Exception { 13 | OSCommand.restartAsAdministrator(RestartAsAdministrator.class, args); 14 | if (OS.isUnix()) { 15 | run("cat", "/etc/passwd"); 16 | run("id"); 17 | } 18 | else { 19 | String pf = System.getenv("PROGRAMFILES"); 20 | if (pf == null) 21 | pf = "C:\\Program Files"; 22 | run("MKDIR", pf + "\\SimpleElevate.TMP"); 23 | run("RD", pf + "\\SimpleElevate.TMP"); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /forker-examples/src/main/java/com/sshtools/forker/examples/RunAsUser.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.examples; 2 | 3 | import com.sshtools.forker.client.EffectiveUser; 4 | import com.sshtools.forker.client.EffectiveUserFactory; 5 | import com.sshtools.forker.client.ForkerBuilder; 6 | import com.sshtools.forker.common.IO; 7 | import com.sshtools.forker.common.Util; 8 | import com.sun.jna.Platform; 9 | 10 | /** 11 | * Demonstrates running a command as another user. 12 | */ 13 | public class RunAsUser { 14 | 15 | public static void main(String[] args) throws Exception { 16 | 17 | /* Either supply a username on the command line when you run this class or 18 | * change this username 'testuser2' to one that exists on your system. 19 | */ 20 | String username = args.length == 0 ? "testuser2" : args[0]; 21 | 22 | /* Get the user object for this user */ 23 | EffectiveUser user = EffectiveUserFactory.getDefault().getUserForUsername(username); 24 | 25 | /* Create the builder */ 26 | ForkerBuilder builder = new ForkerBuilder().effectiveUser(user). 27 | io(IO.IO).redirectErrorStream(true); 28 | 29 | if(Platform.isLinux()) { 30 | // The linux example tries to list the users home directory 31 | builder.command("id"); 32 | } 33 | else { 34 | throw new UnsupportedOperationException(); 35 | } 36 | 37 | Process p = builder.start(); 38 | Util.copy(p.getInputStream(), System.out); 39 | System.out.println(" (" + p.waitFor() + ")"); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /forker-examples/src/main/java/com/sshtools/forker/examples/Shell.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.examples; 2 | 3 | import java.io.IOException; 4 | 5 | import com.sshtools.forker.client.ShellBuilder; 6 | import com.sshtools.forker.common.OS; 7 | import com.sshtools.forker.common.Util; 8 | import com.sshtools.forker.pty.PTYProcess; 9 | import com.sshtools.forker.pty.PTYProcess.PTYProcessListener; 10 | 11 | /** 12 | * This example shows how to create an interactive shell. 13 | * 14 | */ 15 | public class Shell { 16 | 17 | public static void main(String[] args) throws Exception { 18 | /* 19 | * This example reads from stdin (i.e. the console), so stdin needs to be unbuffered with 20 | * no local echoing at this end of the pipe, the following function 21 | * attempts to do this. 22 | */ 23 | OS.unbufferedStdin(); 24 | 25 | 26 | /* ShellBuilder is a specialisation of ForkerBuilder */ 27 | ShellBuilder shell = new ShellBuilder(); 28 | shell.io(PTYProcess.PTY); 29 | 30 | /* Demonstrate we are actually in a different shell by setting PS1 */ 31 | shell.environment().put("MYENV", "An environment variable"); 32 | 33 | /* Start the shell, giving it a window size listener */ 34 | final Process p = shell.start(new PTYProcessListener() { 35 | @Override 36 | public void windowSizeChanged(int ptyWidth, int ptyHeight) { 37 | System.out.println("Window size changed to " + ptyWidth + " x " + ptyHeight); 38 | } 39 | }); 40 | 41 | 42 | new Thread() { 43 | public void run() { 44 | try { 45 | Util.copy(System.in, p.getOutputStream()); 46 | } catch (IOException e) { 47 | } finally { 48 | // Close the process input stream when stdin closes, this 49 | // will end the process 50 | try { 51 | p.getOutputStream().close(); 52 | } catch (IOException e) { 53 | } 54 | } 55 | } 56 | }.start(); 57 | Util.copy(p.getInputStream(), System.out); 58 | int ret = p.waitFor(); 59 | System.err.println("Exited with code: " + ret); 60 | System.exit(ret); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /forker-examples/src/main/java/com/sshtools/forker/examples/ShellAsUser.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.examples; 2 | 3 | import java.io.Console; 4 | import java.io.IOException; 5 | 6 | import com.sshtools.forker.client.EffectiveUserFactory.DefaultEffectiveUserFactory; 7 | import com.sshtools.forker.client.ForkerBuilder; 8 | import com.sshtools.forker.client.ShellBuilder; 9 | import com.sshtools.forker.common.OS; 10 | import com.sshtools.forker.common.Util; 11 | import com.sshtools.forker.pty.PTYProcess; 12 | 13 | /** 14 | * Launch an interactive login shell as a particular user using a Pseudo 15 | * Terminal, or PTY. PTY supports is currently only available via Forker 16 | * Daemon, which in this example is launched as an administrator allowing 17 | * any user to be used for the shell. 18 | */ 19 | public class ShellAsUser { 20 | 21 | public static void main(String[] args) throws Exception { 22 | /* 23 | * This example reads from stdni, so stdid needs to be unbuffered with 24 | * no local echoing at this end of the pipe, the following function 25 | * attempts to do this 26 | */ 27 | OS.unbufferedStdin(); 28 | 29 | ForkerBuilder shell = new ShellBuilder().loginShell(true).io(PTYProcess.PTY).redirectErrorStream(true); 30 | 31 | /* 32 | * Run the shell as the user that launches this class. Any valid UID 33 | * could be used, but we just get the current UID (using 'id -u' 34 | * command) and ask for the shell we spawn to use the that user. 35 | */ 36 | Console console = System.console(); 37 | if (console == null && args.length == 0) 38 | throw new IOException("No console available and no username supplied as command line argument."); 39 | shell.effectiveUser(DefaultEffectiveUserFactory.getDefault() 40 | .getUserForUsername(args.length == 0 ? console.readLine("Username:") : args[0])); 41 | 42 | // Start process 43 | final Process p = shell.start(); 44 | 45 | /* 46 | * Connect both the input and the output streams, start the process and 47 | * wait for it to finish 48 | */ 49 | new Thread() { 50 | public void run() { 51 | try { 52 | Util.copy(System.in, p.getOutputStream()); 53 | } catch (IOException e) { 54 | } finally { 55 | // Close the process input stream when stdin closes, this 56 | // will end the process 57 | try { 58 | p.getOutputStream().close(); 59 | } catch (IOException e) { 60 | } 61 | } 62 | } 63 | }.start(); 64 | Util.copy(p.getInputStream(), System.out); 65 | int ret = p.waitFor(); 66 | System.err.println("Exited with code: " + ret); 67 | System.exit(ret); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /forker-examples/src/main/java/com/sshtools/forker/examples/SimpleElevate.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.examples; 2 | 3 | import static com.sshtools.forker.client.OSCommand.elevate; 4 | import static com.sshtools.forker.client.OSCommand.restrict; 5 | import static com.sshtools.forker.client.OSCommand.run; 6 | 7 | import com.sshtools.forker.client.OSCommand; 8 | import com.sshtools.forker.common.OS; 9 | import com.sun.jna.Platform; 10 | 11 | /** 12 | * Show uses {@link OSCommand}, that uses {@link ThreadLocal} state to configure 13 | * and run simple commands. 14 | */ 15 | public class SimpleElevate { 16 | public static void main(String[] args) throws Exception { 17 | elevate(); 18 | try { 19 | if (Platform.isMac()) 20 | run("cat", "/etc/master.passwd"); 21 | else if (OS.isUnix()) 22 | run("cat", "/etc/passwd"); 23 | else { 24 | String pf = System.getenv("PROGRAMFILES"); 25 | if (pf == null) 26 | pf = "C:\\Program Files"; 27 | run("MKDIR", pf + "\\SimpleElevate.TMP"); 28 | run("RD", pf + "\\SimpleElevate.TMP"); 29 | } 30 | } finally { 31 | restrict(); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /forker-examples/src/main/java/com/sshtools/forker/examples/WrappedProcessTest.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.examples; 2 | 3 | import com.sshtools.forker.client.ForkerBuilder; 4 | import com.sshtools.forker.common.OS; 5 | import com.sshtools.forker.common.Util; 6 | import com.sshtools.forker.wrapper.ForkerWrapper; 7 | import com.sshtools.forker.wrapper.KeyValuePair; 8 | import com.sshtools.forker.wrapper.WrapperIO; 9 | import com.sshtools.forker.wrapper.WrapperProcessFactory; 10 | import com.sun.jna.Platform; 11 | 12 | /** 13 | * Another way of embedded {@link ForkerWrapper}, this time by using the 14 | * {@link ForkerBuilder} with an I/O mode of {@link WrapperIO}. 15 | */ 16 | public class WrappedProcessTest { 17 | 18 | public static void main(String[] args) throws Exception { 19 | // Standard builder creation 20 | ForkerBuilder fb = new ForkerBuilder(); 21 | 22 | // Choose a command to run based on OS 23 | if (OS.isUnix()) 24 | fb.parse("ls /etc"); 25 | else if (Platform.isWindows()) 26 | fb.parse("DIR C:\\"); 27 | else 28 | throw new UnsupportedOperationException("Add a command for your OS to test."); 29 | 30 | // Get a handle on the process factory, allowing configuration of it 31 | WrapperProcessFactory processFactory = fb.configuration().processFactory(WrapperProcessFactory.class); 32 | 33 | processFactory.addOption(new KeyValuePair("native", "true")); 34 | processFactory.addOption(new KeyValuePair("level", "FINEST")); 35 | processFactory.addOption(new KeyValuePair("restart-on", "0")); 36 | processFactory.addOption(new KeyValuePair("restart-wait", "10")); 37 | 38 | // Launches a new JVM 39 | processFactory.setSeparateProcess(true); 40 | 41 | // Tell forker builder we want a wrapped process 42 | fb.io(WrapperIO.WRAPPER); 43 | 44 | // Boilerplate stuff 45 | fb.redirectErrorStream(true); 46 | Process p = fb.start(); 47 | Util.copy(p.getInputStream(), System.out); 48 | System.out.println(" (" + p.waitFor() + ")"); 49 | 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /forker-examples/src/main/java/com/sshtools/forker/examples/WrappedTest.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.examples; 2 | 3 | /** This is the application that {@link WrapperTest} will launch */ 4 | public class WrappedTest { 5 | 6 | public WrappedTest() { 7 | } 8 | 9 | public static void main(String[] args) throws Exception { 10 | Runtime.getRuntime().addShutdownHook(new Thread() { 11 | @Override 12 | public void run() { 13 | try { 14 | for (int i = 0; i < 20; i++) { 15 | System.out.println("Waiting to exit " + i + "/" + 20); 16 | Thread.sleep(1000); 17 | } 18 | } catch (Exception e) { 19 | } 20 | } 21 | }); 22 | 23 | for (int i = 0; i < 1000; i++) { 24 | System.out.println("Running " + i); 25 | Thread.sleep(1000); 26 | } 27 | 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /forker-examples/src/main/java/com/sshtools/forker/examples/WrapperJavascriptTest.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.examples; 2 | 3 | import java.io.File; 4 | 5 | import com.sshtools.forker.wrapper.ForkerWrapper; 6 | 7 | /** 8 | * Shows you can embed {@link ForkerWrapper} and use a JavaScript 9 | * configuration file to configure it. 10 | */ 11 | public class WrapperJavascriptTest { 12 | 13 | public static void main(String[] args) throws Exception { 14 | ForkerWrapper fw = new ForkerWrapper(); 15 | fw.getConfiguration().setRemaining(args); 16 | fw.readConfigFile(new File("wrapper-javascript-test.cfg.js")); 17 | 18 | // Start and wait for wrapper to exit 19 | System.out.println("Wrapped process returned: " + fw.start()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /forker-examples/src/main/java/com/sshtools/forker/examples/WrapperTest.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.examples; 2 | 3 | import com.sshtools.forker.wrapper.ForkerWrapper; 4 | 5 | /** 6 | * Shows you can embed {@link ForkerWrapper}. 7 | */ 8 | public class WrapperTest { 9 | 10 | public static void main(String[] args) throws Exception { 11 | ForkerWrapper fw = new ForkerWrapper(); 12 | 13 | fw.getWrappedApplication().setModule(WrappedTest.class.getPackageName()); 14 | fw.getWrappedApplication().setClassname(WrappedTest.class.getName()); 15 | fw.getConfiguration().setRemaining("arg1"); 16 | fw.getConfiguration().setProperty("level", "INFO"); 17 | 18 | // Start and wait for wrapper to exit 19 | System.out.println("Wrapped process returned: " + fw.start()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /forker-examples/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module com.sshtools.forker.examples { 2 | requires java.logging; 3 | requires com.sshtools.forker.client; 4 | requires com.sshtools.forker.pty; 5 | requires com.sshtools.forker.wrapped; 6 | requires transitive com.sshtools.forker.wrapper; 7 | 8 | exports com.sshtools.forker.examples; 9 | 10 | 11 | } -------------------------------------------------------------------------------- /forker-examples/wrapper-javascript-test.cfg.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2015 - 2021 SSHTOOLS Limited (support@sshtools.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /* This is a test JavaScript wrapper configuration file. 17 | * You can mix code and configuration, but this script 18 | * must evaluate to (return) an object containing the configuration 19 | * properties (i.e. a map of all the same properties that might 20 | * be provided by other methods) 21 | */ 22 | 23 | java.lang.System.out.println('Im configured by a script!'); 24 | 25 | /* Return the configuration object. The outer brackets are required. */ 26 | 27 | ({ 28 | main: 'com.nervepoint.forker.examples.WrappedTest', 29 | level: 'WARNING', 30 | arg: [ 31 | 'arg1', 32 | 'arg2' 33 | ] 34 | }) -------------------------------------------------------------------------------- /forker-pipes/.gitignore: -------------------------------------------------------------------------------- 1 | /.settings/ 2 | /target/ 3 | *.classpath 4 | /bin/ 5 | -------------------------------------------------------------------------------- /forker-pipes/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | forker-pipes 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.wst.common.project.facet.core.builder 10 | 11 | 12 | 13 | 14 | org.eclipse.jdt.core.javabuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.wst.validation.validationbuilder 20 | 21 | 22 | 23 | 24 | org.eclipse.m2e.core.maven2Builder 25 | 26 | 27 | 28 | 29 | 30 | org.eclipse.jem.workbench.JavaEMFNature 31 | org.eclipse.wst.common.modulecore.ModuleCoreNature 32 | org.eclipse.jdt.core.javanature 33 | org.eclipse.m2e.core.maven2Nature 34 | org.eclipse.wst.common.project.facet.core.nature 35 | 36 | 37 | -------------------------------------------------------------------------------- /forker-pipes/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | forker-pipes 6 | Forker Pipes 7 | Abstracts operating system pipes, such as unix domain sockets on Unix and friends, and 8 | named pipes on Windows. 9 | 10 | com.sshtools 11 | forker 12 | 1.8 13 | .. 14 | 15 | 16 | 17 | src/main/java 18 | src/test/java 19 | target/classes 20 | target/test-classes 21 | 22 | 23 | . 24 | src/main/resources 25 | 26 | 27 | 28 | 29 | . 30 | src/test/resources 31 | 32 | 33 | 34 | 35 | 36 | 37 | org.apache.commons 38 | commons-lang3 39 | 3.11 40 | 41 | 42 | ${project.groupId} 43 | forker-common 44 | ${project.version} 45 | 46 | 47 | net.java.dev.jna 48 | jna 49 | 5.9.0 50 | 51 | 52 | net.java.dev.jna 53 | jna-platform 54 | 5.9.0 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /forker-pipes/src/main/java/com/sshtools/forker/pipes/DefaultPipeFactory.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.pipes; 2 | 3 | import java.io.IOException; 4 | import java.net.ServerSocket; 5 | import java.net.Socket; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | import com.sshtools.forker.common.OS; 10 | import com.sun.jna.Platform; 11 | 12 | public class DefaultPipeFactory implements PipeFactory { 13 | 14 | @Override 15 | public ServerSocket createPipeServer(String name, Flag... flags) throws IOException { 16 | ServerSocket serverSocket = serverSocket(name); 17 | serverSocket.bind(null); 18 | return serverSocket; 19 | } 20 | 21 | @Override 22 | public Socket createPipe(String name, Flag... flags) throws IOException { 23 | Socket socket = socket(name); 24 | socket.connect(null); 25 | return socket; 26 | } 27 | 28 | protected Socket socket(String name, Flag... flags) throws IOException { 29 | if (OS.isUnix()) 30 | return new UnixDomainSocket(toUnixName(name, flags), flags); 31 | else if (Platform.isWindows()) 32 | return new NamedPipeSocket(toWindowsName(name, flags), flags); 33 | throw new UnsupportedOperationException(); 34 | } 35 | 36 | protected ServerSocket serverSocket(String name, Flag... flags) throws IOException { 37 | if (OS.isUnix()) 38 | return new UnixDomainServerSocket(toUnixName(name, flags), flags); 39 | else if (Platform.isWindows()) 40 | return new NamedPipeServerSocket(toWindowsName(name, flags), flags); 41 | throw new UnsupportedOperationException(); 42 | } 43 | 44 | protected String validateName(String name, Flag[] flags) { 45 | for (char c : name.toCharArray()) { 46 | if (!Character.isAlphabetic(c) && !Character.isDigit(c) && c != '_' && c != '.' && c != '-' && (!Platform.isWindows() || c != '\\')) 47 | throw new IllegalArgumentException(String.format("Invalid pipe name. %s", name)); 48 | } 49 | return name; 50 | } 51 | 52 | protected String toUnixName(String name, Flag... flags) { 53 | List flagsList = Arrays.asList(flags); 54 | if (flagsList.contains(Flag.CONCRETE)) 55 | return "/tmp/" + validateName(name, flags); 56 | else 57 | return "\0/tmp/" + validateName(name, flags); 58 | } 59 | 60 | protected String toWindowsName(String name, Flag... flags) { 61 | List flagsList = Arrays.asList(flags); 62 | if (flagsList.contains(Flag.CONCRETE) && !flagsList.contains(Flag.ABSTRACT)) 63 | throw new UnsupportedOperationException("Windows does not support concrete file names for pipes."); 64 | 65 | if (flagsList.contains(Flag.REMOTE)) { 66 | String[] parts = name.split(":"); 67 | if (parts.length != 2) 68 | throw new IllegalArgumentException("For a remote pipe, the name must be in the format host:name"); 69 | return "\\\\" + parts[0] + "\\pipe\\" + validateName(parts[1], flags); 70 | } else { 71 | return "\\\\.\\pipe\\" + validateName(name, flags); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /forker-pipes/src/main/java/com/sshtools/forker/pipes/PipeFactory.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.pipes; 2 | 3 | import java.io.IOException; 4 | import java.net.ServerSocket; 5 | import java.net.Socket; 6 | 7 | /** 8 | * A factory for creating pipes. 9 | */ 10 | public interface PipeFactory { 11 | 12 | /** 13 | * Flags 14 | */ 15 | public class Flag { 16 | 17 | /** 18 | * Use a virtual file (if supported). E.g. on unix this would be an 'abstract 19 | * path'. If the underlying OS does not support it an 20 | * {@link UnsupportedOperationException} will be thrown, unless 21 | * {@link Flag#CONCRETE} is specified and that is supported. 22 | */ 23 | public final static Flag ABSTRACT = new Flag(); 24 | 25 | /** 26 | * Use a concrete file (if supported). E.g. on unix this would be an 'pathname'. 27 | * If the underlying OS does not support it an 28 | * {@link UnsupportedOperationException} will be thrown, unless 29 | * {@link Flag#ABSTRACT} is specified and that is supported. 30 | */ 31 | public final static Flag CONCRETE = new Flag(); 32 | 33 | /** 34 | * Access a remote pipe (if supported). The name must be in the format 35 | * host:name if this flag is set. If the underlying OS does not 36 | * support it an {@link UnsupportedOperationException} will be thrown, unless 37 | * {@link Flag#ABSTRACT} is specified and that is supported. 38 | */ 39 | public final static Flag REMOTE = new Flag(); 40 | 41 | /** 42 | * Wait for a pipe to becoming available before opening it. When this flag 43 | * is not present, an exception will be thrown when the pipe is created. 44 | */ 45 | public final static Flag WAIT = new Flag(); 46 | } 47 | 48 | /** 49 | * Creates a new pipe. 50 | * 51 | * @param name the name 52 | * @param flags flags 53 | * @return the socket 54 | * @throws IOException Signals that an I/O exception has occurred. 55 | */ 56 | Socket createPipe(String name, Flag... flags) throws IOException; 57 | 58 | /** 59 | * Creates a new pipe server. 60 | * 61 | * @param name the name 62 | * @param flags flags 63 | * @return the server socket 64 | * @throws IOException Signals that an I/O exception has occurred. 65 | */ 66 | ServerSocket createPipeServer(String name, Flag... flags) throws IOException; 67 | } 68 | -------------------------------------------------------------------------------- /forker-pipes/src/main/java/com/sshtools/forker/pipes/Unix.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.pipes; 2 | 3 | import com.sshtools.forker.common.CSystem; 4 | import com.sun.jna.LastErrorException; 5 | 6 | /** 7 | * Unix utilities. 8 | */ 9 | public class Unix { 10 | static String formatError(LastErrorException lee) { 11 | try { 12 | return CSystem.INSTANCE.strerror(lee.getErrorCode()); 13 | } catch (Throwable t) { 14 | return lee.getMessage(); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /forker-pipes/src/main/java/com/sshtools/forker/pipes/UnixDomainServerSocket.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.pipes; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.net.ServerSocket; 6 | import java.net.Socket; 7 | import java.net.SocketAddress; 8 | import java.util.concurrent.atomic.AtomicBoolean; 9 | 10 | import com.sshtools.forker.common.CSystem; 11 | import com.sshtools.forker.common.CSystem.SockAddr; 12 | import com.sshtools.forker.pipes.PipeFactory.Flag; 13 | import com.sun.jna.LastErrorException; 14 | import com.sun.jna.Platform; 15 | 16 | /** 17 | * Unix domain server socket 18 | */ 19 | public class UnixDomainServerSocket extends ServerSocket { 20 | private final AtomicBoolean lock = new AtomicBoolean(); 21 | private final int fd; 22 | private final SockAddr address; 23 | private boolean closed; 24 | 25 | UnixDomainServerSocket(String path, Flag... flags) throws IOException { 26 | if (Platform.isWindows() || Platform.isWindowsCE()) { 27 | throw new IOException("Unix domain sockets are not supported on Windows"); 28 | } 29 | File socketFile = new File(path); 30 | socketFile.delete(); 31 | address = new SockAddr(path); 32 | socketFile.deleteOnExit(); 33 | lock.set(false); 34 | try { 35 | fd = CSystem.INSTANCE.socket(CSystem.AF_UNIX, CSystem.SOCK_STREAM, CSystem.PROTOCOL); 36 | } catch (LastErrorException lee) { 37 | throw new IOException("native socket() failed : " + Unix.formatError(lee)); 38 | } 39 | } 40 | 41 | /** 42 | * Bind. 43 | * 44 | * @param endpoint the endpoint 45 | * @param backlog the backlog 46 | * @throws IOException Signals that an I/O exception has occurred. 47 | */ 48 | @Override 49 | public void bind(SocketAddress endpoint, int backlog) throws IOException { 50 | try { 51 | CSystem.INSTANCE.bind(fd, address, address.size()); 52 | } catch (LastErrorException lee) { 53 | throw new IOException("native socket() failed : " + Unix.formatError(lee)); 54 | } 55 | try { 56 | CSystem.INSTANCE.listen(fd, backlog); 57 | } catch (LastErrorException lee) { 58 | throw new IOException("native socket() failed : " + Unix.formatError(lee)); 59 | } 60 | } 61 | 62 | /** 63 | * Accept. 64 | * 65 | * @return the socket 66 | * @throws IOException Signals that an I/O exception has occurred. 67 | */ 68 | @Override 69 | public Socket accept() throws IOException { 70 | int tfd; 71 | try { 72 | tfd = CSystem.INSTANCE.accept(fd, null, 0); 73 | } catch (LastErrorException lee) { 74 | throw new IOException("native socket() failed : " + Unix.formatError(lee)); 75 | } 76 | 77 | return new UnixDomainSocket(tfd); 78 | } 79 | 80 | /** 81 | * Checks if is closed. 82 | * 83 | * @return true, if is closed 84 | */ 85 | @Override 86 | public boolean isClosed() { 87 | return closed; 88 | } 89 | 90 | /** 91 | * Close. 92 | * 93 | * @throws IOException Signals that an I/O exception has occurred. 94 | */ 95 | @Override 96 | public void close() throws IOException { 97 | if (!lock.getAndSet(true)) { 98 | try { 99 | CSystem.INSTANCE.close(fd); 100 | } catch (LastErrorException lee) { 101 | throw new IOException("native close() failed : " + Unix.formatError(lee)); 102 | } finally { 103 | closed = true; 104 | } 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /forker-pipes/src/main/java/com/sshtools/forker/pipes/package-info.java: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Abstraction of operating system pipes. 4 | */ 5 | package com.sshtools.forker.pipes; -------------------------------------------------------------------------------- /forker-pipes/src/main/java/com/sshtools/forker/pipes/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Abstraction of operating system pipes. 6 | 7 | -------------------------------------------------------------------------------- /forker-pipes/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Pipes module 3 | */ 4 | module com.sshtools.forker.pipes { 5 | requires com.sshtools.forker.common; 6 | exports com.sshtools.forker.pipes; 7 | } -------------------------------------------------------------------------------- /forker-pipes/src/test/java/com/sshtools/forker/pipes/DefaultPipeFactoryTest.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.pipes; 2 | 3 | import java.io.IOException; 4 | import java.io.OutputStream; 5 | import java.net.ServerSocket; 6 | import java.net.Socket; 7 | 8 | import org.junit.BeforeClass; 9 | import org.junit.Test; 10 | 11 | import com.sshtools.forker.pipes.PipeFactory.Flag; 12 | 13 | public class DefaultPipeFactoryTest { 14 | private static DefaultPipeFactory factory; 15 | 16 | @BeforeClass 17 | public static void createFactory() throws Exception { 18 | factory = new DefaultPipeFactory(); 19 | } 20 | 21 | @Test 22 | public void singlePipe() throws Exception { 23 | String pipeName = "us_xfr"; 24 | ServerSocket srvSock = factory.createPipeServer(pipeName, Flag.ABSTRACT); 25 | Thread srv = new Thread() { 26 | public void run() { 27 | System.out.println("Accepting"); 28 | try (Socket socket = srvSock.accept()) { 29 | System.out.println("Transferring stream"); 30 | socket.getInputStream().transferTo(System.out); 31 | System.out.println("Transferred stream"); 32 | } catch (IOException ie) { 33 | ie.printStackTrace(); 34 | } 35 | } 36 | }; 37 | System.out.println("Starting"); 38 | srv.start(); 39 | 40 | System.out.println("Creating client"); 41 | try (Socket pipe = factory.createPipe(pipeName, Flag.ABSTRACT)) { 42 | System.out.println("Getting output stream"); 43 | try(OutputStream out = pipe.getOutputStream()) { 44 | out.write("Test".getBytes()); 45 | // out.flush(); 46 | } 47 | } 48 | 49 | srv.join(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /forker-pty/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /.settings/ 3 | *.classpath 4 | /bin/ 5 | /linux/ 6 | /logs/ 7 | /tmp/ 8 | -------------------------------------------------------------------------------- /forker-pty/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | forker-pty 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.wst.common.project.facet.core.builder 10 | 11 | 12 | 13 | 14 | org.eclipse.jdt.core.javabuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.wst.validation.validationbuilder 20 | 21 | 22 | 23 | 24 | org.eclipse.m2e.core.maven2Builder 25 | 26 | 27 | 28 | 29 | 30 | org.eclipse.jem.workbench.JavaEMFNature 31 | org.eclipse.wst.common.modulecore.ModuleCoreNature 32 | org.eclipse.jdt.core.javanature 33 | org.eclipse.m2e.core.maven2Nature 34 | org.eclipse.wst.common.project.facet.core.nature 35 | 36 | 37 | -------------------------------------------------------------------------------- /forker-pty/README.md: -------------------------------------------------------------------------------- 1 | # Forker PTY support 2 | 3 | This module adds support for a new I/O mode, [PTYExecutor.PTY](src/main/java/com/sshtools/forker/pty/PTYExecutor.java). 4 | This mode will use [Forker Daemon](../forker-daemon/README.md) to maintain one or more pseudo terminals. 5 | 6 | ## Maven 7 | 8 | This module is not available on Maven Central due to it's dependencies. Instead it can be found at our own Artifactory server :- 9 | 10 | 11 | ```xml 12 | 13 | 14 | opensource-releases 15 | http://artifactory.javassh.com/opensource-snapshots 16 | SSHTOOLS Open Source Releases 17 | 18 | 19 | ``` 20 | 21 | And your dependency configuration :- 22 | 23 | ``` 24 | 25 | 26 | com.sshtools 27 | forker-pty 28 | 1.5 29 | 30 | 31 | ``` 32 | 33 | ## Usage 34 | 35 | Be aware that non-blocking I/O modes do not yet work with the PTY add on. 36 | 37 | ## Example 38 | 39 | ```java 40 | /* 41 | * This example reads from stdin (i.e. the console), so stdin needs to be unbuffered with 42 | * no local echoing at this end of the pipe, the following function 43 | * attempts to do this. 44 | */ 45 | OS.unbufferedStdin(); 46 | 47 | /* PTY requires the daemon, so load it now (or connect to an existing one if you 48 | * have started it yourself). */ 49 | Forker.loadDaemon(); 50 | // Forker.connectDaemon(new Instance("NOAUTH:57872")); 51 | 52 | /* ShellBuilder is a specialisation of ForkerBuilder */ 53 | ShellBuilder shell = new ShellBuilder(); 54 | shell.loginShell(true); 55 | shell.io(PTYExecutor.PTY); 56 | shell.redirectErrorStream(true); 57 | 58 | /* Demonstrate we are actually in a different shell by setting PS1 */ 59 | shell.environment().put("PS1", ">>>"); 60 | 61 | /* Start the shell, giving it a window size listener */ 62 | final Process p = shell.start(new Listener() { 63 | @Override 64 | public void windowSizeChanged(int ptyWidth, int ptyHeight) { 65 | System.out.println("Window size changed to " + ptyWidth + " x " + ptyHeight); 66 | } 67 | }); 68 | 69 | 70 | new Thread() { 71 | public void run() { 72 | try { 73 | IOUtils.copy(System.in, p.getOutputStream()); 74 | } catch (IOException e) { 75 | } finally { 76 | // Close the process input stream when stdin closes, this 77 | // will end the process 78 | try { 79 | p.getOutputStream().close(); 80 | } catch (IOException e) { 81 | } 82 | } 83 | } 84 | }.start(); 85 | IOUtils.copy(p.getInputStream(), System.out); 86 | int ret = p.waitFor(); 87 | System.err.println("Exited with code: " + ret); 88 | System.exit(ret); 89 | ``` 90 | -------------------------------------------------------------------------------- /forker-pty/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | forker-pty 6 | Forker PTY 7 | This module adds PTY capability to forker daemon, alllowing real interactive shells to be launched. 8 | 9 | com.sshtools 10 | forker 11 | 1.8 12 | .. 13 | 14 | 15 | 16 | src/main/java 17 | src/test/java 18 | 19 | 20 | . 21 | src/main/resources 22 | 23 | 24 | 25 | 26 | 27 | 28 | ${project.groupId} 29 | forker-client 30 | ${project.version} 31 | 32 | 33 | net.java.dev.jna 34 | jna 35 | 5.9.0 36 | 37 | 38 | net.java.dev.jna 39 | jna-platform 40 | 5.9.0 41 | 42 | 43 | org.jetbrains.pty4j 44 | pty4j 45 | 0.12.7 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | ext-releases-local 54 | 55 | 56 | false 57 | 58 | https://artifactory.jadaptive.com/ext-releases-local 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /forker-pty/src/main/java/com/sshtools/forker/pty/PTYIO.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.pty; 2 | 3 | import com.sshtools.forker.common.IO.DefaultIO; 4 | 5 | public final class PTYIO extends DefaultIO { 6 | public PTYIO() { 7 | super("PTY", true, true, true, true); 8 | } 9 | } -------------------------------------------------------------------------------- /forker-pty/src/main/java/com/sshtools/forker/pty/PTYProcessFactory.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.pty; 2 | 3 | import java.io.IOException; 4 | 5 | import com.sshtools.forker.client.ForkerBuilder; 6 | import com.sshtools.forker.client.ForkerProcess; 7 | import com.sshtools.forker.client.ForkerProcessFactory; 8 | import com.sshtools.forker.client.ForkerProcessListener; 9 | import com.sshtools.forker.client.NonBlockingProcessListener; 10 | import com.sshtools.forker.common.IO; 11 | import com.sshtools.forker.common.OS; 12 | import com.sun.jna.Platform; 13 | 14 | /** 15 | * Creates a {@link PTYProcess}. 16 | */ 17 | public class PTYProcessFactory implements ForkerProcessFactory { 18 | 19 | @Override 20 | public ForkerProcess createProcess(ForkerBuilder builder, ForkerProcessListener listener) throws IOException { 21 | if (OS.isUnix() && !Platform.isMac() && builder.io() == PTYProcess.PTY) { 22 | if(listener instanceof NonBlockingProcessListener) { 23 | throw new IllegalArgumentException(String.format("%s is not supported by %s, is your I/O mode set correctly (see %s.io(%s))", listener.getClass(), getClass(), ForkerBuilder.class, IO.class)); 24 | } 25 | return new PTYProcess(builder); 26 | } 27 | return null; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /forker-pty/src/main/java/com/sshtools/forker/pty/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 - 2021 SSHTOOLS Limited (support@sshtools.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /** 17 | * Provides the classes required to support creation of Pseudo Terminals for 18 | * interactive shells and other commands. 19 | *

20 | * For example, this in conjunction with other Forker suite facilities (such as 21 | * as privilege escalation and run as user) could be used to create an SSH or 22 | * Telnet server in Java. 23 | */ 24 | package com.sshtools.forker.pty; -------------------------------------------------------------------------------- /forker-pty/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * PTY module 3 | */ 4 | module com.sshtools.forker.pty { 5 | requires transitive com.sshtools.forker.common; 6 | requires transitive pty4j; 7 | requires transitive com.sshtools.forker.client; 8 | exports com.sshtools.forker.pty; 9 | 10 | provides com.sshtools.forker.common.IO 11 | with com.sshtools.forker.pty.PTYIO; 12 | 13 | provides com.sshtools.forker.client.ForkerProcessFactory 14 | with com.sshtools.forker.pty.PTYProcessFactory; 15 | 16 | } -------------------------------------------------------------------------------- /forker-pty/src/main/resources/META-INF/services/com.sshtools.forker.client.ForkerProcessFactory: -------------------------------------------------------------------------------- 1 | com.sshtools.forker.pty.PTYProcessFactory -------------------------------------------------------------------------------- /forker-pty/src/main/resources/META-INF/services/com.sshtools.forker.common.IO: -------------------------------------------------------------------------------- 1 | com.sshtools.forker.pty.PTYIO -------------------------------------------------------------------------------- /forker-services/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /.settings/ 3 | *.classpath 4 | /bin/ 5 | -------------------------------------------------------------------------------- /forker-services/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | forker-services 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.wst.common.project.facet.core.builder 10 | 11 | 12 | 13 | 14 | org.eclipse.jdt.core.javabuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.wst.validation.validationbuilder 20 | 21 | 22 | 23 | 24 | org.eclipse.m2e.core.maven2Builder 25 | 26 | 27 | 28 | 29 | 30 | org.eclipse.jem.workbench.JavaEMFNature 31 | org.eclipse.wst.common.modulecore.ModuleCoreNature 32 | org.eclipse.jdt.core.javanature 33 | org.eclipse.m2e.core.maven2Nature 34 | org.eclipse.wst.common.project.facet.core.nature 35 | 36 | 37 | -------------------------------------------------------------------------------- /forker-services/README.md: -------------------------------------------------------------------------------- 1 | # Forker Services 2 | 3 | ## Introduction 4 | 5 | Forker Services provides a way of starting, stopping and querying local system services. Multiple 6 | implementations are provided for the 3 main operating systems the Forker suite supportes. On 7 | Windows, the *sc* command is currently used (with polling). On Linux and Unix there are multiple 8 | options, with the most efficient being the SystemD DBus based one. 9 | 10 | ## Adding Forker Services To Your Project 11 | 12 | To include the Forker Services in your project, you will need the module :- 13 | 14 | ### Maven 15 | 16 | ``` 17 | 18 | 19 | com.sshtools 20 | forker-services 21 | 1.6 22 | 23 | 24 | ``` 25 | 26 | ## Usage 27 | 28 | The API is very simple. You just need to obtain an instane of a `ServiceService` and call it's various methods to control the local services. 29 | 30 | ### Quick Start 31 | 32 | For the impatient :- 33 | 34 | ```java 35 | 36 | // List services 37 | ServiceService ss = Services.get(); 38 | for(Service s : ss.getServices()) { 39 | System.out.println(s.getNativeName()); 40 | } 41 | 42 | // Stop a service 43 | ss.stopService(ss.getService("ntp")); 44 | 45 | // Start a service 46 | ss.startService(ss.getService("ntp")); 47 | 48 | // Listen for changes in service state 49 | ss.addListener(new ServicesListener() { 50 | 51 | void stateChanged(Service service) { 52 | // Services has started, stopped, etc 53 | } 54 | 55 | void extendedStatusChanged(Service service, ExtendedServiceStatus extStatus) { 56 | // Extended information about the state change if available, called after stateChange() 57 | } 58 | 59 | void serviceAdded(Service service) { 60 | // New service has appeared 61 | } 62 | 63 | void serviceRemoved(Service service) { 64 | // Service has disappeated 65 | } 66 | }); 67 | 68 | ``` 69 | 70 | ### Usage 71 | 72 | #### Obtaining ServiceService 73 | 74 | ##### Using The Services Helper 75 | 76 | The default and easiest way to initialize and obtain a `ServiceService` is to call `Services.get()`. By default, this will detected the best implementation to use for your platform. 77 | 78 | You can specify you own implementation by setting the system property `forker.services.impl` before `get()` is called. 79 | 80 | You can also call `Services.set(myServiceService)`, and again, this must be done before `get()` is called. 81 | 82 | ##### Manually 83 | 84 | If you have you own platform detecting logic, simply instantiate the appropriate implementation yourself. 85 | 86 | ```java 87 | 88 | ServiceService ss; 89 | if(getMyOS().equals("WINDOWS")) { 90 | ss = new Win32ServiceService(); 91 | } 92 | else { 93 | // TODO detect other OSs 94 | } 95 | 96 | // You must then initialize with your own instance of ServicesContext 97 | ss.configure(new MyContext()); 98 | 99 | ``` 100 | -------------------------------------------------------------------------------- /forker-services/src/main/java/com/sshtools/forker/services/DefaultContext.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 - 2021 SSHTOOLS Limited (support@sshtools.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sshtools.forker.services; 17 | 18 | import java.io.IOException; 19 | import java.util.concurrent.Callable; 20 | import java.util.concurrent.Executors; 21 | import java.util.concurrent.ScheduledExecutorService; 22 | import java.util.concurrent.ScheduledFuture; 23 | import java.util.concurrent.TimeUnit; 24 | 25 | public class DefaultContext implements ServicesContext { 26 | private ScheduledExecutorService executor; 27 | { 28 | executor = Executors.newScheduledThreadPool(1); 29 | } 30 | 31 | @Override 32 | public ScheduledFuture schedule(Runnable runnable, long initialDelay, long delay, TimeUnit units) { 33 | return executor.scheduleAtFixedRate(runnable, initialDelay, delay, units); 34 | } 35 | 36 | @Override 37 | public void call(Callable callable) { 38 | executor.schedule(callable, 0, TimeUnit.MILLISECONDS); 39 | } 40 | 41 | @Override 42 | public void close() throws IOException { 43 | executor.shutdown(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /forker-services/src/main/java/com/sshtools/forker/services/ExtendedServiceStatus.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 - 2021 SSHTOOLS Limited (support@sshtools.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sshtools.forker.services; 17 | 18 | import javax.swing.JComponent; 19 | 20 | public interface ExtendedServiceStatus { 21 | int getMessageType(); 22 | 23 | String getId(); 24 | 25 | String getText(); 26 | 27 | Throwable getError(); 28 | 29 | JComponent getAccessory(); 30 | } 31 | -------------------------------------------------------------------------------- /forker-services/src/main/java/com/sshtools/forker/services/Service.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 - 2021 SSHTOOLS Limited (support@sshtools.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sshtools.forker.services; 17 | 18 | public interface Service { 19 | 20 | public interface Listener { 21 | void extendedServiceStatusChanged(Service service, ExtendedServiceStatus newStatus); 22 | } 23 | 24 | public enum Status { 25 | STARTED, STARTING, STOPPED, STOPPING, PAUSING, PAUSED, UNPAUSING, UNKNOWN; 26 | 27 | public boolean isRunning() { 28 | return this == STARTED || this == STARTING || this == PAUSED || this ==PAUSING || this == PAUSED; 29 | } 30 | } 31 | 32 | void addListener(Listener l); 33 | 34 | void removeListener(Listener l); 35 | 36 | void configure(ServiceService service); 37 | 38 | String getNativeName(); 39 | 40 | Status getStatus(); 41 | 42 | ExtendedServiceStatus getExtendedStatus(); 43 | } 44 | -------------------------------------------------------------------------------- /forker-services/src/main/java/com/sshtools/forker/services/ServiceService.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 - 2021 SSHTOOLS Limited (support@sshtools.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sshtools.forker.services; 17 | 18 | import java.io.Closeable; 19 | import java.io.IOException; 20 | import java.util.List; 21 | 22 | public interface ServiceService extends Closeable { 23 | public final static String BUNDLE = "services"; 24 | 25 | void configure(ServicesContext context); 26 | 27 | void addListener(ServicesListener listener); 28 | 29 | void removeListener(ServicesListener listener); 30 | 31 | List getServices() throws IOException; 32 | 33 | void restartService(Service service) throws Exception; 34 | 35 | void startService(Service service) throws Exception; 36 | 37 | void pauseService(Service service) throws Exception; 38 | 39 | void unpauseService(Service service) throws Exception; 40 | 41 | void stopService(Service service) throws Exception; 42 | 43 | void setStartOnBoot(Service service, boolean startOnBoot) throws Exception; 44 | 45 | boolean isStartOnBoot(Service service) throws Exception; 46 | 47 | Service getService(String name) throws IOException; 48 | 49 | default boolean hasService(String name) throws IOException { 50 | return getService(name) != null; 51 | } 52 | 53 | default void close() throws IOException { 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /forker-services/src/main/java/com/sshtools/forker/services/ServiceState.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 - 2021 SSHTOOLS Limited (support@sshtools.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sshtools.forker.services; 17 | 18 | public enum ServiceState { 19 | STOPPED, STARTING, STARTED, STOPPING 20 | } 21 | -------------------------------------------------------------------------------- /forker-services/src/main/java/com/sshtools/forker/services/ServicesContext.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 - 2021 SSHTOOLS Limited (support@sshtools.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sshtools.forker.services; 17 | 18 | import java.io.Closeable; 19 | import java.util.concurrent.Callable; 20 | import java.util.concurrent.ScheduledFuture; 21 | import java.util.concurrent.TimeUnit; 22 | 23 | public interface ServicesContext extends Closeable { 24 | ScheduledFuture schedule(Runnable runnable, long initialDelay, long delay, TimeUnit units); 25 | 26 | void call(Callable callable); 27 | } 28 | -------------------------------------------------------------------------------- /forker-services/src/main/java/com/sshtools/forker/services/ServicesListener.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 - 2021 SSHTOOLS Limited (support@sshtools.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sshtools.forker.services; 17 | 18 | public interface ServicesListener { 19 | 20 | default void stateChanged(Service service) { 21 | } 22 | 23 | default void extendedStatusChanged(Service service, ExtendedServiceStatus extStatus) { 24 | } 25 | 26 | default void serviceAdded(Service service) { 27 | } 28 | 29 | default void serviceRemoved(Service service) { 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /forker-services/src/main/java/com/sshtools/forker/services/impl/AbstractServiceService.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 - 2021 SSHTOOLS Limited (support@sshtools.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sshtools.forker.services.impl; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | import com.sshtools.forker.services.ExtendedServiceStatus; 22 | import com.sshtools.forker.services.Service; 23 | import com.sshtools.forker.services.ServiceService; 24 | import com.sshtools.forker.services.ServicesListener; 25 | 26 | public abstract class AbstractServiceService implements ServiceService { 27 | 28 | private List listeners = new ArrayList<>(); 29 | 30 | @Override 31 | public void addListener(ServicesListener listener) { 32 | listeners.add(listener); 33 | } 34 | 35 | @Override 36 | public void removeListener(ServicesListener listener) { 37 | listeners.remove(listener); 38 | } 39 | 40 | @Override 41 | public void pauseService(Service service) throws Exception { 42 | throw new UnsupportedOperationException(); 43 | } 44 | 45 | @Override 46 | public void unpauseService(Service service) throws Exception { 47 | throw new UnsupportedOperationException(); 48 | } 49 | 50 | protected void fireServiceRemoved(Service s) { 51 | // Removed 52 | for (ServicesListener l : listeners) { 53 | l.serviceRemoved(s); 54 | } 55 | } 56 | 57 | protected void fireStateChange(Service s) { 58 | // PointerState has change 59 | for (ServicesListener l : listeners) { 60 | l.stateChanged(s); 61 | } 62 | } 63 | 64 | protected void fireServiceAdded(Service s) { 65 | // This is a new service 66 | for (ServicesListener l : listeners) { 67 | l.serviceAdded(s); 68 | } 69 | } 70 | 71 | protected void fireExtendedServiceStatusChanged(Service service, ExtendedServiceStatus newStatus) { 72 | for (ServicesListener l : listeners) { 73 | l.extendedStatusChanged(service, newStatus); 74 | } 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /forker-services/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * PTY module 3 | */ 4 | module com.sshtools.forker.services { 5 | requires transitive com.sshtools.forker.common; 6 | requires transitive com.sshtools.forker.client; 7 | requires java.logging; 8 | requires java.desktop; 9 | requires org.freedesktop.dbus; 10 | requires de.thjom.java.systemd; 11 | exports com.sshtools.forker.services; 12 | exports com.sshtools.forker.services.impl; 13 | 14 | } -------------------------------------------------------------------------------- /forker-updater-console/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | *.classpath 3 | *.project 4 | /.settings/ 5 | -------------------------------------------------------------------------------- /forker-updater-console/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | forker-updater-console 6 | Forker Update Console 7 | Console implementation of updater / installer. 8 | 9 | com.sshtools 10 | forker 11 | 1.8 12 | .. 13 | 14 | 15 | 16 | src/main/java 17 | src/test/java 18 | target/classes 19 | target/test-classes 20 | 21 | 22 | . 23 | src/main/resources 24 | 25 | 26 | 27 | 28 | . 29 | src/test/resources 30 | 31 | 32 | 33 | 34 | 35 | 36 | ${project.groupId} 37 | ${project.version} 38 | forker-updater 39 | 40 | 41 | org.fusesource.jansi 42 | jansi 43 | 2.2.0 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /forker-updater-console/src/main/java/com/sshtools/forker/updater/console/ConsoleSystem.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.updater.console; 2 | 3 | import org.fusesource.jansi.AnsiConsole; 4 | 5 | public class ConsoleSystem { 6 | 7 | private static ConsoleSystem instance; 8 | 9 | public static ConsoleSystem get() { 10 | if (instance == null) { 11 | instance = new ConsoleSystem(); 12 | } 13 | return instance; 14 | } 15 | 16 | private boolean enabled; 17 | 18 | private ConsoleSystem() { 19 | } 20 | 21 | public void activate() { 22 | if (!enabled) { 23 | AnsiConsole.systemInstall(); 24 | enabled = true; 25 | } 26 | } 27 | 28 | public void deactivate() { 29 | if (enabled) { 30 | AnsiConsole.systemUninstall(); 31 | enabled = false; 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /forker-updater-console/src/main/java/com/sshtools/forker/updater/console/ConsoleUpdateHandler.java: -------------------------------------------------------------------------------- 1 | 2 | package com.sshtools.forker.updater.console; 3 | 4 | import java.util.concurrent.Callable; 5 | 6 | import org.fusesource.jansi.Ansi; 7 | import org.fusesource.jansi.Ansi.Erase; 8 | 9 | import com.sshtools.forker.updater.Entry; 10 | import com.sshtools.forker.updater.UpdateHandler; 11 | import com.sshtools.forker.updater.UpdateSession; 12 | 13 | public class ConsoleUpdateHandler extends AbstractConsoleHandler implements UpdateHandler { 14 | 15 | @Override 16 | public void startDownloads() throws Exception { 17 | println("Updating"); 18 | } 19 | 20 | @Override 21 | public void startDownloadFile(Entry file, int index) throws Exception { 22 | this.currentIndex = index; 23 | this.currentDest = file.path().getFileName().toString(); 24 | this.currentFrac = 0; 25 | updateRow(); 26 | } 27 | 28 | @Override 29 | public void updateDownloadFileProgress(Entry file, float frac) throws Exception { 30 | } 31 | 32 | @Override 33 | public void doneDownloadFile(Entry file) throws Exception { 34 | } 35 | 36 | @Override 37 | public void updateDone(boolean updateError) { 38 | Ansi ansi = Ansi.ansi(); 39 | println(ansi.cursorToColumn(0).bold().a("Update complete.").eraseLine(Erase.FORWARD).toString()); 40 | } 41 | 42 | @Override 43 | public void updateDownloadProgress(float frac) throws Exception { 44 | currentFrac = frac; 45 | updateRow(); 46 | } 47 | 48 | @Override 49 | public void startUpdateRollback() { 50 | Ansi ansi = Ansi.ansi(); 51 | println(ansi.cursorToColumn(0).bold().a("Starting rollback.").eraseLine(Erase.FORWARD).toString()); 52 | this.currentIndex = 0; 53 | this.currentDest = "Rollback"; 54 | this.currentFrac = 0; 55 | updateRow(); 56 | } 57 | 58 | @Override 59 | public void updateRollbackProgress(float progress) { 60 | currentFrac = progress; 61 | updateRow(); 62 | } 63 | 64 | @Override 65 | public Void prep(Callable callback) { 66 | return null; 67 | } 68 | 69 | @Override 70 | public Void value() { 71 | return null; 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /forker-updater-console/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module com.sshtools.forker.updater.console { 2 | requires transitive com.sshtools.forker.updater; 3 | requires transitive org.fusesource.jansi; 4 | exports com.sshtools.forker.updater.console; 5 | provides com.sshtools.forker.updater.UpdateHandler with com.sshtools.forker.updater.console.ConsoleUpdateHandler; 6 | provides com.sshtools.forker.updater.InstallHandler with com.sshtools.forker.updater.console.ConsoleInstallHandler; 7 | } -------------------------------------------------------------------------------- /forker-updater-console/src/main/resources/META-INF/services/com.sshtools.forker.updater.InstallHandler: -------------------------------------------------------------------------------- 1 | com.sshtools.forker.updater.console.ConsoleInstallHandler -------------------------------------------------------------------------------- /forker-updater-console/src/main/resources/META-INF/services/com.sshtools.forker.updater.UpdateHandler: -------------------------------------------------------------------------------- 1 | com.sshtools.forker.updater.console.ConsoleUpdateHandler -------------------------------------------------------------------------------- /forker-updater-example/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | *.classpath 3 | *.project 4 | /.settings/ 5 | -------------------------------------------------------------------------------- /forker-updater-example/src/main/installer/left-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sshtools/forker/cd8439c8ff5a9f4a1798accadd4e9b75063a444e/forker-updater-example/src/main/installer/left-banner.png -------------------------------------------------------------------------------- /forker-updater-example/src/main/java/hello/world/HelloWorld.java: -------------------------------------------------------------------------------- 1 | package hello.world; 2 | 3 | import java.awt.BorderLayout; 4 | import java.awt.Dimension; 5 | import java.awt.event.WindowAdapter; 6 | import java.awt.event.WindowEvent; 7 | 8 | import javax.swing.JFrame; 9 | import javax.swing.JLabel; 10 | 11 | @SuppressWarnings("serial") 12 | public class HelloWorld extends JFrame { 13 | 14 | public HelloWorld() { 15 | super("HelloWorld"); 16 | setPreferredSize(new Dimension(300, 200)); 17 | setLayout(new BorderLayout()); 18 | add(new JLabel("Hello World!", JLabel.CENTER), BorderLayout.CENTER); 19 | } 20 | 21 | public static void main(String[] args) { 22 | HelloWorld world = new HelloWorld(); 23 | world.addWindowListener(new WindowAdapter() { 24 | @Override 25 | public void windowClosing(WindowEvent e) { 26 | System.exit(0); 27 | } 28 | }); 29 | world.pack(); 30 | world.setVisible(true); 31 | world.setLocation(200, 200); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /forker-updater-example/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module hello.world { 2 | requires java.desktop; 3 | exports hello.world; 4 | opens hello.world; 5 | } -------------------------------------------------------------------------------- /forker-updater-maven-plugin/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /.settings/ 3 | /.classpath 4 | /.project 5 | -------------------------------------------------------------------------------- /forker-updater-maven-plugin/src/main/java/com/sshtools/forker/updater/maven/plugin/App.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.updater.maven.plugin; 2 | 3 | public class App { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /forker-updater-maven-plugin/src/main/java/com/sshtools/forker/updater/maven/plugin/AppFile.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.updater.maven.plugin; 2 | 3 | import org.apache.maven.plugins.annotations.Parameter; 4 | 5 | public final class AppFile { 6 | 7 | @Parameter(property = "source", required = true) String source; 8 | 9 | @Parameter(property = "target", defaultValue = ".", required = true) String target = "."; 10 | 11 | } -------------------------------------------------------------------------------- /forker-updater-maven-plugin/src/main/java/com/sshtools/forker/updater/maven/plugin/BootstrapFile.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.updater.maven.plugin; 2 | 3 | import org.apache.maven.plugins.annotations.Parameter; 4 | 5 | public final class BootstrapFile { 6 | 7 | @Parameter(property = "source", required = true) String source; 8 | 9 | @Parameter(property = "target", defaultValue = ".", required = true) String target = "."; 10 | 11 | } -------------------------------------------------------------------------------- /forker-updater-maven-plugin/src/main/java/com/sshtools/forker/updater/maven/plugin/SelfExtractingExecutableBuilder.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.updater.maven.plugin; 2 | 3 | import java.io.IOException; 4 | import java.io.PrintWriter; 5 | import java.nio.file.DirectoryStream; 6 | import java.nio.file.Files; 7 | import java.nio.file.Path; 8 | 9 | import org.apache.maven.plugin.logging.Log; 10 | 11 | import com.sshtools.forker.client.OSCommand; 12 | import com.sun.jna.Platform; 13 | 14 | public class SelfExtractingExecutableBuilder { 15 | 16 | private Path output; 17 | private Path script; 18 | private Path image; 19 | private Log log; 20 | 21 | public Path output() { 22 | return output; 23 | } 24 | 25 | public SelfExtractingExecutableBuilder output(Path output) { 26 | this.output = output; 27 | return this; 28 | } 29 | 30 | public Path script() { 31 | return script; 32 | } 33 | 34 | public SelfExtractingExecutableBuilder script(Path script) { 35 | this.script = script; 36 | return this; 37 | } 38 | 39 | public Path image() { 40 | return image; 41 | } 42 | 43 | public SelfExtractingExecutableBuilder image(Path image) { 44 | this.image = image; 45 | return this; 46 | } 47 | 48 | public void make() throws IOException { 49 | if(!Files.exists(output)) 50 | Files.createDirectories(output); 51 | 52 | if (Platform.isWindows() ? OSCommand.hasCommand("makensisw") : OSCommand.hasCommand("makensis")) { 53 | Path artifactName = output.getFileName(); 54 | Path nsisDir = image; 55 | Path nsisPath = nsisDir.resolve(artifactName.toString() + ".nsis"); 56 | try { 57 | try (PrintWriter w = new PrintWriter( 58 | Files.newBufferedWriter(nsisPath))) { 59 | w.println("#"); 60 | w.println(String.format("OutFile \"%s\"", 61 | output.resolve(artifactName.toString() + ".exe"))); 62 | w.println(); 63 | w.println("# set desktop as install directory"); 64 | w.println("InstallDir $TEMP"); 65 | w.println(); 66 | w.println("# default section start; every NSIS script has at least one section."); 67 | w.println("Section"); 68 | w.println(); 69 | w.println(" SetOutPath $INSTDIR"); 70 | 71 | try (DirectoryStream stream = Files.newDirectoryStream(image)) { 72 | for (Path path : stream) { 73 | if(path.equals(nsisPath)) { 74 | if (Files.isDirectory(path)) { 75 | w.println(String.format(" File /r \"%s\"", path)); 76 | } 77 | else { 78 | w.println(String.format(" File \"%s\"", path)); 79 | } 80 | } 81 | } 82 | } 83 | 84 | w.println(); 85 | w.println("# default section end."); 86 | w.println("SectionEnd"); 87 | } 88 | 89 | OSCommand.runCommand(Platform.isWindows() ? "makensisw.exe" : "makensis", 90 | nsisPath.toString()); 91 | 92 | 93 | } 94 | finally { 95 | /* Clean up */ 96 | Files.delete(nsisPath); 97 | } 98 | 99 | return; 100 | } 101 | throw new UnsupportedOperationException(); 102 | } 103 | 104 | public Log log() { 105 | return log; 106 | } 107 | 108 | public SelfExtractingExecutableBuilder log(Log log) { 109 | this.log = log; 110 | return this; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /forker-updater-swt/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | *.classpath 3 | *.project 4 | /.settings/ 5 | -------------------------------------------------------------------------------- /forker-updater-swt/src/main/.gitignore: -------------------------------------------------------------------------------- 1 | /java11-processed/ 2 | -------------------------------------------------------------------------------- /forker-updater-swt/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module com.sshtools.forker.updater.swt { 2 | requires java.desktop; 3 | requires java.prefs; 4 | requires transitive org.eclipse.swt; 5 | 6 | /* Jenkins is linux so we must use this for now until a better solution 7 | * is found. SWT (OSGi?) does not play well with JPMS 8 | */ 9 | requires transitive org.eclipse.swt.gtk.linux.x86_64; 10 | // requires static org.eclipse.swt.win32.win32.x86_64; 11 | 12 | requires transitive com.sshtools.forker.updater; 13 | requires transitive com.sshtools.forker.wrapper; 14 | exports com.sshtools.forker.updater.swt; 15 | opens com.sshtools.forker.updater.swt; 16 | provides com.sshtools.forker.updater.UpdateHandler with com.sshtools.forker.updater.swt.SWTUpdateHandler; 17 | provides com.sshtools.forker.updater.InstallHandler with com.sshtools.forker.updater.swt.SWTInstallHandler; 18 | provides com.sshtools.forker.updater.UninstallHandler with com.sshtools.forker.updater.swt.SWTUninstallHandler; 19 | } -------------------------------------------------------------------------------- /forker-updater-swt/src/main/resources/META-INF/services/com.sshtools.forker.updater.InstallHandler: -------------------------------------------------------------------------------- 1 | com.sshtools.forker.updater.swt.SWTInstallHandler -------------------------------------------------------------------------------- /forker-updater-swt/src/main/resources/META-INF/services/com.sshtools.forker.updater.UninstallHandler: -------------------------------------------------------------------------------- 1 | com.sshtools.forker.updater.swt.SWTUninstallHandler -------------------------------------------------------------------------------- /forker-updater-swt/src/main/resources/META-INF/services/com.sshtools.forker.updater.UpdateHandler: -------------------------------------------------------------------------------- 1 | com.sshtools.forker.updater.swt.SWTUpdateHandler -------------------------------------------------------------------------------- /forker-updater-swt/src/main/resources/com/sshtools/forker/updater/swt/Bootstrap.properties: -------------------------------------------------------------------------------- 1 | title=Snake Updater -------------------------------------------------------------------------------- /forker-updater-swt/src/main/resources/com/sshtools/forker/updater/swt/Install.properties: -------------------------------------------------------------------------------- 1 | min=\uf2d1 2 | close=\uf2d3 3 | preparing=Preparing 4 | title=Snake Installer 5 | browse=Browse 6 | target=Directory: 7 | install=\uf019 8 | failed=Failed. {0} 9 | success=Success, launching! 10 | startInstall=Starting installation. 11 | installFile=Installing file {0}. 12 | installFileDone=Installed file {0}. 13 | selectTarget=Choose Destination Folder 14 | -------------------------------------------------------------------------------- /forker-updater-swt/src/main/resources/com/sshtools/forker/updater/swt/Update.properties: -------------------------------------------------------------------------------- 1 | min=\uf2d1 2 | close=\uf2d3 3 | title=Snake 4 | check=Checking for updates .. 5 | ready=Ready to update! 6 | startCheckUpdates=Update check started. 7 | startCheckUpdateFile=Checking {0}. 8 | doneCheckUpdateFile=Checked {0}. 9 | doneCheckUpdates=Update check done. 10 | startDownloads=Starting downloads. 11 | startDownloadFile=Downloading file {0}. 12 | validatingFile=Validating {0}. 13 | doneDownloadFile=Done downloading {0}. 14 | doneDownloads=Downloads complete. 15 | failed=Failed. {0} 16 | success=Success, launching! 17 | noUpdates=No updates, launching now! -------------------------------------------------------------------------------- /forker-updater/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /.settings/ 3 | *.classpath 4 | /bin/ 5 | *.project 6 | -------------------------------------------------------------------------------- /forker-updater/README.md: -------------------------------------------------------------------------------- 1 | # Forker Updater 2 | 3 | This is the core module for Forker Updater System. This extends Forker Wrapper to allow self extracting installers or archives for updateable applications with minimal configuration for Maven projects. 4 | 5 | ## Goals 6 | 7 | * Support 3 major operating systems initially. WIP - Linux complete, Windows partial. Others TBG 8 | * As close to zero configuration as possible, either discovering or using convention over configuration. WIP - More analysis to discover system modules, quirks etc. 9 | * Non-destructive, installation and updates can rollback at any failure point. 10 | * Efficient. Only download files that need updating. 11 | * Consistent. A freshly installed application has exactly the same layout as files in the update repository. 12 | * Familiar. Apps can be distributed as simple archives, or self extracting executables. WIP more operating system support and formats required. 13 | * No special update server required, it can be just an HTTP server. 14 | * Multiple toolkit support. Depending on needs, simple console, colour console or SWT UIs can be used. Further toolkits can be easily added (Swing planned, JavaFX may be ported back from Snake project). 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /forker-updater/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | forker-updater 4 | Forker Updater 5 | Extends forker-wrapper to add update features. 6 | 7 | com.sshtools 8 | forker 9 | 1.8 10 | .. 11 | 12 | 13 | 14 | src/main/java 15 | src/test/java 16 | target/classes 17 | target/test-classes 18 | 19 | 20 | . 21 | src/main/resources 22 | 23 | 24 | 25 | 26 | . 27 | src/test/resources 28 | 29 | 30 | 31 | 32 | com.github.maven-nar 33 | nar-maven-plugin 34 | 3.2.3 35 | true 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | ${project.groupId} 44 | forker-wrapper 45 | ${project.version} 46 | 47 | 48 | 49 | 50 | 51 | ext-snapshots-local 52 | ext-snapshots-local 53 | https://artifactory.jadaptive.com/ext-snapshots-local 54 | 55 | false 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /forker-updater/src/main/java/com/sshtools/forker/updater/AbstractHandler.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.updater; 2 | 3 | import java.io.Console; 4 | import java.io.PrintWriter; 5 | 6 | public abstract class AbstractHandler implements Handler { 7 | 8 | protected Console console; 9 | protected PrintWriter out; 10 | 11 | protected AbstractHandler() { 12 | console = System.console(); 13 | out = console == null ? new PrintWriter(System.out) : console.writer(); 14 | } 15 | 16 | protected void println(String str) { 17 | out.println(str); 18 | } 19 | 20 | protected void print(String str) { 21 | out.print(str); 22 | } 23 | 24 | @Override 25 | public boolean isCancelled() { 26 | return false; 27 | } 28 | 29 | @Override 30 | public void complete() { 31 | } 32 | 33 | @Override 34 | public void failed(Throwable error) { 35 | error.printStackTrace(out); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /forker-updater/src/main/java/com/sshtools/forker/updater/DefaultConsoleUninstallHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Mordechai Meisels 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | package com.sshtools.forker.updater; 17 | 18 | import java.io.BufferedReader; 19 | import java.io.IOException; 20 | import java.io.InputStreamReader; 21 | import java.nio.file.Path; 22 | import java.util.concurrent.Callable; 23 | 24 | public class DefaultConsoleUninstallHandler extends AbstractHandler implements UninstallHandler { 25 | 26 | private boolean deleteAll; 27 | 28 | @Override 29 | public void init(UninstallSession context) { 30 | } 31 | 32 | @Override 33 | public void startUninstall() throws Exception { 34 | } 35 | 36 | @Override 37 | public void uninstallFile(Path file, Path dest, int index) throws Exception { 38 | } 39 | 40 | @Override 41 | public void uninstallFileProgress(Path file, float frac) throws Exception { 42 | } 43 | 44 | @Override 45 | public void uninstallProgress(float frac) throws Exception { 46 | } 47 | 48 | @Override 49 | public void uninstallFileDone(Path file) throws Exception { 50 | } 51 | 52 | @Override 53 | public void uninstallDone() { 54 | } 55 | 56 | @Override 57 | public Boolean prep(Callable callable) { 58 | String answer = prompt("Delete all files (Y)es/(No)?: "); 59 | return deleteAll = answer.equals("y") || answer.equals("yes"); 60 | } 61 | 62 | private String prompt(String text, Object... args) { 63 | if (console == null) { 64 | try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) { 65 | print(String.format(text, args)); 66 | return reader.readLine(); 67 | } catch (IOException ioe) { 68 | throw new IllegalStateException("Failed to get prompt.", ioe); 69 | } 70 | } else { 71 | return console.readLine(text, args); 72 | } 73 | } 74 | 75 | @Override 76 | public Boolean value() { 77 | return deleteAll; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /forker-updater/src/main/java/com/sshtools/forker/updater/DefaultConsoleUpdateHandler.java: -------------------------------------------------------------------------------- 1 | 2 | package com.sshtools.forker.updater; 3 | 4 | import java.util.concurrent.Callable; 5 | 6 | public class DefaultConsoleUpdateHandler extends AbstractHandler implements UpdateHandler { 7 | 8 | @Override 9 | public void init(UpdateSession context) { 10 | } 11 | 12 | @Override 13 | public void startDownloads() throws Exception { 14 | } 15 | 16 | @Override 17 | public void startDownloadFile(Entry file, int index) throws Exception { 18 | } 19 | 20 | @Override 21 | public void updateDownloadFileProgress(Entry file, float frac) throws Exception { 22 | } 23 | 24 | @Override 25 | public void doneDownloadFile(Entry file) throws Exception { 26 | } 27 | 28 | @Override 29 | public void updateDone(boolean upgradeError) { 30 | } 31 | 32 | @Override 33 | public void updateDownloadProgress(float frac) throws Exception { 34 | } 35 | 36 | @Override 37 | public void startUpdateRollback() { 38 | System.out.println("Rolling back."); 39 | } 40 | 41 | @Override 42 | public void updateRollbackProgress(float progress) { 43 | if(progress == 1) 44 | System.out.println("Rollback complete."); 45 | 46 | } 47 | 48 | @Override 49 | public Void prep(Callable callback) { 50 | return null; 51 | } 52 | 53 | @Override 54 | public Void value() { 55 | return null; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /forker-updater/src/main/java/com/sshtools/forker/updater/Handler.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.updater; 2 | 3 | import java.util.concurrent.Callable; 4 | 5 | public interface Handler { 6 | 7 | boolean isCancelled(); 8 | 9 | void init(S session); 10 | 11 | void complete(); 12 | 13 | void failed(Throwable error); 14 | 15 | V prep(Callable callback); 16 | 17 | V value(); 18 | } 19 | -------------------------------------------------------------------------------- /forker-updater/src/main/java/com/sshtools/forker/updater/InstallHandler.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.updater; 2 | 3 | import java.nio.file.Path; 4 | 5 | public interface InstallHandler extends Handler { 6 | 7 | void startInstall() throws Exception; 8 | 9 | void installProgress(float frac) throws Exception; 10 | 11 | void installFile(Path file, Path d, int index) throws Exception; 12 | 13 | void installFileProgress(Path file, float progress) throws Exception; 14 | 15 | void installFileDone(Path file) throws Exception; 16 | 17 | void startInstallRollback() throws Exception; 18 | 19 | void installRollbackProgress(float progress); 20 | 21 | void installDone(); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /forker-updater/src/main/java/com/sshtools/forker/updater/InstallSession.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.updater; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.Files; 5 | import java.nio.file.Path; 6 | import java.util.ArrayList; 7 | import java.util.Collections; 8 | import java.util.List; 9 | 10 | public class InstallSession extends AbstractSession { 11 | 12 | private List files = new ArrayList<>(); 13 | private Path base; 14 | private long sz = -1; 15 | 16 | public InstallSession() { 17 | super(); 18 | } 19 | 20 | public InstallSession(Path propertiesFile) throws IOException { 21 | super(propertiesFile); 22 | } 23 | 24 | public Path base() { 25 | return base; 26 | } 27 | 28 | @Override 29 | public int updates() { 30 | return files.size(); 31 | } 32 | 33 | public List files() { 34 | return Collections.unmodifiableList(files); 35 | } 36 | 37 | public InstallSession addFile(Path path) { 38 | files.add(path); 39 | if (sz > -1) { 40 | sz = -1; 41 | } 42 | return this; 43 | } 44 | 45 | public InstallSession base(Path base) { 46 | this.base = base; 47 | return this; 48 | } 49 | 50 | public long size() { 51 | if (sz == -1) { 52 | sz = 0; 53 | try { 54 | for (Path p : files()) { 55 | sz += Files.size(p); 56 | } 57 | } catch (IOException ioe) { 58 | throw new IllegalStateException("Could not get update size.", ioe); 59 | } 60 | } 61 | return sz; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /forker-updater/src/main/java/com/sshtools/forker/updater/InstallerResults.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.updater; 2 | 3 | public interface InstallerResults { 4 | 5 | void upgradeAndLaunch() throws Exception; 6 | 7 | void upgradeAndExit(int exit); 8 | 9 | void exit(int exit); 10 | 11 | void upgradeAndWait(); 12 | } 13 | -------------------------------------------------------------------------------- /forker-updater/src/main/java/com/sshtools/forker/updater/Launcher.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.updater; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class Launcher { 7 | 8 | private List entries = new ArrayList<>(); 9 | private String id; 10 | 11 | public Launcher(String id) { 12 | this.id = id; 13 | } 14 | 15 | public String id() { 16 | return id; 17 | } 18 | 19 | public List entries() { 20 | return entries; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /forker-updater/src/main/java/com/sshtools/forker/updater/NotFatalException.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.updater; 2 | 3 | @SuppressWarnings("serial") 4 | public class NotFatalException extends Exception { 5 | 6 | public NotFatalException(String message, Throwable cause) { 7 | super(message, cause); 8 | } 9 | 10 | public NotFatalException(String message) { 11 | super(message); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /forker-updater/src/main/java/com/sshtools/forker/updater/Session.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.updater; 2 | 3 | import java.util.Properties; 4 | 5 | public interface Session { 6 | 7 | int updates(); 8 | 9 | long size(); 10 | 11 | Properties properties(); 12 | } 13 | -------------------------------------------------------------------------------- /forker-updater/src/main/java/com/sshtools/forker/updater/ThrottledInputStream.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.updater; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | 6 | public class ThrottledInputStream extends InputStream { 7 | 8 | private final InputStream inputStream; 9 | private final long maxBytesPerSec; 10 | private final long startTime = System.nanoTime(); 11 | 12 | private long bytesRead = 0; 13 | private long totalSleepTime = 0; 14 | 15 | private static final long SLEEP_DURATION_MS = 30; 16 | 17 | public ThrottledInputStream(InputStream inputStream) { 18 | this(inputStream, Long.MAX_VALUE); 19 | } 20 | 21 | public ThrottledInputStream(InputStream inputStream, long maxBytesPerSec) { 22 | if (maxBytesPerSec < 0) { 23 | throw new IllegalArgumentException("maxBytesPerSec shouldn't be negative"); 24 | } 25 | if (inputStream == null) { 26 | throw new IllegalArgumentException("inputStream shouldn't be null"); 27 | } 28 | 29 | this.inputStream = inputStream; 30 | this.maxBytesPerSec = maxBytesPerSec; 31 | } 32 | 33 | @Override 34 | public void close() throws IOException { 35 | inputStream.close(); 36 | } 37 | 38 | @Override 39 | public int read() throws IOException { 40 | throttle(); 41 | int data = inputStream.read(); 42 | if (data != -1) { 43 | bytesRead++; 44 | } 45 | return data; 46 | } 47 | 48 | @Override 49 | public int read(byte[] b) throws IOException { 50 | throttle(); 51 | int readLen = inputStream.read(b); 52 | if (readLen != -1) { 53 | bytesRead += readLen; 54 | } 55 | return readLen; 56 | } 57 | 58 | @Override 59 | public int read(byte[] b, int off, int len) throws IOException { 60 | throttle(); 61 | int readLen = inputStream.read(b, off, len); 62 | if (readLen != -1) { 63 | bytesRead += readLen; 64 | } 65 | return readLen; 66 | } 67 | 68 | private void throttle() throws IOException { 69 | while (getBytesPerSec() > maxBytesPerSec) { 70 | try { 71 | Thread.sleep(SLEEP_DURATION_MS); 72 | totalSleepTime += SLEEP_DURATION_MS; 73 | } catch (InterruptedException e) { 74 | System.out.println("Thread interrupted" + e.getMessage()); 75 | throw new IOException("Thread interrupted", e); 76 | } 77 | } 78 | } 79 | 80 | public long getTotalBytesRead() { 81 | return bytesRead; 82 | } 83 | 84 | /** 85 | * Return the number of bytes read per second 86 | */ 87 | public long getBytesPerSec() { 88 | long elapsed = (System.nanoTime() - startTime) / 1000000000; 89 | if (elapsed == 0) { 90 | return bytesRead; 91 | } else { 92 | return bytesRead / elapsed; 93 | } 94 | } 95 | 96 | public long getTotalSleepTime() { 97 | return totalSleepTime; 98 | } 99 | 100 | @Override 101 | public String toString() { 102 | return "ThrottledInputStream{" + "bytesRead=" + bytesRead + ", maxBytesPerSec=" + maxBytesPerSec 103 | + ", bytesPerSec=" + getBytesPerSec() + ", totalSleepTimeInSeconds=" + totalSleepTime / 1000 + '}'; 104 | } 105 | } -------------------------------------------------------------------------------- /forker-updater/src/main/java/com/sshtools/forker/updater/ThrottledOutputStream.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.updater; 2 | 3 | import java.io.IOException; 4 | import java.io.OutputStream; 5 | 6 | public class ThrottledOutputStream extends OutputStream { 7 | private OutputStream outputStream; 8 | private final long maxBytesPerSecond; 9 | private final long startTime = System.nanoTime(); 10 | 11 | private long bytesWrite = 0; 12 | private long totalSleepTime = 0; 13 | private static final long SLEEP_DURATION_MS = 30; 14 | 15 | public ThrottledOutputStream(OutputStream outputStream) { 16 | this(outputStream, Long.MAX_VALUE); 17 | } 18 | 19 | public ThrottledOutputStream(OutputStream outputStream, long maxBytesPerSecond) { 20 | if (outputStream == null) { 21 | throw new IllegalArgumentException("outputStream shouldn't be null"); 22 | } 23 | 24 | if (maxBytesPerSecond <= 0) { 25 | throw new IllegalArgumentException("maxBytesPerSecond should be greater than zero"); 26 | } 27 | 28 | this.outputStream = outputStream; 29 | this.maxBytesPerSecond = maxBytesPerSecond; 30 | } 31 | 32 | @Override 33 | public void write(int arg0) throws IOException { 34 | throttle(); 35 | outputStream.write(arg0); 36 | bytesWrite++; 37 | } 38 | 39 | @Override 40 | public void write(byte[] b, int off, int len) throws IOException { 41 | if (len < maxBytesPerSecond) { 42 | throttle(); 43 | bytesWrite = bytesWrite + len; 44 | outputStream.write(b, off, len); 45 | return; 46 | } 47 | 48 | long currentOffSet = off; 49 | long remainingBytesToWrite = len; 50 | 51 | do { 52 | throttle(); 53 | remainingBytesToWrite = remainingBytesToWrite - maxBytesPerSecond; 54 | bytesWrite = bytesWrite + maxBytesPerSecond; 55 | outputStream.write(b, (int) currentOffSet, (int) maxBytesPerSecond); 56 | currentOffSet = currentOffSet + maxBytesPerSecond; 57 | 58 | } while (remainingBytesToWrite > maxBytesPerSecond); 59 | 60 | throttle(); 61 | bytesWrite = bytesWrite + remainingBytesToWrite; 62 | outputStream.write(b, (int) currentOffSet, (int) remainingBytesToWrite); 63 | } 64 | 65 | @Override 66 | public void write(byte[] b) throws IOException { 67 | this.write(b, 0, b.length); 68 | } 69 | 70 | public void throttle() throws IOException { 71 | while (getBytesPerSec() > maxBytesPerSecond) { 72 | try { 73 | Thread.sleep(SLEEP_DURATION_MS); 74 | totalSleepTime += SLEEP_DURATION_MS; 75 | } catch (InterruptedException e) { 76 | System.out.println("Thread interrupted" + e.getMessage()); 77 | throw new IOException("Thread interrupted", e); 78 | } 79 | } 80 | } 81 | 82 | /** 83 | * Return the number of bytes read per second 84 | */ 85 | public long getBytesPerSec() { 86 | long elapsed = (System.nanoTime() - startTime) / 1000000000; 87 | if (elapsed == 0) { 88 | return bytesWrite; 89 | } else { 90 | return bytesWrite / elapsed; 91 | } 92 | } 93 | 94 | @Override 95 | public String toString() { 96 | return "ThrottledOutputStream{" + "bytesWrite=" + bytesWrite + ", maxBytesPerSecond=" + maxBytesPerSecond 97 | + ", bytesPerSec=" + getBytesPerSec() + ", totalSleepTimeInSeconds=" + totalSleepTime / 1000 + '}'; 98 | } 99 | 100 | public void close() throws IOException { 101 | outputStream.close(); 102 | } 103 | } -------------------------------------------------------------------------------- /forker-updater/src/main/java/com/sshtools/forker/updater/UndoableOp.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.updater; 2 | 3 | import java.io.IOException; 4 | 5 | public interface UndoableOp { 6 | void undo() throws IOException; 7 | 8 | default void cleanUp() { 9 | } 10 | } -------------------------------------------------------------------------------- /forker-updater/src/main/java/com/sshtools/forker/updater/UninstallHandler.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.updater; 2 | 3 | import java.nio.file.Path; 4 | 5 | public interface UninstallHandler extends Handler { 6 | 7 | void startUninstall() throws Exception; 8 | 9 | void uninstallProgress(float frac) throws Exception; 10 | 11 | void uninstallFile(Path file, Path d, int index) throws Exception; 12 | 13 | void uninstallFileProgress(Path file, float progress) throws Exception; 14 | 15 | void uninstallFileDone(Path file) throws Exception; 16 | 17 | void uninstallDone(); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /forker-updater/src/main/java/com/sshtools/forker/updater/UninstallSession.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.updater; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.Files; 5 | import java.nio.file.Path; 6 | import java.util.Collections; 7 | import java.util.LinkedHashSet; 8 | import java.util.Set; 9 | 10 | public class UninstallSession extends AbstractSession { 11 | 12 | private Set files = new LinkedHashSet<>(); 13 | private Path base; 14 | private long sz = -1; 15 | 16 | public UninstallSession() throws IOException { 17 | super(); 18 | } 19 | 20 | public UninstallSession(Path propertiesFile) throws IOException { 21 | super(propertiesFile); 22 | } 23 | 24 | public Path base() { 25 | return base; 26 | } 27 | 28 | @Override 29 | public int updates() { 30 | return files.size(); 31 | } 32 | 33 | public Set files() { 34 | return Collections.unmodifiableSet(files); 35 | } 36 | 37 | public UninstallSession addDirectory(Path path) { 38 | if(Files.exists(path)) { 39 | try { 40 | Files.walk(path).forEach(s -> { 41 | if (Files.isDirectory(s)) 42 | addFile(s); 43 | }); 44 | } 45 | catch(IOException ioe) { 46 | throw new IllegalStateException(String.format("Failed to add directory %s to uninstall list.")); 47 | } 48 | } 49 | return this; 50 | } 51 | 52 | public UninstallSession addFile(Path path) { 53 | files.add(path); 54 | if (sz > -1) { 55 | sz = -1; 56 | } 57 | return this; 58 | } 59 | 60 | public UninstallSession base(Path base) { 61 | this.base = base; 62 | return this; 63 | } 64 | 65 | @Override 66 | public long size() { 67 | return updates(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /forker-updater/src/main/java/com/sshtools/forker/updater/UpdateHandler.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.updater; 2 | 3 | import java.net.URL; 4 | import java.util.concurrent.Callable; 5 | 6 | public interface UpdateHandler extends Handler { 7 | 8 | default void startingManifestLoad(URL location) { 9 | } 10 | 11 | default void completedManifestLoad(URL location) { 12 | } 13 | 14 | void doneDownloadFile(Entry file) throws Exception; 15 | 16 | void updateDownloadFileProgress(Entry file, float progress) throws Exception; 17 | 18 | void updateDownloadProgress(float progress) throws Exception; 19 | 20 | void startDownloadFile(Entry file, int index) throws Exception; 21 | 22 | void startDownloads() throws Exception; 23 | 24 | default boolean noUpdates(Callable task) { 25 | return true; 26 | } 27 | 28 | default boolean updatesComplete(Callable task) throws Exception { 29 | return true; 30 | } 31 | 32 | void updateDone(boolean upgradeError); 33 | 34 | void startUpdateRollback(); 35 | 36 | void updateRollbackProgress(float progress); 37 | } 38 | -------------------------------------------------------------------------------- /forker-updater/src/main/java/com/sshtools/forker/updater/test/InstallTest.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.updater.test; 2 | 3 | import java.io.IOException; 4 | import java.io.InterruptedIOException; 5 | import java.nio.file.Path; 6 | import java.nio.file.Paths; 7 | import java.util.concurrent.Callable; 8 | 9 | import com.sshtools.forker.updater.InstallHandler; 10 | import com.sshtools.forker.updater.InstallSession; 11 | 12 | 13 | public class InstallTest { 14 | public static void main(String[] args, InstallHandler installHandler) throws Exception { 15 | InstallSession s = new InstallSession(); 16 | s.properties().putAll(System.getProperties()); 17 | s.base(Paths.get(System.getProperty("user.dir"))); 18 | installHandler.init(s); 19 | Path destination = installHandler.prep(new Callable() { 20 | @Override 21 | public Void call() throws Exception { 22 | installTo(installHandler, s, installHandler.value()); 23 | return null; 24 | } 25 | 26 | }); 27 | if (destination == null) 28 | return; 29 | installTo(installHandler, s, destination); 30 | } 31 | 32 | protected static void installTo(InstallHandler handler, InstallSession session, Path dest) throws Exception { 33 | handler.startInstall(); 34 | float iprg = 0; 35 | try { 36 | Path d = Paths.get(System.getProperty("java.io.tmpdir")); 37 | for (int i = 0; i < 100; i++) { 38 | Path f = Paths.get(String.format("file%d", i)); 39 | checkCancel(handler); 40 | handler.installFile(f, d, i); 41 | for (float prg = 0; prg < 1; prg += Math.random()) { 42 | checkCancel(handler); 43 | handler.installFileProgress(f, prg); 44 | handler.installProgress(iprg = ((float) i / 100) + (0.01f * prg)); 45 | Thread.sleep(50); 46 | } 47 | checkCancel(handler); 48 | handler.installFileDone(f); 49 | if(i > 50 && Boolean.getBoolean("installTest.testRollback")) { 50 | throw new InterruptedIOException("Failed to install a thing!"); 51 | } 52 | } 53 | checkCancel(handler); 54 | handler.installDone(); 55 | handler.complete(); 56 | } catch (InterruptedIOException iioe) { 57 | handler.startInstallRollback(); 58 | for (float prg = iprg; prg >= 0; prg -= 0.01) { 59 | handler.installRollbackProgress(prg); 60 | Thread.sleep(50); 61 | } 62 | handler.failed(iioe); 63 | } catch (Exception e) { 64 | handler.failed(e); 65 | throw e; 66 | } 67 | } 68 | 69 | protected static void checkCancel(InstallHandler handler) throws InterruptedIOException { 70 | if (handler.isCancelled()) 71 | throw new InterruptedIOException("Cancelled."); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /forker-updater/src/main/java/com/sshtools/forker/updater/test/UninstallTest.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.updater.test; 2 | 3 | import java.io.InterruptedIOException; 4 | import java.nio.file.Path; 5 | import java.nio.file.Paths; 6 | import java.util.concurrent.Callable; 7 | 8 | import com.sshtools.forker.updater.UninstallHandler; 9 | import com.sshtools.forker.updater.UninstallSession; 10 | 11 | public class UninstallTest { 12 | public static void main(String[] args, UninstallHandler uninstallHandler) throws Exception { 13 | UninstallSession s = new UninstallSession(); 14 | s.properties().putAll(System.getProperties()); 15 | s.base(Paths.get(System.getProperty("user.dir"))); 16 | uninstallHandler.init(s); 17 | Boolean deleteAll = uninstallHandler.prep(new Callable() { 18 | @Override 19 | public Void call() throws Exception { 20 | uninstallFrom(uninstallHandler, s, uninstallHandler.value()); 21 | return null; 22 | } 23 | 24 | }); 25 | if (deleteAll == null) 26 | /* 27 | * No destination means the install handler will prompt soon in its own way, 28 | * call the callback when it has the destination folder 29 | */ 30 | return; 31 | uninstallFrom(uninstallHandler, s, deleteAll); 32 | 33 | } 34 | 35 | protected static void uninstallFrom(UninstallHandler handler, UninstallSession session, boolean deleteAll) 36 | throws Exception { 37 | handler.startUninstall(); 38 | try { 39 | Path d = Paths.get(System.getProperty("java.io.tmpdir")); 40 | for (int i = 0; i < 100; i++) { 41 | Path f = Paths.get(String.format("file%d", i)); 42 | checkCancel(handler); 43 | handler.uninstallFile(f, d, i); 44 | for (float prg = 0; prg < 1; prg += Math.random()) { 45 | checkCancel(handler); 46 | handler.uninstallFileProgress(f, prg); 47 | handler.uninstallProgress(((float) i / 100) + (0.01f * prg)); 48 | Thread.sleep(50); 49 | } 50 | checkCancel(handler); 51 | handler.uninstallFileDone(f); 52 | } 53 | checkCancel(handler); 54 | handler.uninstallDone(); 55 | handler.complete(); 56 | } catch (Exception e) { 57 | handler.failed(e); 58 | throw e; 59 | } 60 | } 61 | 62 | protected static void checkCancel(UninstallHandler handler) throws InterruptedIOException { 63 | if (handler.isCancelled()) 64 | throw new InterruptedIOException("Cancelled."); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /forker-updater/src/main/java/com/sshtools/forker/updater/test/UpdateTest.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.updater.test; 2 | 3 | import java.io.InterruptedIOException; 4 | import java.nio.file.Files; 5 | import java.nio.file.Path; 6 | import java.util.concurrent.Callable; 7 | 8 | import com.sshtools.forker.updater.AppManifest; 9 | import com.sshtools.forker.updater.Entry; 10 | import com.sshtools.forker.updater.InstallHandler; 11 | import com.sshtools.forker.updater.UpdateHandler; 12 | import com.sshtools.forker.updater.UpdateSession; 13 | 14 | public class UpdateTest { 15 | public static void main(String[] args, UpdateHandler updateHandler) throws Exception { 16 | UpdateSession s = new UpdateSession(); 17 | s.properties().putAll(System.getProperties()); 18 | updateHandler.init(s); 19 | updateHandler.prep(new Callable() { 20 | @Override 21 | public Void call() throws Exception { 22 | update(updateHandler, s); 23 | return null; 24 | } 25 | }); 26 | } 27 | 28 | protected static void update(UpdateHandler handler, UpdateSession session) throws Exception { 29 | AppManifest mf = new AppManifest(); 30 | handler.startDownloads(); 31 | float iprg = 0; 32 | try { 33 | for (int i = 0; i < 100; i++) { 34 | Path pf = Files.createTempFile("abc", "def"); 35 | pf.toFile().deleteOnExit(); 36 | Entry f = new Entry(pf, mf); 37 | checkCancel(handler); 38 | handler.startDownloadFile(f, i); 39 | for (float prg = 0; prg < 1; prg += Math.random()) { 40 | checkCancel(handler); 41 | handler.updateDownloadFileProgress(f, prg); 42 | handler.updateDownloadProgress(iprg = (((float) i / 100) + (0.01f * prg))); 43 | Thread.sleep(50); 44 | } 45 | checkCancel(handler); 46 | handler.doneDownloadFile(f); 47 | Files.delete(pf); 48 | if (i > 50 && Boolean.getBoolean("updateTest.testRollback")) { 49 | throw new InterruptedIOException("Failed to update a thing!"); 50 | } 51 | } 52 | checkCancel(handler); 53 | handler.updateDone(false); 54 | handler.complete(); 55 | } catch (InterruptedIOException iioe) { 56 | handler.startUpdateRollback(); 57 | for (float prg = iprg; prg >= 0; prg -= 0.01) { 58 | handler.updateRollbackProgress(prg); 59 | Thread.sleep(50); 60 | } 61 | handler.failed(iioe); 62 | } catch (Exception e) { 63 | e.printStackTrace(); 64 | handler.failed(e); 65 | throw e; 66 | } 67 | } 68 | 69 | protected static void checkCancel(UpdateHandler handler) throws InterruptedIOException { 70 | if (handler.isCancelled()) 71 | throw new InterruptedIOException("Cancelled."); 72 | } 73 | 74 | protected static void checkCancel(InstallHandler handler) throws InterruptedIOException { 75 | if (handler.isCancelled()) 76 | throw new InterruptedIOException("Cancelled."); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /forker-updater/src/main/java/com/sshtoos/forker/updater/rpc/UpdateServicesMBean.java: -------------------------------------------------------------------------------- 1 | package com.sshtoos.forker.updater.rpc; 2 | 3 | public interface UpdateServicesMBean { 4 | 5 | void open(String[] args); 6 | } 7 | -------------------------------------------------------------------------------- /forker-updater/src/main/java/com/sshtoos/forker/updater/rpc/UpdaterServices.java: -------------------------------------------------------------------------------- 1 | package com.sshtoos.forker.updater.rpc; 2 | 3 | public class UpdaterServices implements UpdateServicesMBean { 4 | 5 | public void open(String[] args) { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /forker-updater/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Updater module 3 | */ 4 | module com.sshtools.forker.updater { 5 | requires transitive java.xml; 6 | requires transitive com.sshtools.forker.wrapper; 7 | exports com.sshtools.forker.updater; 8 | exports com.sshtools.forker.updater.test; 9 | uses com.sshtools.forker.updater.UpdateHandler; 10 | uses com.sshtools.forker.updater.InstallHandler; 11 | uses com.sshtools.forker.updater.UninstallHandler; 12 | opens com.sshtools.forker.updater to info.picocli; 13 | } -------------------------------------------------------------------------------- /forker-wrapped/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /.settings/ 3 | *.classpath 4 | *.project 5 | -------------------------------------------------------------------------------- /forker-wrapped/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | forker-wrapped 4 | Forker Wrapped 5 | This module can be added by wrapped applications to access single-instance reuse and other communication with the wrapper. It deliberately has no other dependencies 6 | 7 | com.sshtools 8 | forker 9 | 1.8 10 | .. 11 | 12 | 13 | 14 | src/main/java 15 | src/test/java 16 | target/classes 17 | target/test-classes 18 | 19 | 20 | . 21 | src/main/resources 22 | 23 | 24 | 25 | 26 | . 27 | src/test/resources 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /forker-wrapped/src/main/java/com/sshtools/forker/wrapped/WrappedMXBean.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.wrapped; 2 | 3 | public interface WrappedMXBean { 4 | 5 | int launch(String[] args); 6 | 7 | int shutdown(); 8 | 9 | void ping(); 10 | } 11 | -------------------------------------------------------------------------------- /forker-wrapped/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Wrapped module 3 | */ 4 | module com.sshtools.forker.wrapped { 5 | exports com.sshtools.forker.wrapped; 6 | requires java.management; 7 | } -------------------------------------------------------------------------------- /forker-wrapper-plugin-scripts/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /.settings/ 3 | *.classpath 4 | /bin/ 5 | *.project 6 | -------------------------------------------------------------------------------- /forker-wrapper-plugin-scripts/README.md: -------------------------------------------------------------------------------- 1 | # Forker Wrapper Plugin - Scripts 2 | 3 | ## Introduction 4 | 5 | A plugin for Forker Wrapper that allows scripts to be run at significant events. This 6 | used to be part of the core but was separated as part of a modularisation effort. 7 | -------------------------------------------------------------------------------- /forker-wrapper-plugin-scripts/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | forker-wrapper-plugin-scripts 6 | Forker Wrapper Plugin - Scripts 7 | Extend forker-wrapper allow scripts to be run at significant efents. 8 | 9 | com.sshtools 10 | forker 11 | 1.8 12 | .. 13 | 14 | 15 | 16 | src/main/java 17 | src/test/java 18 | target/classes 19 | target/test-classes 20 | 21 | 22 | . 23 | src/main/resources 24 | 25 | 26 | 27 | 28 | . 29 | src/test/resources 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | com.sshtools 38 | forker-wrapper 39 | ${project.version} 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | ext-releases 49 | https://artifactory.jadaptive.com/ext-releases-local/ 50 | 51 | 52 | false 53 | 54 | 55 | 56 | ext-snapshots 57 | https://artifactory.jadaptive.com/ext-snapshots-local/ 58 | 59 | false 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | public-releases 68 | https://artifactory.jadaptive.com/public-releases 69 | 70 | 71 | false 72 | 73 | 74 | 75 | public-snapshots 76 | https://artifactory.jadaptive.com/public-snapshots 77 | 78 | false 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | opensource-releases 87 | https://artifactory.jadaptive.com/opensource-releases 88 | 89 | 90 | false 91 | 92 | 93 | 94 | opensource-snapshots 95 | https://artifactory.jadaptive.com/opensource-snapshots 96 | 97 | false 98 | 99 | 100 | true 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /forker-wrapper-plugin-scripts/src/main/java/com/sshtools/forker/wrapper/plugin/scripts/ScriptWrapperPlugin.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.wrapper.plugin.scripts; 2 | 3 | import java.io.File; 4 | import java.io.FileReader; 5 | import java.io.IOException; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | import javax.script.Bindings; 10 | import javax.script.ScriptEngine; 11 | import javax.script.ScriptEngineManager; 12 | import javax.script.ScriptException; 13 | 14 | import com.sshtools.forker.wrapper.ForkerWrapper; 15 | import com.sshtools.forker.wrapper.KeyValuePair; 16 | import com.sshtools.forker.wrapper.WrapperPlugin; 17 | 18 | public class ScriptWrapperPlugin implements WrapperPlugin { 19 | private ScriptEngine engine; 20 | private ForkerWrapper wrapper; 21 | 22 | @Override 23 | public void init(ForkerWrapper wrapper) { 24 | this.wrapper = wrapper; 25 | } 26 | 27 | @Override 28 | public void readConfigFile(File file, List properties) throws IOException { 29 | // 30 | // TODO restart app and/or adjust other configuration on reload 31 | // TODO it shouldnt reload one at a time, it should wait a short while for 32 | // all changes, then reload all configuration files in the same order 33 | // 'properties' should 34 | // be cleared before all are reloaded. 35 | 36 | if (file.getName().endsWith(".js")) { 37 | if (engine == null) { 38 | ScriptEngineManager engineManager = new ScriptEngineManager(); 39 | engine = engineManager.getEngineByName("nashorn"); 40 | Bindings bindings = engine.createBindings(); 41 | bindings.put("wrapper", wrapper); 42 | bindings.put("log", wrapper.getLogger()); 43 | if (engine == null) 44 | throw new IOException("Cannot find JavaScript engine. Are you on at least Java 8?"); 45 | } 46 | FileReader r = new FileReader(file); 47 | try { 48 | @SuppressWarnings("unchecked") 49 | Map o = (Map) engine.eval(r); 50 | for (Map.Entry en : o.entrySet()) { 51 | if (en.getValue() instanceof Map) { 52 | @SuppressWarnings("unchecked") 53 | Map m = (Map) en.getValue(); 54 | for (Map.Entry men : m.entrySet()) { 55 | properties.add(new KeyValuePair(en.getKey(), 56 | men.getValue() == null ? null : String.valueOf(men.getValue()))); 57 | } 58 | } else 59 | properties.add(new KeyValuePair(en.getKey(), 60 | en.getValue() == null ? null : String.valueOf(en.getValue()))); 61 | } 62 | } catch (ScriptException e) { 63 | throw new IOException("Failed to evaluate configuration script.", e); 64 | } 65 | } 66 | 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /forker-wrapper-plugin-scripts/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module com.sshtools.forker.wrapper.plugin.scripts { 2 | requires transitive com.sshtools.forker.wrapper; 3 | requires java.scripting; 4 | provides com.sshtools.forker.wrapper.WrapperPlugin with com.sshtools.forker.wrapper.plugin.scripts.ScriptWrapperPlugin; 5 | } -------------------------------------------------------------------------------- /forker-wrapper-plugin-scripts/src/main/resources/META-INF/services/com.sshtools.forker.wrapper.WrapperPlugin: -------------------------------------------------------------------------------- 1 | com.sshtools.forker.wrapper.plugin.scripts.ScriptWrapperPlugin -------------------------------------------------------------------------------- /forker-wrapper/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /.settings/ 3 | *.classpath 4 | /bin/ 5 | /linux/ 6 | /logs/ 7 | /tmp/ 8 | -------------------------------------------------------------------------------- /forker-wrapper/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | forker-wrapper 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.wst.common.project.facet.core.builder 10 | 11 | 12 | 13 | 14 | org.eclipse.jdt.core.javabuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.wst.validation.validationbuilder 20 | 21 | 22 | 23 | 24 | org.eclipse.m2e.core.maven2Builder 25 | 26 | 27 | 28 | 29 | 30 | org.eclipse.jem.workbench.JavaEMFNature 31 | org.eclipse.wst.common.modulecore.ModuleCoreNature 32 | org.eclipse.jdt.core.javanature 33 | org.eclipse.m2e.core.maven2Nature 34 | org.eclipse.wst.common.project.facet.core.nature 35 | 36 | 37 | -------------------------------------------------------------------------------- /forker-wrapper/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | forker-wrapper 6 | Forker Wrapper 7 | This module uses Forker almost in reverse, providing a way to 'wrap' a Java process, lowering its privileges (but stiill providing a way for root commands to be run), restarting itself and more. 8 | 9 | com.sshtools 10 | forker 11 | 1.8 12 | .. 13 | 14 | 15 | 16 | src/main/java 17 | src/test/java 18 | target/classes 19 | target/test-classes 20 | 21 | 22 | . 23 | src/main/resources 24 | 25 | 26 | 27 | 28 | . 29 | src/test/resources 30 | 31 | 32 | 33 | 34 | org.codehaus.mojo 35 | exec-maven-plugin 36 | 3.0.0 37 | 38 | 39 | com.sshtools.forker.wrapper.ForkerWrapper 40 | true 41 | true 42 | 43 | --help 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | ${project.groupId} 53 | forker-client 54 | ${project.version} 55 | 56 | 57 | info.picocli 58 | picocli 59 | 4.6.1 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /forker-wrapper/src/main/java/com/sshtools/forker/wrapper/AbstractWrapper.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.wrapper; 2 | 3 | import java.awt.SplashScreen; 4 | import java.util.logging.Handler; 5 | import java.util.logging.Level; 6 | import java.util.logging.Logger; 7 | 8 | public class AbstractWrapper { 9 | 10 | 11 | /** The logger. */ 12 | protected Logger logger = Logger.getGlobal(); 13 | 14 | /** 15 | * Sets the log level. 16 | * 17 | * @param lvl the new log level 18 | */ 19 | public void setLogLevel(String lvl) { 20 | setLogLevel(Level.parse(lvl)); 21 | } 22 | 23 | /** 24 | * Gets the logger. 25 | * 26 | * @return the logger 27 | */ 28 | public Logger getLogger() { 29 | return logger; 30 | } 31 | 32 | /** 33 | * Sets the log level. 34 | * 35 | * @param lvl the new log level 36 | */ 37 | public void setLogLevel(Level lvl) { 38 | Logger logger = this.logger; 39 | do { 40 | logger.setLevel(lvl); 41 | for (Handler h : logger.getHandlers()) { 42 | h.setLevel(lvl); 43 | } 44 | logger = logger.getParent(); 45 | } while (logger != null); 46 | } 47 | 48 | public void closeSplash() { 49 | try { 50 | final SplashScreen splash = SplashScreen.getSplashScreen(); 51 | if(splash != null) 52 | splash.close(); 53 | } 54 | catch(Exception e) { 55 | logger.log(Level.FINE, "Ignoring splash error.", e); 56 | } 57 | } 58 | 59 | /** 60 | * Reconfigure logging. 61 | */ 62 | protected void reconfigureLogging(String levelName) { 63 | Level lvl = Level.parse(levelName); 64 | setLogLevel(lvl); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /forker-wrapper/src/main/java/com/sshtools/forker/wrapper/ArgMode.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.wrapper; 2 | 3 | public enum ArgMode { 4 | FORCE, APPEND, PREPEND, DEFAULT 5 | } -------------------------------------------------------------------------------- /forker-wrapper/src/main/java/com/sshtools/forker/wrapper/ArgfileMode.java: -------------------------------------------------------------------------------- 1 | 2 | package com.sshtools.forker.wrapper; 3 | public enum ArgfileMode { 4 | COMPACT, ARGFILE, EXPANDED 5 | } 6 | -------------------------------------------------------------------------------- /forker-wrapper/src/main/java/com/sshtools/forker/wrapper/Argument.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.wrapper; 2 | 3 | /** 4 | * The Class Argument. 5 | */ 6 | public class Argument { 7 | 8 | /** The type. */ 9 | private ArgumentType type; 10 | 11 | /** The value. */ 12 | private String value; 13 | 14 | /** 15 | * Instantiates a new argument. 16 | * 17 | * @param value the value 18 | */ 19 | public Argument(String value) { 20 | this(ArgumentType.OPTION, value); 21 | } 22 | 23 | /** 24 | * Instantiates a new argument. 25 | * 26 | * @param type the type 27 | * @param value the value 28 | */ 29 | public Argument(ArgumentType type, String value) { 30 | this.type = type; 31 | this.value = value; 32 | } 33 | 34 | /** 35 | * Type. 36 | * 37 | * @return the argument type 38 | */ 39 | public ArgumentType type() { 40 | return type; 41 | } 42 | 43 | /** 44 | * To arg file line. 45 | * 46 | * @return the string 47 | */ 48 | public String toArgFileLine() { 49 | switch (type) { 50 | case QUOTED: 51 | return quote(value, true); 52 | case VALUED_OPTION: 53 | int idx = value.indexOf('='); 54 | if (idx == -1) 55 | return quote(value, true); 56 | else 57 | return value.substring(0, idx + 1) + quote(value.substring(idx + 1), true); 58 | case VALUED_EXTENDED_OPTION: 59 | idx = value.indexOf(':'); 60 | if (idx == -1) 61 | return quote(value, true); 62 | else 63 | return value.substring(0, idx + 1) + quote(value.substring(idx + 1), true); 64 | default: 65 | return value; 66 | } 67 | } 68 | 69 | /** 70 | * Quote. 71 | * 72 | * @param value the value 73 | * @param escapeBackslashes the escape backslashes 74 | * @return the string 75 | */ 76 | protected String quote(String value, boolean escapeBackslashes) { 77 | if (hasWhitespace(value)) { 78 | return "\"" + value.replace("\\", "\\\\") + "\""; 79 | } else 80 | return value; 81 | } 82 | 83 | /** 84 | * Checks for whitespace. 85 | * 86 | * @param v the v 87 | * @return true, if successful 88 | */ 89 | protected boolean hasWhitespace(String v) { 90 | return v.contains(" ") || v.contains("\t") || v.contains("\n") || v.contains("\r"); 91 | } 92 | 93 | /** 94 | * To process build argument. 95 | * 96 | * @return the string 97 | */ 98 | public String toProcessBuildArgument() { 99 | /* 100 | * ProcessBuilder type arguments (i.e. as added to the List arguments) 101 | * are added pretty much as is, except for VALUED_OPTION. 102 | * 103 | * TODO Check this is actually needed for VALUED_OPTION and this processing 104 | * wasnt added just for argfile 105 | */ 106 | if (type == ArgumentType.VALUED_OPTION) { 107 | int idx = value.indexOf('='); 108 | if (idx == -1) 109 | return quote(value, false); 110 | else 111 | return value.substring(0, idx + 1) + quote(value.substring(idx + 1), false); 112 | } else 113 | return value; 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /forker-wrapper/src/main/java/com/sshtools/forker/wrapper/ArgumentType.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.wrapper; 2 | 3 | /** 4 | * The Enum ArgumentType. 5 | */ 6 | public enum ArgumentType { 7 | 8 | /** The option. */ 9 | OPTION, 10 | /** The quoted. */ 11 | QUOTED, 12 | /** The valued option. */ 13 | VALUED_OPTION, 14 | /** The valued extended option. */ 15 | VALUED_EXTENDED_OPTION 16 | } 17 | -------------------------------------------------------------------------------- /forker-wrapper/src/main/java/com/sshtools/forker/wrapper/DisplayMode.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.wrapper; 2 | 3 | public enum DisplayMode { 4 | CONSOLE, GUI 5 | } 6 | -------------------------------------------------------------------------------- /forker-wrapper/src/main/java/com/sshtools/forker/wrapper/ForkerWrapperMXBean.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 - 2021 SSHTOOLS Limited (support@sshtools.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sshtools.forker.wrapper; 17 | 18 | public interface ForkerWrapperMXBean { 19 | void ready(); 20 | 21 | void wrapped(String jmxUrl); 22 | 23 | String getClassname(); 24 | 25 | String getModule(); 26 | 27 | String[] getArguments(); 28 | 29 | void restart() throws InterruptedException; 30 | 31 | void restart(boolean wait) throws InterruptedException; 32 | 33 | void stop() throws InterruptedException; 34 | 35 | void stop(boolean wait) throws InterruptedException; 36 | 37 | void setLogLevel(String lvl); 38 | 39 | void ping(); 40 | } 41 | -------------------------------------------------------------------------------- /forker-wrapper/src/main/java/com/sshtools/forker/wrapper/KeyValuePair.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.wrapper; 2 | 3 | public class KeyValuePair { 4 | String key; 5 | String value; 6 | private boolean bool; 7 | 8 | public KeyValuePair(String line) { 9 | key = line; 10 | int idx = line.indexOf('='); 11 | int spcidx = line.indexOf(' '); 12 | if (spcidx != -1 && (spcidx < idx || idx == -1)) { 13 | idx = spcidx; 14 | } 15 | if (idx != -1) { 16 | value = line.substring(idx + 1).trim(); 17 | key = line.substring(0, idx); 18 | } else { 19 | bool = true; 20 | } 21 | } 22 | 23 | public KeyValuePair(String key, String value) { 24 | this.key = key; 25 | this.value = value; 26 | } 27 | 28 | public String getName() { 29 | return key; 30 | } 31 | 32 | public String getValue() { 33 | return value; 34 | } 35 | 36 | public boolean isBool() { 37 | return bool; 38 | } 39 | 40 | public void setValue(String value) { 41 | this.value = value; 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | return "KeyValuePair [key=" + key + ", value=" + value + ", bool=" + bool + "]"; 47 | } 48 | } -------------------------------------------------------------------------------- /forker-wrapper/src/main/java/com/sshtools/forker/wrapper/SinkOutputStream.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.wrapper; 2 | 3 | import java.io.IOException; 4 | import java.io.OutputStream; 5 | 6 | public class SinkOutputStream extends OutputStream { 7 | @Override 8 | public void write(int b) throws IOException { 9 | } 10 | 11 | @Override 12 | public void write(byte[] b) throws IOException { 13 | } 14 | 15 | @Override 16 | public void write(byte[] b, int off, int len) throws IOException { 17 | } 18 | 19 | @Override 20 | public void flush() throws IOException { 21 | } 22 | 23 | @Override 24 | public void close() throws IOException { 25 | } 26 | } -------------------------------------------------------------------------------- /forker-wrapper/src/main/java/com/sshtools/forker/wrapper/WrapperIO.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 - 2021 SSHTOOLS Limited (support@sshtools.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.sshtools.forker.wrapper; 17 | 18 | import com.sshtools.forker.client.ForkerBuilder; 19 | import com.sshtools.forker.common.IO; 20 | import com.sshtools.forker.common.IO.DefaultIO; 21 | 22 | /** 23 | * I/O mode that allows executed or Java classes processes to be wrapped in 24 | * {@link ForkerWrapper}. * 25 | */ 26 | public class WrapperIO extends DefaultIO { 27 | 28 | /** 29 | * I/O mode to use to obtain a {@link Process} that is wrapped in 30 | * {@link ForkerWrapper} using the standard {@link ForkerBuilder}. 31 | */ 32 | public static final IO WRAPPER = DefaultIO.valueOf("WRAPPER"); 33 | 34 | public WrapperIO() { 35 | super("WRAPPER", true, false); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /forker-wrapper/src/main/java/com/sshtools/forker/wrapper/WrapperPlugin.java: -------------------------------------------------------------------------------- 1 | package com.sshtools.forker.wrapper; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.List; 6 | 7 | import com.sshtools.forker.client.ForkerBuilder; 8 | 9 | import picocli.CommandLine.Model.CommandSpec; 10 | 11 | public interface WrapperPlugin { 12 | 13 | default void init(ForkerWrapper wrapper) throws Exception { 14 | } 15 | 16 | default void readConfigFile(File file, List properties) throws IOException { 17 | } 18 | 19 | default boolean event(String name, String cmd, String... args) { 20 | return false; 21 | } 22 | 23 | default boolean buildCommand(ForkerBuilder appBuilder) { 24 | return false; 25 | } 26 | 27 | default int maybeRestart(int retval, int lastRetVal) { 28 | return Integer.MIN_VALUE; 29 | } 30 | 31 | default void addOptions(CommandSpec options) { 32 | } 33 | 34 | default void beforeProcess() throws IOException { 35 | } 36 | 37 | default void start() throws IOException { 38 | } 39 | 40 | default void beforeLaunch() throws IOException { 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /forker-wrapper/src/main/java/com/sshtools/forker/wrapper/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015 - 2021 SSHTOOLS Limited (support@sshtools.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /** 17 | * Provides the classes required to 'wrap' a Java application that it can be 18 | * monitored, restarted, and more. This is all provided by the 19 | * {@link com.sshtools.forker.wrapper.ForkerWrapper} class that may either be 20 | * run stand-alone or embedded into another application. See the documentation 21 | * for this class for more information. 22 | */ 23 | package com.sshtools.forker.wrapper; -------------------------------------------------------------------------------- /forker-wrapper/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Wrapper module 3 | */ 4 | module com.sshtools.forker.wrapper { 5 | requires transitive com.sshtools.forker.common; 6 | requires transitive com.sshtools.forker.client; 7 | requires transitive java.logging; 8 | requires java.management; 9 | requires java.management.rmi; 10 | requires transitive info.picocli; 11 | requires static java.desktop; 12 | 13 | exports com.sshtools.forker.wrapper; 14 | 15 | provides com.sshtools.forker.client.ForkerProcessFactory with com.sshtools.forker.wrapper.WrapperProcessFactory; 16 | provides com.sshtools.forker.common.IO with com.sshtools.forker.wrapper.WrapperIO; 17 | 18 | uses com.sshtools.forker.wrapper.WrapperPlugin; 19 | } -------------------------------------------------------------------------------- /forker-wrapper/src/main/resources/META-INF/services/com.sshtools.forker.client.ForkerProcessFactory: -------------------------------------------------------------------------------- 1 | com.sshtools.forker.wrapper.WrapperProcessFactory -------------------------------------------------------------------------------- /forker-wrapper/src/main/resources/META-INF/services/com.sshtools.forker.common.IO: -------------------------------------------------------------------------------- 1 | com.sshtools.forker.wrapper.WrapperIO -------------------------------------------------------------------------------- /src/site/site.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 23 | 24 | 25 |

26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /templates/APACHE-2.txt: -------------------------------------------------------------------------------- 1 | Copyright © ${project.inceptionYear} - ${year} ${owner} (${email}) 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. --------------------------------------------------------------------------------