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 static String[] getLinkCountCommand = { 126 Shell.WINUTILS, "hardlink", "stat", null}; 127 128 /* 129 * @see org.apache.hadoop.fs.HardLink.HardLinkCommandGetter#linkCount(java.io.File) 130 */ 131 @Override 132 String[] linkCount(File file) throws IOException { 133 String[] buf = new String[getLinkCountCommand.length]; 134 System.arraycopy(getLinkCountCommand, 0, buf, 0, 135 getLinkCountCommand.length); 136 buf[getLinkCountCommand.length - 1] = file.getCanonicalPath(); 137 return buf; 138 } 139 } 140 141 /* 142 * **************************************************** 143 * Complexity is above. User-visible functionality is below 144 * **************************************************** 145 */ 146 147 /** 148 * Creates a hardlink 149 * @param file - existing source file 150 * @param linkName - desired target link file 151 */ 152 public static void createHardLink(File file, File linkName) 153 throws IOException { 154 if (file == null) { 155 throw new IOException( 156 "invalid arguments to createHardLink: source file is null"); 157 } 158 if (linkName == null) { 159 throw new IOException( 160 "invalid arguments to createHardLink: link name is null"); 161 } 162 createLink(linkName.toPath(), file.toPath()); 163 } 164 165 /** 166 * Creates hardlinks from multiple existing files within one parent 167 * directory, into one target directory. 168 * @param parentDir - directory containing source files 169 * @param fileBaseNames - list of path-less file names, as returned by 170 * parentDir.list() 171 * @param linkDir - where the hardlinks should be put. It must already exist. 172 */ 173 public static void createHardLinkMult(File parentDir, String[] fileBaseNames, 174 File linkDir) throws IOException { 175 if (parentDir == null) { 176 throw new IOException( 177 "invalid arguments to createHardLinkMult: parent directory is null"); 178 } 179 if (linkDir == null) { 180 throw new IOException( 181 "invalid arguments to createHardLinkMult: link directory is null"); 182 } 183 if (fileBaseNames == null) { 184 throw new IOException( 185 "invalid arguments to createHardLinkMult: " 186 + "filename list can be empty but not null"); 187 } 188 if (!linkDir.exists()) { 189 throw new FileNotFoundException(linkDir + " not found."); 190 } 191 for (String name : fileBaseNames) { 192 createLink(linkDir.toPath().resolve(name), 193 parentDir.toPath().resolve(name)); 194 } 195 } 196 197 /** 198 * Retrieves the number of links to the specified file. 199 */ 200 public static int getLinkCount(File fileName) throws IOException { 201 if (fileName == null) { 202 throw new IOException( 203 "invalid argument to getLinkCount: file name is null"); 204 } 205 if (!fileName.exists()) { 206 throw new FileNotFoundException(fileName + " not found."); 207 } 208 209 // construct and execute shell command 210 String[] cmd = getHardLinkCommand.linkCount(fileName); 211 String inpMsg = null; 212 String errMsg = null; 213 int exitValue = -1; 214 BufferedReader in = null; 215 216 ShellCommandExecutor shexec = new ShellCommandExecutor(cmd); 217 try { 218 shexec.execute(); 219 in = new BufferedReader(new StringReader(shexec.getOutput())); 220 inpMsg = in.readLine(); 221 exitValue = shexec.getExitCode(); 222 if (inpMsg == null || exitValue != 0) { 223 throw createIOException(fileName, inpMsg, errMsg, exitValue, null); 224 } 225 if (Shell.SOLARIS) { 226 String[] result = inpMsg.split("\\s+"); 227 return Integer.parseInt(result[1]); 228 } else { 229 return Integer.parseInt(inpMsg); 230 } 231 } catch (ExitCodeException e) { 232 inpMsg = shexec.getOutput(); 233 errMsg = e.getMessage(); 234 exitValue = e.getExitCode(); 235 throw createIOException(fileName, inpMsg, errMsg, exitValue, e); 236 } catch (NumberFormatException e) { 237 throw createIOException(fileName, inpMsg, errMsg, exitValue, e); 238 } finally { 239 IOUtils.closeStream(in); 240 } 241 } 242 243 /* Create an IOException for failing to get link count. */ 244 private static IOException createIOException(File f, String message, 245 String error, int exitvalue, Exception cause) { 246 247 final String s = "Failed to get link count on file " + f 248 + ": message=" + message 249 + "; error=" + error 250 + "; exit value=" + exitvalue; 251 return (cause == null) ? new IOException(s) : new IOException(s, cause); 252 } 253 254 255 /** 256 * HardLink statistics counters and methods. 257 * Not multi-thread safe, obviously. 258 * Init is called during HardLink instantiation, above. 259 * 260 * These are intended for use by knowledgeable clients, not internally, 261 * because many of the internal methods are static and can't update these 262 * per-instance counters. 263 */ 264 public static class LinkStats { 265 public int countDirs = 0; 266 public int countSingleLinks = 0; 267 public int countMultLinks = 0; 268 public int countFilesMultLinks = 0; 269 public int countEmptyDirs = 0; 270 public int countPhysicalFileCopies = 0; 271 272 public void clear() { 273 countDirs = 0; 274 countSingleLinks = 0; 275 countMultLinks = 0; 276 countFilesMultLinks = 0; 277 countEmptyDirs = 0; 278 countPhysicalFileCopies = 0; 279 } 280 281 public String report() { 282 return "HardLinkStats: " + countDirs + " Directories, including " 283 + countEmptyDirs + " Empty Directories, " 284 + countSingleLinks 285 + " single Link operations, " + countMultLinks 286 + " multi-Link operations, linking " + countFilesMultLinks 287 + " files, total " + (countSingleLinks + countFilesMultLinks) 288 + " linkable files. Also physically copied " 289 + countPhysicalFileCopies + " other files."; 290 } 291 } 292} 293