001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019package org.apache.hadoop.fs; 020 021import java.io.BufferedReader; 022import java.io.File; 023import java.io.FileNotFoundException; 024import java.io.IOException; 025import java.io.StringReader; 026 027import org.apache.hadoop.io.IOUtils; 028import org.apache.hadoop.util.Shell; 029import org.apache.hadoop.util.Shell.ExitCodeException; 030import org.apache.hadoop.util.Shell.ShellCommandExecutor; 031 032import com.google.common.annotations.VisibleForTesting; 033 034import static java.nio.file.Files.createLink; 035 036/** 037 * Class for creating hardlinks. 038 * Supports Unix/Linux, Windows via winutils , and Mac OS X. 039 * 040 * The HardLink class was formerly a static inner class of FSUtil, 041 * and the methods provided were blatantly non-thread-safe. 042 * To enable volume-parallel Update snapshots, we now provide static 043 * threadsafe methods that allocate new buffer string arrays 044 * upon each call. We also provide an API to hardlink all files in a 045 * directory with a single command, which is up to 128 times more 046 * efficient - and minimizes the impact of the extra buffer creations. 047 */ 048public class HardLink { 049 050 private static HardLinkCommandGetter getHardLinkCommand; 051 052 public final LinkStats linkStats; //not static 053 054 //initialize the command "getters" statically, so can use their 055 //methods without instantiating the HardLink object 056 static { 057 if (Shell.WINDOWS) { 058 // Windows 059 getHardLinkCommand = new HardLinkCGWin(); 060 } else { 061 // Unix or Linux 062 getHardLinkCommand = new HardLinkCGUnix(); 063 //override getLinkCountCommand for the particular Unix variant 064 //Linux is already set as the default - {"stat","-c%h", null} 065 if (Shell.MAC || Shell.FREEBSD) { 066 String[] linkCountCmdTemplate = {"/usr/bin/stat","-f%l", null}; 067 HardLinkCGUnix.setLinkCountCmdTemplate(linkCountCmdTemplate); 068 } else if (Shell.SOLARIS) { 069 String[] linkCountCmdTemplate = {"ls","-l", null}; 070 HardLinkCGUnix.setLinkCountCmdTemplate(linkCountCmdTemplate); 071 } 072 } 073 } 074 075 public HardLink() { 076 linkStats = new LinkStats(); 077 } 078 079 /** 080 * This abstract class bridges the OS-dependent implementations of the 081 * needed functionality for querying link counts. 082 * The particular implementation class is chosen during 083 * static initialization phase of the HardLink class. 084 * The "getter" methods construct shell command strings. 085 */ 086 private static abstract class HardLinkCommandGetter { 087 /** 088 * Get the command string to query the hardlink count of a file 089 */ 090 abstract String[] linkCount(File file) throws IOException; 091 } 092 093 /** 094 * Implementation of HardLinkCommandGetter class for Unix 095 */ 096 private static class HardLinkCGUnix extends HardLinkCommandGetter { 097 private static String[] getLinkCountCommand = {"stat","-c%h", null}; 098 private static synchronized 099 void setLinkCountCmdTemplate(String[] template) { 100 //May update this for specific unix variants, 101 //after static initialization phase 102 getLinkCountCommand = template; 103 } 104 105 /* 106 * @see org.apache.hadoop.fs.HardLink.HardLinkCommandGetter#linkCount(java.io.File) 107 */ 108 @Override 109 String[] linkCount(File file) 110 throws IOException { 111 String[] buf = new String[getLinkCountCommand.length]; 112 System.arraycopy(getLinkCountCommand, 0, buf, 0, 113 getLinkCountCommand.length); 114 buf[getLinkCountCommand.length - 1] = FileUtil.makeShellPath(file, true); 115 return buf; 116 } 117 } 118 119 /** 120 * Implementation of HardLinkCommandGetter class for Windows 121 */ 122 @VisibleForTesting 123 static class HardLinkCGWin extends HardLinkCommandGetter { 124 125 /** 126 * Build the windows link command. This must not 127 * use an exception-raising reference to WINUTILS, as 128 * some tests examine the command. 129 */ 130 @SuppressWarnings("deprecation") 131 static String[] getLinkCountCommand = { 132 Shell.WINUTILS, "hardlink", "stat", null}; 133 134 /* 135 * @see org.apache.hadoop.fs.HardLink.HardLinkCommandGetter#linkCount(java.io.File) 136 */ 137 @Override 138 String[] linkCount(File file) throws IOException { 139 // trigger the check for winutils 140 Shell.getWinUtilsFile(); 141 String[] buf = new String[getLinkCountCommand.length]; 142 System.arraycopy(getLinkCountCommand, 0, buf, 0, 143 getLinkCountCommand.length); 144 buf[getLinkCountCommand.length - 1] = file.getCanonicalPath(); 145 return buf; 146 } 147 } 148 149 /* 150 * **************************************************** 151 * Complexity is above. User-visible functionality is below 152 * **************************************************** 153 */ 154 155 /** 156 * Creates a hardlink 157 * @param file - existing source file 158 * @param linkName - desired target link file 159 */ 160 public static void createHardLink(File file, File linkName) 161 throws IOException { 162 if (file == null) { 163 throw new IOException( 164 "invalid arguments to createHardLink: source file is null"); 165 } 166 if (linkName == null) { 167 throw new IOException( 168 "invalid arguments to createHardLink: link name is null"); 169 } 170 createLink(linkName.toPath(), file.toPath()); 171 } 172 173 /** 174 * Creates hardlinks from multiple existing files within one parent 175 * directory, into one target directory. 176 * @param parentDir - directory containing source files 177 * @param fileBaseNames - list of path-less file names, as returned by 178 * parentDir.list() 179 * @param linkDir - where the hardlinks should be put. It must already exist. 180 */ 181 public static void createHardLinkMult(File parentDir, String[] fileBaseNames, 182 File linkDir) throws IOException { 183 if (parentDir == null) { 184 throw new IOException( 185 "invalid arguments to createHardLinkMult: parent directory is null"); 186 } 187 if (linkDir == null) { 188 throw new IOException( 189 "invalid arguments to createHardLinkMult: link directory is null"); 190 } 191 if (fileBaseNames == null) { 192 throw new IOException( 193 "invalid arguments to createHardLinkMult: " 194 + "filename list can be empty but not null"); 195 } 196 if (!linkDir.exists()) { 197 throw new FileNotFoundException(linkDir + " not found."); 198 } 199 for (String name : fileBaseNames) { 200 createLink(linkDir.toPath().resolve(name), 201 parentDir.toPath().resolve(name)); 202 } 203 } 204 205 /** 206 * Retrieves the number of links to the specified file. 207 */ 208 public static int getLinkCount(File fileName) throws IOException { 209 if (fileName == null) { 210 throw new IOException( 211 "invalid argument to getLinkCount: file name is null"); 212 } 213 if (!fileName.exists()) { 214 throw new FileNotFoundException(fileName + " not found."); 215 } 216 217 // construct and execute shell command 218 String[] cmd = getHardLinkCommand.linkCount(fileName); 219 String inpMsg = null; 220 String errMsg = null; 221 int exitValue = -1; 222 BufferedReader in = null; 223 224 ShellCommandExecutor shexec = new ShellCommandExecutor(cmd); 225 try { 226 shexec.execute(); 227 in = new BufferedReader(new StringReader(shexec.getOutput())); 228 inpMsg = in.readLine(); 229 exitValue = shexec.getExitCode(); 230 if (inpMsg == null || exitValue != 0) { 231 throw createIOException(fileName, inpMsg, errMsg, exitValue, null); 232 } 233 if (Shell.SOLARIS) { 234 String[] result = inpMsg.split("\\s+"); 235 return Integer.parseInt(result[1]); 236 } else { 237 return Integer.parseInt(inpMsg); 238 } 239 } catch (ExitCodeException e) { 240 inpMsg = shexec.getOutput(); 241 errMsg = e.getMessage(); 242 exitValue = e.getExitCode(); 243 throw createIOException(fileName, inpMsg, errMsg, exitValue, e); 244 } catch (NumberFormatException e) { 245 throw createIOException(fileName, inpMsg, errMsg, exitValue, e); 246 } finally { 247 IOUtils.closeStream(in); 248 } 249 } 250 251 /* Create an IOException for failing to get link count. */ 252 private static IOException createIOException(File f, String message, 253 String error, int exitvalue, Exception cause) { 254 255 final String s = "Failed to get link count on file " + f 256 + ": message=" + message 257 + "; error=" + error 258 + "; exit value=" + exitvalue; 259 return (cause == null) ? new IOException(s) : new IOException(s, cause); 260 } 261 262 263 /** 264 * HardLink statistics counters and methods. 265 * Not multi-thread safe, obviously. 266 * Init is called during HardLink instantiation, above. 267 * 268 * These are intended for use by knowledgeable clients, not internally, 269 * because many of the internal methods are static and can't update these 270 * per-instance counters. 271 */ 272 public static class LinkStats { 273 public int countDirs = 0; 274 public int countSingleLinks = 0; 275 public int countMultLinks = 0; 276 public int countFilesMultLinks = 0; 277 public int countEmptyDirs = 0; 278 public int countPhysicalFileCopies = 0; 279 280 public void clear() { 281 countDirs = 0; 282 countSingleLinks = 0; 283 countMultLinks = 0; 284 countFilesMultLinks = 0; 285 countEmptyDirs = 0; 286 countPhysicalFileCopies = 0; 287 } 288 289 public String report() { 290 return "HardLinkStats: " + countDirs + " Directories, including " 291 + countEmptyDirs + " Empty Directories, " 292 + countSingleLinks 293 + " single Link operations, " + countMultLinks 294 + " multi-Link operations, linking " + countFilesMultLinks 295 + " files, total " + (countSingleLinks + countFilesMultLinks) 296 + " linkable files. Also physically copied " 297 + countPhysicalFileCopies + " other files."; 298 } 299 } 300} 301