1 /*
2  * Copyright (c) 2018 Václav Haisman, All Rights Reserved
3  *
4  * The contents of this file is dual-licensed under 2
5  * alternative Open Source/Free licenses: LGPL 2.1 or later and
6  * Apache License 2.0.
7  *
8  * You can freely decide which license you want to apply to
9  * the project.
10  *
11  * You may obtain a copy of the LGPL License at:
12  *
13  * http://www.gnu.org/licenses/licenses.html
14  *
15  * A copy is also included in the downloadable source code package
16  * containing JNA, in file "LGPL2.1".
17  *
18  * You may obtain a copy of the Apache License at:
19  *
20  * http://www.apache.org/licenses/
21  *
22  * A copy is also included in the downloadable source code package
23  * containing JNA, in file "AL2.0".
24  */
25 package com.sun.jna.platform.linux;
26 
27 import com.sun.jna.Memory;
28 import com.sun.jna.Native;
29 import com.sun.jna.platform.linux.XAttr.size_t;
30 import com.sun.jna.platform.linux.XAttr.ssize_t;
31 
32 import java.io.IOException;
33 import java.nio.ByteBuffer;
34 import java.nio.charset.Charset;
35 import java.util.Collection;
36 import java.util.LinkedHashSet;
37 import java.util.Set;
38 
39 /**
40  * Utility functions class for handling file extended attributes on Linux.
41  */
42 public abstract class XAttrUtil {
43 
XAttrUtil()44     private XAttrUtil() {
45     }
46 
47     /**
48      * Set or replace value of extended attribute.
49      *
50      * @param path  file path
51      * @param name  extended attribute name
52      * @param value value to set
53      * @throws IOException on any error
54      */
setXAttr(String path, String name, String value)55     public static void setXAttr(String path, String name, String value) throws IOException {
56         setXAttr(path, name, value, Native.getDefaultStringEncoding());
57     }
58 
59     /**
60      * Set or replace value of extended attribute.
61      *
62      * @param path     file path
63      * @param name     extended attribute name
64      * @param value    value to set
65      * @param encoding character encoding to be used for stored value
66      * @throws IOException on any error
67      */
setXAttr(String path, String name, String value, String encoding)68     public static void setXAttr(String path, String name, String value, String encoding)
69         throws IOException {
70         setXAttr(path, name, value.getBytes(encoding));
71     }
72 
73     /**
74      * Set or replace value of extended attribute.
75      *
76      * @param path  file path
77      * @param name  extended attribute name
78      * @param value value to set
79      * @throws IOException on any error
80      */
setXAttr(String path, String name, byte[] value)81     public static void setXAttr(String path, String name, byte[] value) throws IOException {
82         int retval = XAttr.INSTANCE.setxattr(path, name, value, new size_t(value.length), 0);
83         if (retval != 0) {
84             final int eno = Native.getLastError();
85             throw new IOException("errno: " + eno);
86         }
87     }
88 
89 
90     /**
91      * Set or replace value of extended attribute but in case of symbolic link set the extended
92      * attribute on the link itself instead linked file.
93      *
94      * @param path  file path
95      * @param name  extended attribute name
96      * @param value value to set
97      * @throws IOException on any error
98      */
lSetXAttr(String path, String name, String value)99     public static void lSetXAttr(String path, String name, String value) throws IOException {
100         lSetXAttr(path, name, value, Native.getDefaultStringEncoding());
101     }
102 
103     /**
104      * Set or replace value of extended attribute but in case of symbolic link set the extended
105      * attribute on the link itself instead linked file.
106      *
107      * @param path     file path
108      * @param name     extended attribute name
109      * @param value    value to set
110      * @param encoding character encoding to be used for stored value
111      * @throws IOException on any error
112      */
lSetXAttr(String path, String name, String value, String encoding)113     public static void lSetXAttr(String path, String name, String value, String encoding)
114         throws IOException {
115         lSetXAttr(path, name, value.getBytes(encoding));
116     }
117 
118     /**
119      * Set or replace value of extended attribute but in case of symbolic link set the extended
120      * attribute on the link itself instead linked file.
121      *
122      * @param path  file path
123      * @param name  extended attribute name
124      * @param value value to set
125      * @throws IOException on any error
126      */
lSetXAttr(String path, String name, byte[] value)127     public static void lSetXAttr(String path, String name, byte[] value) throws IOException {
128         final int retval = XAttr.INSTANCE.lsetxattr(path, name, value, new size_t(value.length), 0);
129         if (retval != 0) {
130             final int eno = Native.getLastError();
131             throw new IOException("errno: " + eno);
132         }
133     }
134 
135 
136     /**
137      * Set or replace value of extended attribute.
138      *
139      * @param fd    file handle
140      * @param name  extended attribute name
141      * @param value value to set
142      * @throws IOException on any error
143      */
fSetXAttr(int fd, String name, String value)144     public static void fSetXAttr(int fd, String name, String value) throws IOException {
145         fSetXAttr(fd, name, value, Native.getDefaultStringEncoding());
146     }
147 
148     /**
149      * Set or replace value of extended attribute.
150      *
151      * @param fd       file handle
152      * @param name     extended attribute name
153      * @param value    value to set
154      * @param encoding character encoding to be used for stored value
155      * @throws IOException on any error
156      */
fSetXAttr(int fd, String name, String value, String encoding)157     public static void fSetXAttr(int fd, String name, String value, String encoding)
158         throws IOException {
159         fSetXAttr(fd, name, value.getBytes(encoding));
160     }
161 
162     /**
163      * Set or replace value of extended attribute.
164      *
165      * @param fd    file handle
166      * @param name  extended attribute name
167      * @param value value to set
168      * @throws IOException on any error
169      */
fSetXAttr(int fd, String name, byte[] value)170     public static void fSetXAttr(int fd, String name, byte[] value) throws IOException {
171         final int retval = XAttr.INSTANCE.fsetxattr(fd, name, value, new size_t(value.length), 0);
172         if (retval != 0) {
173             final int eno = Native.getLastError();
174             throw new IOException("errno: " + eno);
175         }
176     }
177 
178 
179     /**
180      * Get extended attribute value.
181      *
182      * @param path file path
183      * @param name extended attribute name
184      * @return extended attribute value
185      * @throws IOException on any error except <code>ERANGE</code> which handled internally
186      */
getXAttr(String path, String name)187     public static String getXAttr(String path, String name) throws IOException {
188         return getXAttr(path, name, Native.getDefaultStringEncoding());
189     }
190 
191     /**
192      * Get extended attribute value.
193      *
194      * @param path     file path
195      * @param name     extended attribute name
196      * @param encoding character encoding to be used to decode stored extended attribute value
197      * @return extended attribute value
198      * @throws IOException on any error except <code>ERANGE</code> which handled internally
199      */
getXAttr(String path, String name, String encoding)200     public static String getXAttr(String path, String name, String encoding) throws IOException {
201         byte[] valueMem = getXAttrBytes(path, name);
202         return new String(valueMem, Charset.forName(encoding));
203     }
204 
205     /**
206      * Get extended attribute value.
207      *
208      * @param path file path
209      * @param name extended attribute name
210      * @return extended attribute value
211      * @throws IOException on any error except <code>ERANGE</code> which handled internally
212      */
getXAttrBytes(String path, String name)213     public static byte[] getXAttrBytes(String path, String name) throws IOException {
214         ssize_t retval;
215         byte[] valueMem;
216         int eno = 0;
217 
218         do {
219             retval = XAttr.INSTANCE.getxattr(path, name, (byte[]) null, size_t.ZERO);
220             if (retval.longValue() < 0) {
221                 eno = Native.getLastError();
222                 throw new IOException("errno: " + eno);
223             }
224 
225             valueMem = new byte[retval.intValue()];
226             retval = XAttr.INSTANCE.getxattr(path, name, valueMem, new size_t(valueMem.length));
227             if (retval.longValue() < 0) {
228                 eno = Native.getLastError();
229                 if (eno != XAttr.ERANGE) {
230                     throw new IOException("errno: " + eno);
231                 }
232             }
233         } while (retval.longValue() < 0 && eno == XAttr.ERANGE);
234 
235         return valueMem;
236     }
237 
238     /**
239      * Get extended attribute value.
240      *
241      * @param path file path
242      * @param name extended attribute name
243      * @return extended attribute value
244      * @throws IOException on any error except <code>ERANGE</code> which handled internally
245      */
getXAttrAsMemory(String path, String name)246     public static Memory getXAttrAsMemory(String path, String name) throws IOException {
247         ssize_t retval;
248         Memory valueMem;
249         int eno = 0;
250 
251         do {
252             retval = XAttr.INSTANCE.getxattr(path, name, (Memory) null, size_t.ZERO);
253             if (retval.longValue() < 0) {
254                 eno = Native.getLastError();
255                 throw new IOException("errno: " + eno);
256             }
257 
258             if (retval.longValue() == 0) {
259                 return null;
260             }
261 
262             valueMem = new Memory(retval.longValue());
263             retval = XAttr.INSTANCE.getxattr(path, name, valueMem, new size_t(valueMem.size()));
264             if (retval.longValue() < 0) {
265                 eno = Native.getLastError();
266                 if (eno != XAttr.ERANGE) {
267                     throw new IOException("errno: " + eno);
268                 }
269             }
270         } while (retval.longValue() < 0 && eno == XAttr.ERANGE);
271 
272         return valueMem;
273     }
274 
275 
276     /**
277      * Get extended attribute value but in case of symbolic link get the value from the link
278      * itself instead of linked file.
279      *
280      * @param path file path
281      * @param name extended attribute name
282      * @return extended attribute value
283      * @throws IOException on any error except <code>ERANGE</code> which handled internally
284      */
lGetXAttr(String path, String name)285     public static String lGetXAttr(String path, String name) throws IOException {
286         return lGetXAttr(path, name, Native.getDefaultStringEncoding());
287     }
288 
289     /**
290      * Get extended attribute value but in case of symbolic link get the value from the link
291      * itself instead of linked file.
292      *
293      * @param path     file path
294      * @param name     extended attribute name
295      * @param encoding character encoding to be used to decode stored extended attribute value
296      * @return extended attribute value
297      * @throws IOException on any error except <code>ERANGE</code> which handled internally
298      */
lGetXAttr(String path, String name, String encoding)299     public static String lGetXAttr(String path, String name, String encoding) throws IOException {
300         byte[] valueMem = lGetXAttrBytes(path, name);
301         return new String(valueMem, Charset.forName(encoding));
302     }
303 
304     /**
305      * Get extended attribute value but in case of symbolic link get the value from the link
306      * itself instead of linked file.
307      *
308      * @param path file path
309      * @param name extended attribute name
310      * @return extended attribute value
311      * @throws IOException on any error except <code>ERANGE</code> which handled internally
312      */
lGetXAttrBytes(String path, String name)313     public static byte[] lGetXAttrBytes(String path, String name) throws IOException {
314         ssize_t retval;
315         byte[] valueMem;
316         int eno = 0;
317 
318         do {
319             retval = XAttr.INSTANCE.lgetxattr(path, name, (byte[]) null, size_t.ZERO);
320             if (retval.longValue() < 0) {
321                 eno = Native.getLastError();
322                 throw new IOException("errno: " + eno);
323             }
324 
325             valueMem = new byte[retval.intValue()];
326             retval = XAttr.INSTANCE.lgetxattr(path, name, valueMem, new size_t(valueMem.length));
327             if (retval.longValue() < 0) {
328                 eno = Native.getLastError();
329                 if (eno != XAttr.ERANGE) {
330                     throw new IOException("errno: " + eno);
331                 }
332             }
333         } while (retval.longValue() < 0 && eno == XAttr.ERANGE);
334 
335         return valueMem;
336     }
337 
338     /**
339      * Get extended attribute value but in case of symbolic link get the value from the link
340      * itself instead of linked file.
341      *
342      * @param path file path
343      * @param name extended attribute name
344      * @return extended attribute value
345      * @throws IOException on any error except <code>ERANGE</code> which handled internally
346      */
lGetXAttrAsMemory(String path, String name)347     public static Memory lGetXAttrAsMemory(String path, String name) throws IOException {
348         ssize_t retval;
349         Memory valueMem;
350         int eno = 0;
351 
352         do {
353             retval = XAttr.INSTANCE.lgetxattr(path, name, (Memory) null, size_t.ZERO);
354             if (retval.longValue() < 0) {
355                 eno = Native.getLastError();
356                 throw new IOException("errno: " + eno);
357             }
358 
359             if (retval.longValue() == 0) {
360                 return null;
361             }
362 
363             valueMem = new Memory(retval.longValue());
364             retval = XAttr.INSTANCE.lgetxattr(path, name, valueMem, new size_t(valueMem.size()));
365             if (retval.longValue() < 0) {
366                 eno = Native.getLastError();
367                 if (eno != XAttr.ERANGE) {
368                     throw new IOException("errno: " + eno);
369                 }
370             }
371         } while (retval.longValue() < 0 && eno == XAttr.ERANGE);
372 
373         return valueMem;
374     }
375 
376 
377     /**
378      * Get extended attribute value.
379      *
380      * @param fd   file handle
381      * @param name extended attribute name
382      * @return extended attribute value
383      * @throws IOException on any error except <code>ERANGE</code> which handled internally
384      */
fGetXAttr(int fd, String name)385     public static String fGetXAttr(int fd, String name) throws IOException {
386         return fGetXAttr(fd, name, Native.getDefaultStringEncoding());
387     }
388 
389     /**
390      * Get extended attribute value.
391      *
392      * @param fd       file handle
393      * @param name     extended attribute name
394      * @param encoding character encoding to be used to decode stored extended attribute value
395      * @return extended attribute value
396      * @throws IOException on any error except <code>ERANGE</code> which handled internally
397      */
fGetXAttr(int fd, String name, String encoding)398     public static String fGetXAttr(int fd, String name, String encoding) throws IOException {
399         byte[] valueMem = fGetXAttrBytes(fd, name);
400         return new String(valueMem, Charset.forName(encoding));
401     }
402 
403     /**
404      * Get extended attribute value.
405      *
406      * @param fd   file handle
407      * @param name extended attribute name
408      * @return extended attribute value
409      * @throws IOException on any error except <code>ERANGE</code> which handled internally
410      */
fGetXAttrBytes(int fd, String name)411     public static byte[] fGetXAttrBytes(int fd, String name) throws IOException {
412         ssize_t retval;
413         byte[] valueMem;
414         int eno = 0;
415 
416         do {
417             retval = XAttr.INSTANCE.fgetxattr(fd, name, (byte[]) null, size_t.ZERO);
418             if (retval.longValue() < 0) {
419                 eno = Native.getLastError();
420                 throw new IOException("errno: " + eno);
421             }
422 
423             valueMem = new byte[retval.intValue()];
424             retval = XAttr.INSTANCE.fgetxattr(fd, name, valueMem, new size_t(valueMem.length));
425             if (retval.longValue() < 0) {
426                 eno = Native.getLastError();
427                 if (eno != XAttr.ERANGE) {
428                     throw new IOException("errno: " + eno);
429                 }
430             }
431         } while (retval.longValue() < 0 && eno == XAttr.ERANGE);
432 
433         return valueMem;
434     }
435 
436     /**
437      * Get extended attribute value.
438      *
439      * @param fd   file handle
440      * @param name extended attribute name
441      * @return extended attribute value
442      * @throws IOException on any error except <code>ERANGE</code> which handled internally
443      */
fGetXAttrAsMemory(int fd, String name)444     public static Memory fGetXAttrAsMemory(int fd, String name) throws IOException {
445         ssize_t retval;
446         Memory valueMem;
447         int eno = 0;
448 
449         do {
450             retval = XAttr.INSTANCE.fgetxattr(fd, name, (Memory) null, size_t.ZERO);
451             if (retval.longValue() < 0) {
452                 eno = Native.getLastError();
453                 throw new IOException("errno: " + eno);
454             }
455 
456             if (retval.longValue() == 0) {
457                 return null;
458             }
459 
460             valueMem = new Memory(retval.longValue());
461             retval = XAttr.INSTANCE.fgetxattr(fd, name, valueMem, new size_t(valueMem.size()));
462             if (retval.longValue() < 0) {
463                 eno = Native.getLastError();
464                 if (eno != XAttr.ERANGE) {
465                     throw new IOException("errno: " + eno);
466                 }
467             }
468         } while (retval.longValue() < 0 && eno == XAttr.ERANGE);
469 
470         return valueMem;
471     }
472 
473 
474     /**
475      * List extended attributes on file.
476      *
477      * @param path file path
478      * @return collection of extended attributes' names
479      * @throws IOException on any error except <code>ERANGE</code> which handled internally
480      */
listXAttr(String path)481     public static Collection<String> listXAttr(String path) throws IOException {
482         return listXAttr(path, Native.getDefaultStringEncoding());
483     }
484 
485     /**
486      * List extended attributes on file.
487      *
488      * @param path     file path
489      * @param encoding character encoding use to decode extended attributes' names
490      * @return collection of extended attributes' names
491      * @throws IOException on any error except <code>ERANGE</code> which handled internally
492      */
listXAttr(String path, String encoding)493     public static Collection<String> listXAttr(String path, String encoding) throws IOException {
494         ssize_t retval;
495         byte[] listMem;
496         int eno = 0;
497 
498         do {
499             retval = XAttr.INSTANCE.listxattr(path, (byte[]) null, size_t.ZERO);
500             if (retval.longValue() < 0) {
501                 eno = Native.getLastError();
502                 throw new IOException("errno: " + eno);
503             }
504 
505             listMem = new byte[retval.intValue()];
506             retval = XAttr.INSTANCE.listxattr(path, listMem, new size_t(listMem.length));
507             if (retval.longValue() < 0) {
508                 eno = Native.getLastError();
509                 if (eno != XAttr.ERANGE) {
510                     throw new IOException("errno: " + eno);
511                 }
512             }
513         } while (retval.longValue() < 0 && eno == XAttr.ERANGE);
514 
515         return splitBufferToStrings(listMem, encoding);
516     }
517 
518 
519     /**
520      * List extended attributes on file but in case of symbolic link get extended attributes of
521      * the link itself instead of linked file.
522      *
523      * @param path file path
524      * @return collection of extended attributes' names
525      * @throws IOException on any error except <code>ERANGE</code> which handled internally
526      */
lListXAttr(String path)527     public static Collection<String> lListXAttr(String path) throws IOException {
528         return lListXAttr(path, Native.getDefaultStringEncoding());
529     }
530 
531     /**
532      * List extended attributes on file but in case of symbolic link get extended attributes of
533      * the link itself instead of linked file.
534      *
535      * @param path     file path
536      * @param encoding character encoding use to decode extended attributes' names
537      * @return collection of extended attributes' names
538      * @throws IOException on any error except <code>ERANGE</code> which handled internally
539      */
lListXAttr(String path, String encoding)540     public static Collection<String> lListXAttr(String path, String encoding) throws IOException {
541         ssize_t retval;
542         byte[] listMem;
543         int eno = 0;
544 
545         do {
546             retval = XAttr.INSTANCE.llistxattr(path, (byte[]) null, size_t.ZERO);
547             if (retval.longValue() < 0) {
548                 eno = Native.getLastError();
549                 throw new IOException("errno: " + eno);
550             }
551 
552             listMem = new byte[retval.intValue()];
553             retval = XAttr.INSTANCE.llistxattr(path, listMem, new size_t(listMem.length));
554             if (retval.longValue() < 0) {
555                 eno = Native.getLastError();
556                 if (eno != XAttr.ERANGE) {
557                     throw new IOException("errno: " + eno);
558                 }
559             }
560         } while (retval.longValue() < 0 && eno == XAttr.ERANGE);
561 
562         return splitBufferToStrings(listMem, encoding);
563     }
564 
565 
566     /**
567      * List extended attributes on file.
568      *
569      * @param fd file handle
570      * @return collection of extended attributes' names
571      * @throws IOException on any error except <code>ERANGE</code> which handled internally
572      */
fListXAttr(int fd)573     public static Collection<String> fListXAttr(int fd) throws IOException {
574         return fListXAttr(fd, Native.getDefaultStringEncoding());
575     }
576 
577     /**
578      * List extended attributes on file.
579      *
580      * @param fd       file handle
581      * @param encoding character encoding use to decode extended attributes' names
582      * @return collection of extended attributes' names
583      * @throws IOException on any error except <code>ERANGE</code> which handled internally
584      */
fListXAttr(int fd, String encoding)585     public static Collection<String> fListXAttr(int fd, String encoding) throws IOException {
586         ssize_t retval;
587         byte[] listMem;
588         int eno = 0;
589 
590         do {
591             retval = XAttr.INSTANCE.flistxattr(fd, (byte[]) null, size_t.ZERO);
592             if (retval.longValue() < 0) {
593                 eno = Native.getLastError();
594                 throw new IOException("errno: " + eno);
595             }
596 
597             listMem = new byte[retval.intValue()];
598             retval = XAttr.INSTANCE.flistxattr(fd, listMem, new size_t(listMem.length));
599             if (retval.longValue() < 0) {
600                 eno = Native.getLastError();
601                 if (eno != XAttr.ERANGE) {
602                     throw new IOException("errno: " + eno);
603                 }
604             }
605         } while (retval.longValue() < 0 && eno == XAttr.ERANGE);
606 
607         return splitBufferToStrings(listMem, encoding);
608     }
609 
610 
611     /**
612      * Remove extended attribute from file.
613      *
614      * @param path file path
615      * @param name extended attribute name
616      * @throws IOException on any error
617      */
removeXAttr(String path, String name)618     public static void removeXAttr(String path, String name) throws IOException {
619         final int retval = XAttr.INSTANCE.removexattr(path, name);
620         if (retval != 0) {
621             final int eno = Native.getLastError();
622             throw new IOException("errno: " + eno);
623         }
624     }
625 
626     /**
627      * Remove extended attribute from file but in case of symbolic link remove extended attribute
628      * from the link itself instead of linked file.
629      *
630      * @param path file path
631      * @param name extended attribute name
632      * @throws IOException on any error
633      */
lRemoveXAttr(String path, String name)634     public static void lRemoveXAttr(String path, String name) throws IOException {
635         final int retval = XAttr.INSTANCE.lremovexattr(path, name);
636         if (retval != 0) {
637             final int eno = Native.getLastError();
638             throw new IOException("errno: " + eno);
639         }
640     }
641 
642     /**
643      * Remove extended attribute from file.
644      *
645      * @param fd   file handle
646      * @param name extended attribute name
647      * @throws IOException on any error
648      */
fRemoveXAttr(int fd, String name)649     public static void fRemoveXAttr(int fd, String name) throws IOException {
650         final int retval = XAttr.INSTANCE.fremovexattr(fd, name);
651         if (retval != 0) {
652             final int eno = Native.getLastError();
653             throw new IOException("errno: " + eno);
654         }
655     }
656 
splitBufferToStrings(byte[] valueMem, String encoding)657     private static Collection<String> splitBufferToStrings(byte[] valueMem, String encoding)
658         throws IOException {
659         final Charset charset = Charset.forName(encoding);
660         final Set<String> attributesList = new LinkedHashSet<String>(1);
661         int offset = 0;
662         for(int i = 0; i < valueMem.length; i++) {
663             // each entry is terminated by a single \0 byte
664             if(valueMem[i] == 0) {
665                 // Convert bytes of the name to String.
666                 final String name = new String(valueMem, offset, i - offset, charset);
667                 attributesList.add(name);
668                 offset = i + 1;
669             }
670         }
671         return attributesList;
672     }
673 }
674