001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU General
007: * Public License Version 2 only ("GPL") or the Common Development and Distribution
008: * License("CDDL") (collectively, the "License"). You may not use this file except in
009: * compliance with the License. You can obtain a copy of the License at
010: * http://www.netbeans.org/cddl-gplv2.html or nbbuild/licenses/CDDL-GPL-2-CP. See the
011: * License for the specific language governing permissions and limitations under the
012: * License. When distributing the software, include this License Header Notice in
013: * each file and include the License file at nbbuild/licenses/CDDL-GPL-2-CP. Sun
014: * designates this particular file as subject to the "Classpath" exception as
015: * provided by Sun in the GPL Version 2 section of the License file that
016: * accompanied this code. If applicable, add the following below the License Header,
017: * with the fields enclosed by brackets [] replaced by your own identifying
018: * information: "Portions Copyrighted [year] [name of copyright owner]"
019: *
020: * Contributor(s):
021: *
022: * The Original Software is NetBeans. The Initial Developer of the Original Software
023: * is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun Microsystems, Inc. All
024: * Rights Reserved.
025: *
026: * If you wish your version of this file to be governed by only the CDDL or only the
027: * GPL Version 2, indicate your decision by adding "[Contributor] elects to include
028: * this software in this distribution under the [CDDL or GPL Version 2] license." If
029: * you do not indicate a single choice of license, a recipient has the option to
030: * distribute your version of this file under either the CDDL, the GPL Version 2 or
031: * to extend the choice of license to its licensees as provided above. However, if
032: * you add GPL Version 2 code and therefore, elected the GPL Version 2 license, then
033: * the option applies only if the new code is made subject to such option by the
034: * copyright holder.
035: */
036:
037: package org.netbeans.installer.utils.system;
038:
039: import java.io.BufferedInputStream;
040: import java.io.File;
041: import java.io.FileInputStream;
042: import java.io.IOException;
043: import java.io.InputStream;
044: import java.util.ArrayList;
045: import java.util.Arrays;
046: import java.util.LinkedList;
047: import java.util.List;
048: import org.netbeans.installer.utils.StringUtils;
049: import org.netbeans.installer.utils.helper.EnvironmentScope;
050: import org.netbeans.installer.utils.helper.ErrorLevel;
051: import org.netbeans.installer.utils.helper.ExecutionResults;
052: import org.netbeans.installer.utils.FileUtils;
053: import org.netbeans.installer.utils.LogManager;
054: import org.netbeans.installer.utils.ResourceUtils;
055: import org.netbeans.installer.utils.StreamUtils;
056: import org.netbeans.installer.utils.helper.FilesList;
057: import org.netbeans.installer.utils.system.shortcut.FileShortcut;
058: import org.netbeans.installer.utils.system.shortcut.InternetShortcut;
059: import org.netbeans.installer.utils.system.shortcut.Shortcut;
060: import org.netbeans.installer.utils.SystemUtils;
061: import org.netbeans.installer.utils.exceptions.NativeException;
062: import org.netbeans.installer.utils.helper.ApplicationDescriptor;
063: import org.netbeans.installer.utils.system.cleaner.ProcessOnExitCleanerHandler;
064: import org.netbeans.installer.utils.system.launchers.Launcher;
065: import org.netbeans.installer.utils.progress.Progress;
066: import org.netbeans.installer.utils.system.cleaner.OnExitCleanerHandler;
067: import org.netbeans.installer.utils.system.shortcut.LocationType;
068: import org.netbeans.installer.utils.system.unix.shell.BourneShell;
069: import org.netbeans.installer.utils.system.unix.shell.CShell;
070: import org.netbeans.installer.utils.system.unix.shell.KornShell;
071: import org.netbeans.installer.utils.system.unix.shell.Shell;
072: import org.netbeans.installer.utils.system.unix.shell.TCShell;
073:
074: /**
075: *
076: * @author Dmitry Lipin
077: */
078: public abstract class UnixNativeUtils extends NativeUtils {
079: private boolean isUserAdminSet;
080: private boolean isUserAdmin;
081:
082: private static final String[] FORBIDDEN_DELETING_FILES_UNIX = {
083: System.getProperty("user.home"),
084: System.getProperty("java.home"), "/", "/bin", "/boot",
085: "/dev", "/etc", "/home", "/lib", "/mnt", "/opt", "/sbin",
086: "/share", "/usr", "/usr/bin", "/usr/include", "/usr/lib",
087: "/usr/man", "/usr/sbin", "/var", };
088:
089: private static final String CLEANER_RESOURCE = NATIVE_CLEANER_RESOURCE_SUFFIX
090: + "unix/cleaner.sh"; // NOI18N
091:
092: private static final String CLEANER_FILENAME = "nbi-cleaner.sh"; // NOI18N
093:
094: public static final String XDG_DATA_HOME_ENV_VARIABLE = "XDG_DATA_HOME"; // NOI18N
095:
096: public static final String XDG_DATA_DIRS_ENV_VARIABLE = "XDG_DATA_DIRS"; // NOI18N
097:
098: public static final String DEFAULT_XDG_DATA_HOME = ".local/share"; // NOI18N
099:
100: public static final String DEFAULT_XDG_DATA_DIRS = "/usr/share"; // NOI18N
101:
102: public boolean isCurrentUserAdmin() throws NativeException {
103: if (isUserAdminSet) {
104: return isUserAdmin;
105: }
106: boolean result = isCurrentUserAdmin0();
107: isUserAdmin = result;
108: isUserAdminSet = true;
109: return result;
110: }
111:
112: @Override
113: protected OnExitCleanerHandler newDeleteOnExitCleanerHandler() {
114: return new UnixProcessOnExitCleanerHandler(CLEANER_FILENAME);
115: }
116:
117: public void updateApplicationsMenu() {
118: try {
119: SystemUtils.executeCommand(null, new String[] { "pkill",
120: "-u", SystemUtils.getUserName(), "panel" });
121: } catch (IOException ex) {
122: LogManager.log(ErrorLevel.WARNING, ex);
123: }
124: }
125:
126: public File getShortcutLocation(final Shortcut shortcut,
127: final LocationType locationType) throws NativeException {
128: LogManager.logIndent("devising the shortcut location by type: "
129: + locationType); // NOI18N
130:
131: final String XDG_DATA_HOME = SystemUtils
132: .getEnvironmentVariable(XDG_DATA_HOME_ENV_VARIABLE);
133: final String XDG_DATA_DIRS = SystemUtils
134: .getEnvironmentVariable(XDG_DATA_DIRS_ENV_VARIABLE);
135:
136: final File currentUserLocation;
137: if (XDG_DATA_HOME == null) {
138: currentUserLocation = new File(SystemUtils
139: .getUserHomeDirectory(), DEFAULT_XDG_DATA_HOME);
140: } else {
141: currentUserLocation = new File(XDG_DATA_HOME);
142: }
143:
144: final File allUsersLocation;
145: if (XDG_DATA_DIRS == null) {
146: allUsersLocation = new File(DEFAULT_XDG_DATA_DIRS);
147: } else {
148: allUsersLocation = new File(XDG_DATA_DIRS.split(SystemUtils
149: .getPathSeparator())[0]);
150: }
151:
152: LogManager.log("XDG_DATA_HOME = " + currentUserLocation); // NOI18N
153: LogManager.log("XDG_DATA_DIRS = " + allUsersLocation); // NOI18N
154:
155: String fileName = shortcut.getFileName();
156: if (fileName == null) {
157: if (shortcut instanceof FileShortcut) {
158: final File target = ((FileShortcut) shortcut)
159: .getTarget();
160:
161: fileName = target.getName();
162: if (!target.isDirectory()) {
163: fileName += ".desktop";
164: }
165: } else if (shortcut instanceof InternetShortcut) {
166: fileName = ((InternetShortcut) shortcut).getURL()
167: .getFile()
168: + ".desktop";
169: }
170: }
171:
172: LogManager.log(""); // NOI18N
173:
174: final File shortcutFile;
175: switch (locationType) {
176: case CURRENT_USER_DESKTOP:
177: case ALL_USERS_DESKTOP:
178: shortcutFile = new File(SystemUtils.getUserHomeDirectory(),
179: "Desktop/" + fileName);
180: break;
181: case CURRENT_USER_START_MENU:
182: shortcutFile = new File(currentUserLocation,
183: "applications/" + fileName);
184: break;
185: case ALL_USERS_START_MENU:
186: shortcutFile = new File(allUsersLocation, "applications/"
187: + fileName);
188: break;
189: default:
190: shortcutFile = null;
191: }
192:
193: LogManager.logUnindent("shortcut file: " + shortcutFile); // NOI18N
194:
195: return shortcutFile;
196: }
197:
198: private List<String> getDesktopEntry(FileShortcut shortcut) {
199: final List<String> list = new ArrayList<String>();
200:
201: list.add("[Desktop Entry]");
202: list.add("Encoding=UTF-8");
203: list.add("Name=" + shortcut.getName());
204: list
205: .add("Exec=/bin/sh \""
206: + shortcut.getTarget()
207: + "\""
208: + ((shortcut.getArguments() != null && shortcut
209: .getArguments().size() != 0) ? StringUtils.SPACE
210: + shortcut.getArgumentsString()
211: : StringUtils.EMPTY_STRING));
212:
213: if (shortcut.getIcon() != null) {
214: list.add("Icon=" + shortcut.getIconPath());
215: }
216: if (shortcut.getCategories().length != 0) {
217: list.add("Categories="
218: + StringUtils.asString(shortcut.getCategories(),
219: ";"));
220: }
221:
222: list.add("Version=1.0");
223: list.add("StartupNotify=true");
224: list.add("Type=Application");
225: list.add("Terminal=0");
226: list.add(SystemUtils.getLineSeparator());
227: return list;
228: }
229:
230: protected List<String> getDesktopEntry(InternetShortcut shortcut) {
231: final List<String> list = new ArrayList<String>();
232: list.add("[Desktop Entry]");
233: list.add("Encoding=UTF-8");
234: list.add("Name=" + shortcut.getName());
235: list.add("URL=" + shortcut.getURL());
236: if (shortcut.getIcon() != null) {
237: list.add("Icon=" + shortcut.getIconPath());
238: }
239: if (shortcut.getCategories().length != 0) {
240: list.add("Categories="
241: + StringUtils.asString(shortcut.getCategories(),
242: ";"));
243: }
244: list.add("Version=1.0");
245: list.add("StartupNotify=true");
246: list.add("Type=Link");
247: list.add(SystemUtils.getLineSeparator());
248: return list;
249: }
250:
251: public File createShortcut(Shortcut shortcut,
252: LocationType locationType) throws NativeException {
253: final File file = getShortcutLocation(shortcut, locationType);
254: try {
255: if (shortcut instanceof FileShortcut) {
256: File target = ((FileShortcut) shortcut).getTarget();
257: if (target.isDirectory()) {
258: createSymLink(file, target);
259: } else {
260: FileUtils.writeStringList(file,
261: getDesktopEntry((FileShortcut) shortcut));
262: }
263: } else if (shortcut instanceof InternetShortcut) {
264: FileUtils.writeStringList(file,
265: getDesktopEntry((InternetShortcut) shortcut));
266: }
267: } catch (IOException e) {
268: throw new NativeException("Cannot create shortcut", e);
269: }
270:
271: return file;
272: }
273:
274: public void removeShortcut(Shortcut shortcut,
275: LocationType locationType, boolean cleanupParents)
276: throws NativeException {
277: try {
278: File shortcutFile = getShortcutLocation(shortcut,
279: locationType);
280:
281: FileUtils.deleteFile(shortcutFile);
282:
283: if (cleanupParents
284: && (locationType == LocationType.ALL_USERS_START_MENU || locationType == LocationType.CURRENT_USER_START_MENU)) {
285: FileUtils.deleteEmptyParents(shortcutFile);
286: }
287: } catch (IOException e) {
288: throw new NativeException("Cannot remove shortcut", e);
289: }
290: }
291:
292: public List<File> findExecutableFiles(File parent)
293: throws IOException {
294: List<File> files = new ArrayList<File>();
295:
296: if (parent.exists()) {
297: if (parent.isDirectory()) {
298: File[] children = parent.listFiles();
299: for (File child : children) {
300: files.addAll(findExecutableFiles(child));
301: }
302: } else {
303: // name based analysis
304: File child = parent;
305: String name = child.getName();
306: String[] scriptExtensions = { ".sh", ".pl", ".py" }; //shell, perl, python
307: for (String ext : scriptExtensions) {
308: if (name.endsWith(ext)) {
309: files.add(child);
310: return files;
311: }
312: }
313: // contents based analysis
314: String line = FileUtils.readFirstLine(child);
315: if (line != null) {
316: if (line.startsWith("#!")) { // a script of some sort
317: files.add(child);
318: return files;
319: }
320: }
321: // is it an ELF file?
322: BufferedInputStream bis = new BufferedInputStream(
323: new FileInputStream(child));
324: byte[] buf = new byte[4];
325: bis.read(buf);
326: bis.close();
327: if (Arrays.equals(buf, ELF_BYTES)) {
328: files.add(child);
329: return files;
330: }
331: }
332: }
333: return files;
334: }
335:
336: public List<File> findIrrelevantFiles(File parent)
337: throws IOException {
338: List<File> files = new ArrayList<File>();
339:
340: if (parent.exists()) {
341: if (parent.isDirectory()) {
342: File[] children = parent.listFiles();
343: for (File child : children) {
344: files.addAll(findIrrelevantFiles(child));
345: }
346: } else {
347: // contents based analysis - none at this point
348:
349: // name based analysis
350: File child = parent;
351: String name = child.getName();
352: String[] windowsExtensions = { ".bat", ".cmd", ".dll",
353: ".exe", ".com", ".vbs", ".vbe", ".wsf", ".wsh" };
354: for (String ext : windowsExtensions) {
355: if (name.endsWith(ext)) {
356: files.add(child);
357: break;
358: }
359: }
360: }
361: }
362: return files;
363: }
364:
365: public void chmod(File file, String mode) throws IOException {
366: chmod(Arrays.asList(file), mode);
367: }
368:
369: public void chmod(File file, int mode) throws IOException {
370: chmod(file, Integer.toString(mode));
371: }
372:
373: public void chmod(List<File> files, String mode) throws IOException {
374: for (File file : files) {
375: File directory = file.getParentFile();
376: String name = file.getName();
377:
378: SystemUtils.executeCommand(directory, "chmod", mode, name);
379: }
380: }
381:
382: public void setPermissions(File file, int mode, int change)
383: throws IOException {
384: LogManager.log("setting permissions "
385: + Integer.toString(mode, 8) + " on " + file);
386:
387: setPermissions0(file.getAbsolutePath(), mode, change);
388: }
389:
390: public int getPermissions(File file) throws IOException {
391: return getPermissions0(file.getAbsolutePath());
392: }
393:
394: public void removeIrrelevantFiles(File parent) throws IOException {
395: FileUtils.deleteFiles(findIrrelevantFiles(parent));
396: }
397:
398: public void correctFilesPermissions(File parent) throws IOException {
399: chmod(findExecutableFiles(parent), "ugo+x");
400: }
401:
402: public long getFreeSpace(File file) {
403: if ((file == null) || file.getPath().equals("")) {
404: return 0;
405: } else {
406: return getFreeSpace0(file.getPath());
407: }
408: }
409:
410: public boolean isUNCPath(String path) {
411: // for Unix UNC is smth like servername:/folder...
412: return path.matches("^.+:/.+");
413: }
414:
415: // other ... //////////////////////////
416:
417: public String getEnvironmentVariable(String name,
418: EnvironmentScope scope, boolean flag) {
419: return System.getenv(name);
420: }
421:
422: public void setEnvironmentVariable(String name, String value,
423: EnvironmentScope scope, boolean flag)
424: throws NativeException {
425: if (EnvironmentScope.PROCESS == scope) {
426: SystemUtils.getEnvironment().put(name, value);
427: } else {
428: try {
429: getCurrentShell().setVar(name, value, scope);
430: } catch (IOException e) {
431: throw new NativeException(
432: "Cannot set the environment variable value", e);
433: }
434: }
435: }
436:
437: public Shell getCurrentShell() {
438: LogManager.log(ErrorLevel.DEBUG, "Getting current shell..");
439: LogManager.indent();
440: Shell[] avaliableShells = { new BourneShell(), new CShell(),
441: new TCShell(), new KornShell() };
442: String shell = System.getenv("SHELL");
443: Shell result = null;
444: if (shell == null) {
445: shell = System.getenv("shell");
446: }
447: LogManager.log(ErrorLevel.DEBUG, "... shell env variable = "
448: + shell);
449:
450: if (shell != null) {
451: if (shell.lastIndexOf(File.separator) != -1) {
452: shell = shell.substring(shell
453: .lastIndexOf(File.separator) + 1);
454: }
455: LogManager.log(ErrorLevel.DEBUG,
456: "... searching for the shell with name [" + shell
457: + "] " + "among available shells names");
458: for (Shell sh : avaliableShells) {
459: if (sh.isCurrentShell(shell)) {
460: result = sh;
461: LogManager.log(ErrorLevel.DEBUG,
462: "... detected shell: "
463: + sh.getClass().getSimpleName());
464: break;
465: }
466: }
467:
468: }
469: if (result == null) {
470: LogManager.log(ErrorLevel.DEBUG, "... no shell found");
471: }
472: LogManager.unindent();
473: LogManager
474: .log(ErrorLevel.DEBUG, "... finished detecting shell");
475: return result;
476: }
477:
478: public File getDefaultApplicationsLocation() {
479: File opt = new File("/opt");
480:
481: if (opt.exists() && opt.isDirectory()
482: && FileUtils.canWrite(opt)) {
483: return opt;
484: } else {
485: return SystemUtils.getUserHomeDirectory();
486: }
487: }
488:
489: public boolean isPathValid(String path) {
490: return true;
491: }
492:
493: public FilesList addComponentToSystemInstallManager(
494: ApplicationDescriptor descriptor) throws NativeException {
495: final FilesList list = new FilesList();
496:
497: if (descriptor.getModifyCommand() != null) {
498: try {
499: final Launcher launcher = createUninstaller(descriptor,
500: false, new Progress());
501: correctFilesPermissions(launcher.getOutputFile());
502: list.add(launcher.getOutputFile());
503: } catch (IOException e) {
504: throw new NativeException("Can't create uninstaller", e);
505: }
506: }
507:
508: if (descriptor.getUninstallCommand() != null) {
509: try {
510: final Launcher launcher = createUninstaller(descriptor,
511: true, new Progress());
512: correctFilesPermissions(launcher.getOutputFile());
513: list.add(launcher.getOutputFile());
514: } catch (IOException e) {
515: throw new NativeException("Can't create uninstaller", e);
516: }
517: }
518:
519: return list;
520: }
521:
522: public void removeComponentFromSystemInstallManager(
523: ApplicationDescriptor descriptor) {
524: // does nothing - no support for unix package managers yet
525: }
526:
527: public FilesList createSymLink(File source, File target)
528: throws IOException {
529: return createSymLink(source, target, true);
530: }
531:
532: public FilesList createSymLink(File source, File target,
533: boolean useRelativePath) throws IOException {
534: FilesList list = new FilesList();
535:
536: list.add(FileUtils.mkdirs(source.getParentFile()));
537: list.add(source);
538:
539: String relativePath = null;
540: if (useRelativePath) {
541: relativePath = FileUtils.getRelativePath(source, target);
542: }
543:
544: SystemUtils.executeCommand("ln", "-s",
545: relativePath == null ? target.getAbsolutePath()
546: : relativePath, source.getAbsolutePath());
547:
548: return list;
549: }
550:
551: public List<File> getFileSystemRoots() throws IOException {
552: try {
553: setEnvironmentVariable("LANG", "C",
554: EnvironmentScope.PROCESS, false);
555:
556: setEnvironmentVariable("LC_COLLATE", "C",
557: EnvironmentScope.PROCESS, false);
558: setEnvironmentVariable("LC_CTYPE", "C",
559: EnvironmentScope.PROCESS, false);
560: setEnvironmentVariable("LC_MESSAGES", "C",
561: EnvironmentScope.PROCESS, false);
562: setEnvironmentVariable("LC_MONETARY", "C",
563: EnvironmentScope.PROCESS, false);
564: setEnvironmentVariable("LC_NUMERIC", "C",
565: EnvironmentScope.PROCESS, false);
566: setEnvironmentVariable("LC_TIME", "C",
567: EnvironmentScope.PROCESS, false);
568:
569: final String stdout = SystemUtils
570: .executeCommand("df", "-h").getStdOut();
571: final String[] lines = StringUtils.splitByLines(stdout);
572:
573: // a quick and dirty solution - we assume that % is present only once in
574: // each line - in the part where the percentage is reported, hence we
575: // look for the percentage sign and then for the first slash
576: final List<File> roots = new LinkedList<File>();
577: for (int i = 1; i < lines.length; i++) {
578: int index = lines[i].indexOf("%");
579: if (index != -1) {
580: index = lines[i].indexOf("/", index);
581:
582: if (index != -1) {
583: final String path = lines[i].substring(index);
584: final File file = new File(path);
585:
586: if (!roots.contains(file)) {
587: roots.add(file);
588: }
589: }
590: }
591: }
592:
593: return roots;
594: } catch (NativeException e) {
595: final IOException ioException = new IOException(
596: "Cannot define the environment");
597:
598: throw (IOException) ioException.initCause(e);
599: }
600: }
601:
602: // native declarations //////////////////////////////////////////////////////////
603: private native long getFreeSpace0(String s);
604:
605: private native void setPermissions0(String path, int mode,
606: int change);
607:
608: private native int getPermissions0(String path);
609:
610: private native boolean isCurrentUserAdmin0();
611:
612: /////////////////////////////////////////////////////////////////////////////////
613: // Inner Classes
614: private class UnixProcessOnExitCleanerHandler extends
615: ProcessOnExitCleanerHandler {
616: public UnixProcessOnExitCleanerHandler(String cleanerFileName) {
617: super (cleanerFileName);
618: }
619:
620: protected void writeCleaner(File cleanerFile)
621: throws IOException {
622: InputStream is = ResourceUtils
623: .getResource(CLEANER_RESOURCE);
624: CharSequence cs = StreamUtils.readStream(is);
625: is.close();
626: String[] lines = StringUtils.splitByLines(cs);
627: FileUtils.writeFile(cleanerFile, StringUtils.asString(
628: lines, SystemUtils.getLineSeparator()));
629: }
630:
631: protected void writeCleaningFileList(File listFile,
632: List<String> files) throws IOException {
633: // be sure that the list file contains end-of-line
634: // otherwise the installer will run into Issue 104079
635: List<String> newList = new LinkedList<String>(files);
636: newList.add(SystemUtils.getLineSeparator());
637: FileUtils.writeStringList(listFile, newList);
638: }
639: }
640:
641: public static class FileAccessMode {
642: /** Read by user */
643: public static final int RU = 0400;
644: /** Write by user */
645: public static final int WU = 0200;
646: /** Execute by user */
647: public static final int EU = 0100;
648:
649: /** Read by group */
650: public static final int RG = 040;
651: /** Write by group */
652: public static final int WG = 020;
653: /** Execute by group */
654: public static final int EG = 010;
655:
656: /** Read by others */
657: public static final int RO = 04;
658: /** Write by others */
659: public static final int WO = 02;
660: /** Execute by others */
661: public static final int EO = 01;
662: }
663:
664: @Override
665: protected void initializeForbiddenFiles(String... files) {
666: super .initializeForbiddenFiles(FORBIDDEN_DELETING_FILES_UNIX);
667: super .initializeForbiddenFiles(files);
668: }
669:
670: private static final byte[] ELF_BYTES = new byte[] { '\177', 'E',
671: 'L', 'F' };
672: }
|