001package com.pi4j.io.file; 002 003/*- 004 * #%L 005 * ********************************************************************** 006 * ORGANIZATION : Pi4J 007 * PROJECT : Pi4J :: Java Library (Core) 008 * FILENAME : LinuxFile.java 009 * 010 * This file is part of the Pi4J project. More information about 011 * this project can be found here: https://www.pi4j.com/ 012 * ********************************************************************** 013 * %% 014 * Copyright (C) 2012 - 2021 Pi4J 015 * %% 016 * This program is free software: you can redistribute it and/or modify 017 * it under the terms of the GNU Lesser General Public License as 018 * published by the Free Software Foundation, either version 3 of the 019 * License, or (at your option) any later version. 020 * 021 * This program is distributed in the hope that it will be useful, 022 * but WITHOUT ANY WARRANTY; without even the implied warranty of 023 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 024 * GNU General Lesser Public License for more details. 025 * 026 * You should have received a copy of the GNU General Lesser Public 027 * License along with this program. If not, see 028 * <http://www.gnu.org/licenses/lgpl-3.0.html>. 029 * #L% 030 */ 031 032import com.pi4j.util.NativeLibraryLoader; 033 034import sun.misc.Cleaner; 035import sun.misc.SharedSecrets; 036 037import java.io.FileDescriptor; 038import java.io.FileNotFoundException; 039import java.io.IOException; 040import java.io.RandomAccessFile; 041 042import java.lang.reflect.Constructor; 043import java.lang.reflect.Field; 044import java.lang.reflect.InvocationTargetException; 045 046import java.nio.*; 047 048/** 049 * Extends RandomAccessFile to provide access to Linux ioctl. 050 */ 051@SuppressWarnings("restriction") 052public class LinuxFile extends RandomAccessFile { 053 public LinuxFile(String name, String mode) throws FileNotFoundException { 054 super(name, mode); 055 } 056 057 public static final int wordSize = getWordSize(); 058 public static final int localBufferSize = 2048; //about 1 page 059 060 public static final ThreadLocal<ByteBuffer> localDataBuffer = new ThreadLocal<>(); 061 public static final ThreadLocal<IntBuffer> localOffsetsBuffer = new ThreadLocal<>(); 062 063 private static final Constructor<?> directByteBufferConstructor; 064 065 private static final Field addressField; 066 private static final Field capacityField; 067 private static final Field cleanerField; 068 069 static { 070 try { 071 // Load the platform library 072 NativeLibraryLoader.load("libpi4j.so", "pi4j"); 073 074 Class<?> dbb = Class.forName("java.nio.DirectByteBuffer"); 075 076 addressField = Buffer.class.getDeclaredField("address"); 077 capacityField = Buffer.class.getDeclaredField("capacity"); 078 cleanerField = dbb.getDeclaredField("cleaner"); 079 directByteBufferConstructor = dbb.getDeclaredConstructor( 080 new Class[] { int.class, long.class, FileDescriptor.class, Runnable.class }); 081 082 addressField.setAccessible(true); 083 capacityField.setAccessible(true); 084 cleanerField.setAccessible(true); 085 directByteBufferConstructor.setAccessible(true); 086 } catch (NoSuchFieldException e) { 087 throw new InternalError(e.getMessage()); 088 } catch (ClassNotFoundException e) { 089 throw new InternalError(e.getMessage()); 090 } catch (NoSuchMethodException e) { 091 throw new InternalError(e.getMessage()); 092 } 093 } 094 095 /** 096 * Runs an ioctl value command on a file descriptor. 097 * 098 * @param command ioctl command 099 * @param value int ioctl value 100 * @return result of operation. Zero if everything is OK, less than zero if there was an error. 101 */ 102 public void ioctl(long command, int value) throws IOException { 103 final int response = directIOCTL(getFileDescriptor(), command, value); 104 105 if(response < 0) 106 throw new LinuxFileException(); 107 } 108 109 /** 110 * Runs an ioctl on a file descriptor. Uses special offset buffer to produce real C-like structures 111 * with pointers. Advanced use only! Must be able to produce byte-perfect data structures just as 112 * gcc would on this system, including struct padding and pointer size. 113 * 114 * The data ByteBuffer uses the current position to determine the head point of data 115 * passed to the ioctl. This is useful for appending entry-point data structures 116 * at the end of the buffer, while referring to other structures/data that come before 117 * them in the buffer. 118 * 119 * <I NEED A BETTER EXPL OF BUFFERS HERE> 120 * 121 * When assembling the structured data, use {@link LinuxFile#wordSize} to determine the size 122 * in bytes needed for a pointer. Also be sure to consider GCC padding and structure alignment. 123 * GCC will try a field to its word size (32b ints align at 4-byte, etc), and will align the 124 * structure size with the native word size (4-byte for 32b, 8-byte for 64b). 125 * 126 * Provided IntBuffer offsets must use native byte order (endianness). 127 * 128 * <pre> 129 * {@code 130 * <NEED BETTER EXAMPLE HERE> 131 * } 132 * </pre> 133 * 134 * DANGER: check your buffer length! The possible length varies depending on the ioctl call. 135 * Overruns are very possible. ioctl tries to determine EFAULTs, but sometimes 136 * you might trample JVM data if you are not careful. 137 * 138 * @param command ioctl command 139 * @param data values in bytes for all structures, with 4 or 8 byte size holes for pointers 140 * @param offsets byte offsets of pointer at given index 141 * @throws IOException 142 */ 143 public void ioctl(final long command, ByteBuffer data, IntBuffer offsets) throws IOException { 144 ByteBuffer originalData = data; 145 146 if(data == null || offsets == null) 147 throw new NullPointerException("data and offsets required!"); 148 149 if(offsets.order() != ByteOrder.nativeOrder()) 150 throw new IllegalArgumentException("provided IntBuffer offsets ByteOrder must be native!"); 151 152 //buffers must be direct 153 try { 154 if(!data.isDirect()) { 155 ByteBuffer newBuf = getDataBuffer(data.limit()); 156 int pos = data.position(); //keep position 157 158 data.rewind(); 159 newBuf.clear(); 160 newBuf.put(data); 161 newBuf.position(pos); //restore position 162 163 data = newBuf; 164 } 165 166 if(!offsets.isDirect()) { 167 IntBuffer newBuf = getOffsetsBuffer(offsets.remaining()); 168 169 newBuf.clear(); 170 newBuf.put(offsets); 171 newBuf.flip(); 172 173 offsets = newBuf; 174 } 175 } catch (BufferOverflowException e) { 176 throw new ScratchBufferOverrun(); 177 } 178 179 if((offsets.remaining() & 1) != 0) 180 throw new IllegalArgumentException("offset buffer must be even length!"); 181 182 for(int i = offsets.position() ; i < offsets.limit() ; i += 2) { 183 final int ptrOffset = offsets.get(i); 184 final int dataOffset = offsets.get(i + 1); 185 186 if(dataOffset >= data.capacity() || dataOffset < 0) 187 throw new IndexOutOfBoundsException("invalid data offset specified in buffer: " + dataOffset); 188 189 if((ptrOffset + wordSize) > data.capacity() || ptrOffset < 0) 190 throw new IndexOutOfBoundsException("invalid pointer offset specified in buffer: " + ptrOffset); 191 } 192 193 final int response = directIOCTLStructure(getFileDescriptor(), command, data, 194 data.position(), offsets, offsets.position(), offsets.remaining()); 195 196 if(response < 0) 197 throw new LinuxFileException(); 198 199 //fast forward positions 200 offsets.position(offsets.limit()); 201 data.rewind(); 202 203 //if original data wasnt direct, copy it back in. 204 if(originalData != data) { 205 originalData.rewind(); 206 originalData.put(data); 207 originalData.rewind(); 208 } 209 } 210 211 /** 212 * Gets the real POSIX file descriptor for use by custom jni calls. 213 */ 214 private int getFileDescriptor() throws IOException { 215 final int fd = SharedSecrets.getJavaIOFileDescriptorAccess().get(getFD()); 216 217 if(fd < 1) 218 throw new IOException("failed to get POSIX file descriptor!"); 219 220 return fd; 221 } 222 223 private static int getWordSize() { 224 //TODO: there has to be a better way... 225 final String archDataModel = System.getProperty("sun.arch.data.model"); 226 return "64".equals(archDataModel) ? 8 : 4; 227 } 228 229 @Override 230 protected void finalize() throws Throwable { 231 close(); 232 233 super.finalize(); 234 } 235 236 private synchronized IntBuffer getOffsetsBuffer(int size) { 237 final int byteSize = size * 4; 238 IntBuffer buf = localOffsetsBuffer.get(); 239 240 if(byteSize > localBufferSize) 241 throw new ScratchBufferOverrun(); 242 243 if(buf == null) { 244 ByteBuffer bb = ByteBuffer.allocateDirect(localBufferSize); 245 246 //keep native order, set before cast to IntBuffer 247 bb.order(ByteOrder.nativeOrder()); 248 249 buf = bb.asIntBuffer(); 250 localOffsetsBuffer.set(buf); 251 } 252 253 return buf; 254 } 255 256 private synchronized ByteBuffer getDataBuffer(int size) { 257 ByteBuffer buf = localDataBuffer.get(); 258 259 if(size > localBufferSize) 260 throw new ScratchBufferOverrun(); 261 262 if(buf == null) { 263 buf = ByteBuffer.allocateDirect(localBufferSize); 264 localDataBuffer.set(buf); 265 } 266 267 return buf; 268 } 269 270 /** 271 * Direct memory mapping from a file descriptor. 272 * This is normally possible through the local FileChannel, 273 * but NIO will try to truncate files if they don't report 274 * a correct size. This will avoid that. 275 * 276 * 277 * @param length length of desired mapping 278 * @param prot protocol used for mapping 279 * @param flags flags for mapping 280 * @param offset offset in file for mapping 281 * @return direct mapped ByteBuffer 282 * @throws IOException 283 */ 284 public ByteBuffer mmap(int length, MMAPProt prot, MMAPFlags flags, int offset) throws IOException { 285 long pointer = mmap(getFileDescriptor(), length, prot.flag, flags.flag, offset); 286 287 if(pointer == -1) 288 throw new LinuxFileException(); 289 290 return newMappedByteBuffer(length, pointer, () -> { 291 munmapDirect(pointer, length); 292 }); 293 } 294 295 public static void munmap(ByteBuffer mappedBuffer) throws IOException { 296 if(!mappedBuffer.isDirect()) 297 throw new IllegalArgumentException("Must be a mapped direct buffer"); 298 299 try { 300 long address = addressField.getLong(mappedBuffer); 301 int capacity = capacityField.getInt(mappedBuffer); 302 303 if(address == 0 || capacity == 0) return; 304 305 //reset address field first 306 addressField.setLong(mappedBuffer, 0); 307 capacityField.setInt(mappedBuffer, 0); 308 309 //reset mark and position to new 0 capacity 310 mappedBuffer.clear(); 311 312 //clean object so it doesnt clean on collection 313 ((Cleaner)cleanerField.get(mappedBuffer)).clean(); 314 } catch (IllegalAccessException e) { 315 e.printStackTrace(); 316 throw new InternalError(e.getMessage()); 317 } 318 } 319 320 private MappedByteBuffer newMappedByteBuffer(int size, long addr, Runnable unmapper) throws IOException 321 { 322 MappedByteBuffer dbb; 323 try { 324 dbb = (MappedByteBuffer)directByteBufferConstructor.newInstance( 325 new Object[] { new Integer(size), new Long(addr), this.getFD(), unmapper }); 326 } catch (InstantiationException e) { 327 throw new InternalError(e.getMessage()); 328 } catch (IllegalAccessException e) { 329 throw new InternalError(e.getMessage()); 330 } catch (InvocationTargetException e) { 331 throw new InternalError(e.getMessage()); 332 } 333 return dbb; 334 } 335 336 public static class ScratchBufferOverrun extends IllegalArgumentException { 337 private static final long serialVersionUID = -418203522640826177L; 338 339 public ScratchBufferOverrun() { 340 super("Scratch buffer overrun! Provide direct ByteBuffer for data larger than " + localBufferSize + " bytes"); 341 } 342 } 343 344 public static class LinuxFileException extends IOException { 345 private static final long serialVersionUID = -2581606746434701394L; 346 int code; 347 348 public LinuxFileException() { 349 this(errno()); 350 } 351 352 LinuxFileException(int code) { 353 super(strerror(code)); 354 355 this.code = code; 356 } 357 358 /** 359 * Gets the POSIX code associated with this IO error 360 * 361 * @return POSIX error code 362 */ 363 public int getCode() { 364 return code; 365 } 366 } 367 368 public enum MMAPProt { 369 NONE(0), 370 READ(1), 371 WRITE(2), 372 EXEC(4), 373 RW(READ.flag | WRITE.flag), 374 RX(READ.flag | EXEC.flag), 375 RWX(READ.flag | WRITE.flag | EXEC.flag), 376 WX(WRITE.flag | EXEC.flag); 377 378 public final int flag; 379 380 MMAPProt(int flag) { 381 this.flag = flag; 382 } 383 } 384 385 public enum MMAPFlags { 386 SHARED(1), 387 PRIVATE(2), 388 SHARED_PRIVATE(SHARED.flag | PRIVATE.flag); 389 390 public final int flag; 391 392 MMAPFlags(int flag) { 393 this.flag = flag; 394 } 395 } 396 397 public static native int errno(); 398 399 public static native String strerror(int code); 400 401 protected static native int directIOCTL(int fd, long command, int value); 402 403 protected static native long mmap(int fd, int length, int prot, int flags, int offset); 404 405 protected static native int munmapDirect(long address, long capacity); 406 407 protected static native int directIOCTLStructure(int fd, long command, ByteBuffer data, int dataOffset, IntBuffer offsetMap, int offsetMapOffset, int offsetCapacity); 408}