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}