1 /*
   2  * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.nio.fs;
  27 
  28 import java.nio.file.*;
  29 import java.nio.ByteBuffer;
  30 import java.io.IOException;
  31 import java.util.*;
  32 import jdk.internal.misc.Unsafe;
  33 
  34 import static sun.nio.fs.UnixConstants.*;
  35 import static sun.nio.fs.LinuxNativeDispatcher.*;
  36 
  37 /**
  38  * Linux implementation of UserDefinedFileAttributeView using extended attributes.
  39  */
  40 
  41 class LinuxUserDefinedFileAttributeView
  42     extends AbstractUserDefinedFileAttributeView
  43 {
  44     private static final Unsafe unsafe = Unsafe.getUnsafe();
  45 
  46     // namespace for extended user attributes
  47     private static final String USER_NAMESPACE = "user.";
  48 
  49     // maximum bytes in extended attribute name (includes namespace)
  50     private static final int XATTR_NAME_MAX = 255;
  51 
  52     private byte[] nameAsBytes(UnixPath file, String name) throws IOException {
  53         if (name == null)
  54             throw new NullPointerException("'name' is null");
  55         name = USER_NAMESPACE + name;
  56         byte[] bytes = Util.toBytes(name);
  57         if (bytes.length > XATTR_NAME_MAX) {
  58             throw new FileSystemException(file.getPathForExceptionMessage(),
  59                 null, "'" + name + "' is too big");
  60         }
  61         return bytes;
  62     }
  63 
  64     // Parses buffer as array of NULL-terminated C strings.
  65     private List<String> asList(long address, int size) {
  66         List<String> list = new ArrayList<>();
  67         int start = 0;
  68         int pos = 0;
  69         while (pos < size) {
  70             if (unsafe.getByte(address + pos) == 0) {
  71                 int len = pos - start;
  72                 byte[] value = new byte[len];
  73                 unsafe.copyMemory(null, address+start, value,
  74                     Unsafe.ARRAY_BYTE_BASE_OFFSET, len);
  75                 String s = Util.toString(value);
  76                 if (s.startsWith(USER_NAMESPACE)) {
  77                     s = s.substring(USER_NAMESPACE.length());
  78                     list.add(s);
  79                 }
  80                 start = pos + 1;
  81             }
  82             pos++;
  83         }
  84         return list;
  85     }
  86 
  87     private final UnixPath file;
  88     private final boolean followLinks;
  89 
  90     LinuxUserDefinedFileAttributeView(UnixPath file, boolean followLinks) {
  91         this.file = file;
  92         this.followLinks = followLinks;
  93     }
  94 
  95     @Override
  96     public List<String> list() throws IOException  {
  97         if (System.getSecurityManager() != null)
  98             checkAccess(file.getPathForPermissionCheck(), true, false);
  99 
 100         int fd = file.openForAttributeAccess(followLinks);
 101         NativeBuffer buffer = null;
 102         try {
 103             int size = 1024;
 104             buffer = NativeBuffers.getNativeBuffer(size);
 105             for (;;) {
 106                 try {
 107                     int n = flistxattr(fd, buffer.address(), size);
 108                     List<String> list = asList(buffer.address(), n);
 109                     return Collections.unmodifiableList(list);
 110                 } catch (UnixException x) {
 111                     // allocate larger buffer if required
 112                     if (x.errno() == ERANGE && size < 32*1024) {
 113                         buffer.release();
 114                         size *= 2;
 115                         buffer = null;
 116                         buffer = NativeBuffers.getNativeBuffer(size);
 117                         continue;
 118                     }
 119                     throw new FileSystemException(file.getPathForExceptionMessage(),
 120                         null, "Unable to get list of extended attributes: " +
 121                         x.getMessage());
 122                 }
 123             }
 124         } finally {
 125             if (buffer != null)
 126                 buffer.release();
 127             close(fd);
 128         }
 129     }
 130 
 131     @Override
 132     public int size(String name) throws IOException  {
 133         if (System.getSecurityManager() != null)
 134             checkAccess(file.getPathForPermissionCheck(), true, false);
 135 
 136         int fd = file.openForAttributeAccess(followLinks);
 137         try {
 138             // fgetxattr returns size if called with size==0
 139             return fgetxattr(fd, nameAsBytes(file,name), 0L, 0);
 140         } catch (UnixException x) {
 141             throw new FileSystemException(file.getPathForExceptionMessage(),
 142                 null, "Unable to get size of extended attribute '" + name +
 143                 "': " + x.getMessage());
 144         } finally {
 145             close(fd);
 146         }
 147     }
 148 
 149     @Override
 150     public int read(String name, ByteBuffer dst) throws IOException {
 151         if (System.getSecurityManager() != null)
 152             checkAccess(file.getPathForPermissionCheck(), true, false);
 153 
 154         if (dst.isReadOnly())
 155             throw new IllegalArgumentException("Read-only buffer");
 156         int pos = dst.position();
 157         int lim = dst.limit();
 158         assert (pos <= lim);
 159         int rem = (pos <= lim ? lim - pos : 0);
 160 
 161         NativeBuffer nb;
 162         long address;
 163         if (dst instanceof sun.nio.ch.DirectBuffer) {
 164             nb = null;
 165             address = ((sun.nio.ch.DirectBuffer)dst).address() + pos;
 166         } else {
 167             // substitute with native buffer
 168             nb = NativeBuffers.getNativeBuffer(rem);
 169             address = nb.address();
 170         }
 171 
 172         int fd = file.openForAttributeAccess(followLinks);
 173         try {
 174             try {
 175                 int n = fgetxattr(fd, nameAsBytes(file,name), address, rem);
 176 
 177                 // if remaining is zero then fgetxattr returns the size
 178                 if (rem == 0) {
 179                     if (n > 0)
 180                         throw new UnixException(ERANGE);
 181                     return 0;
 182                 }
 183 
 184                 // copy from buffer into backing array if necessary
 185                 if (nb != null) {
 186                     int off = dst.arrayOffset() + pos + Unsafe.ARRAY_BYTE_BASE_OFFSET;
 187                     unsafe.copyMemory(null, address, dst.array(), off, n);
 188                 }
 189                 dst.position(pos + n);
 190                 return n;
 191             } catch (UnixException x) {
 192                 String msg = (x.errno() == ERANGE) ?
 193                     "Insufficient space in buffer" : x.getMessage();
 194                 throw new FileSystemException(file.getPathForExceptionMessage(),
 195                     null, "Error reading extended attribute '" + name + "': " + msg);
 196             } finally {
 197                 close(fd);
 198             }
 199         } finally {
 200             if (nb != null)
 201                 nb.release();
 202         }
 203     }
 204 
 205     @Override
 206     public int write(String name, ByteBuffer src) throws IOException {
 207         if (System.getSecurityManager() != null)
 208             checkAccess(file.getPathForPermissionCheck(), false, true);
 209 
 210         int pos = src.position();
 211         int lim = src.limit();
 212         assert (pos <= lim);
 213         int rem = (pos <= lim ? lim - pos : 0);
 214 
 215         NativeBuffer nb;
 216         long address;
 217         if (src instanceof sun.nio.ch.DirectBuffer) {
 218             nb = null;
 219             address = ((sun.nio.ch.DirectBuffer)src).address() + pos;
 220         } else {
 221             // substitute with native buffer
 222             nb = NativeBuffers.getNativeBuffer(rem);
 223             address = nb.address();
 224 
 225             if (src.hasArray()) {
 226                 // copy from backing array into buffer
 227                 int off = src.arrayOffset() + pos + Unsafe.ARRAY_BYTE_BASE_OFFSET;
 228                 unsafe.copyMemory(src.array(), off, null, address, rem);
 229             } else {
 230                 // backing array not accessible so transfer via temporary array
 231                 byte[] tmp = new byte[rem];
 232                 src.get(tmp);
 233                 src.position(pos);  // reset position as write may fail
 234                 unsafe.copyMemory(tmp, Unsafe.ARRAY_BYTE_BASE_OFFSET, null,
 235                     address, rem);
 236             }
 237         }
 238 
 239         int fd = file.openForAttributeAccess(followLinks);
 240         try {
 241             try {
 242                 fsetxattr(fd, nameAsBytes(file,name), address, rem);
 243                 src.position(pos + rem);
 244                 return rem;
 245             } catch (UnixException x) {
 246                 throw new FileSystemException(file.getPathForExceptionMessage(),
 247                     null, "Error writing extended attribute '" + name + "': " +
 248                     x.getMessage());
 249             } finally {
 250                 close(fd);
 251             }
 252         } finally {
 253             if (nb != null)
 254                 nb.release();
 255         }
 256     }
 257 
 258     @Override
 259     public void delete(String name) throws IOException {
 260         if (System.getSecurityManager() != null)
 261             checkAccess(file.getPathForPermissionCheck(), false, true);
 262 
 263         int fd = file.openForAttributeAccess(followLinks);
 264         try {
 265             fremovexattr(fd, nameAsBytes(file,name));
 266         } catch (UnixException x) {
 267             throw new FileSystemException(file.getPathForExceptionMessage(),
 268                 null, "Unable to delete extended attribute '" + name + "': " + x.getMessage());
 269         } finally {
 270             close(fd);
 271         }
 272     }
 273 
 274     /**
 275      * Used by copyTo/moveTo to copy extended attributes from source to target.
 276      *
 277      * @param   ofd
 278      *          file descriptor for source file
 279      * @param   nfd
 280      *          file descriptor for target file
 281      */
 282     static void copyExtendedAttributes(int ofd, int nfd) {
 283         NativeBuffer buffer = null;
 284         try {
 285 
 286             // call flistxattr to get list of extended attributes.
 287             int size = 1024;
 288             buffer = NativeBuffers.getNativeBuffer(size);
 289             for (;;) {
 290                 try {
 291                     size = flistxattr(ofd, buffer.address(), size);
 292                     break;
 293                 } catch (UnixException x) {
 294                     // allocate larger buffer if required
 295                     if (x.errno() == ERANGE && size < 32*1024) {
 296                         buffer.release();
 297                         size *= 2;
 298                         buffer = null;
 299                         buffer = NativeBuffers.getNativeBuffer(size);
 300                         continue;
 301                     }
 302 
 303                     // unable to get list of attributes
 304                     return;
 305                 }
 306             }
 307 
 308             // parse buffer as array of NULL-terminated C strings.
 309             long address = buffer.address();
 310             int start = 0;
 311             int pos = 0;
 312             while (pos < size) {
 313                 if (unsafe.getByte(address + pos) == 0) {
 314                     // extract attribute name and copy attribute to target.
 315                     // FIXME: We can avoid needless copying by using address+pos
 316                     // as the address of the name.
 317                     int len = pos - start;
 318                     byte[] name = new byte[len];
 319                     unsafe.copyMemory(null, address+start, name,
 320                         Unsafe.ARRAY_BYTE_BASE_OFFSET, len);
 321                     try {
 322                         copyExtendedAttribute(ofd, name, nfd);
 323                     } catch (UnixException ignore) {
 324                         // ignore
 325                     }
 326                     start = pos + 1;
 327                 }
 328                 pos++;
 329             }
 330 
 331         } finally {
 332             if (buffer != null)
 333                 buffer.release();
 334         }
 335     }
 336 
 337     private static void copyExtendedAttribute(int ofd, byte[] name, int nfd)
 338         throws UnixException
 339     {
 340         int size = fgetxattr(ofd, name, 0L, 0);
 341         NativeBuffer buffer = NativeBuffers.getNativeBuffer(size);
 342         try {
 343             long address = buffer.address();
 344             size = fgetxattr(ofd, name, address, size);
 345             fsetxattr(nfd, name, address, size);
 346         } finally {
 347             buffer.release();
 348         }
 349     }
 350 }