]> arthur.barton.de Git - netatalk.git/blob - libatalk/vfs/ea.c
EA bug fixes
[netatalk.git] / libatalk / vfs / ea.c
1 /*
2   $Id: ea.c,v 1.2 2009-10-02 14:57:57 franklahm Exp $
3   Copyright (c) 2009 Frank Lahm <franklahm@gmail.com>
4
5   This program is free software; you can redistribute it and/or modify
6   it under the terms of the GNU General Public License as published by
7   the Free Software Foundation; either version 2 of the License, or
8   (at your option) any later version.
9
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14 */
15
16 /* According to man fsattr.5 we must define _ATFILE_SOURCE */
17 #ifdef HAVE_SOLARIS_EAS
18 #define _ATFILE_SOURCE
19 #endif
20
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif /* HAVE_CONFIG_H */
24
25 #include <unistd.h>
26 #include <stdint.h>
27 #include <errno.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <fcntl.h>
33 #include <dirent.h>
34
35 #include <atalk/adouble.h>
36 #include <atalk/ea.h>
37 #include <atalk/afp.h>
38 #include <atalk/logger.h>
39 #include <atalk/volume.h>
40 #include <atalk/vfs.h>
41 #include <atalk/util.h>
42
43 /*
44  * Store Extended Attributes inside .AppleDouble folders as follows:
45  *
46  * filename "fileWithEAs" with EAs "testEA1" and "testEA2"
47  *
48  * - create header with with the format struct adouble_ea_ondisk, the file is written to
49  *   ".AppleDouble/fileWithEAs::EA"
50  * - store EAs in files "fileWithEAs::EA::testEA1" and "fileWithEAs::EA::testEA2"
51  */
52
53 /*
54  * Function: unpack_header
55  *
56  * Purpose: unpack and verify header file data buffer at ea->ea_data into struct ea
57  *
58  * Arguments:
59  *
60  *    ea      (rw) handle to struct ea
61  *
62  * Returns: 0 on success, -1 on error
63  *
64  * Effects:
65  *
66  * Verifies magic and version.
67  */
68 static int unpack_header(struct ea * restrict ea)
69 {
70     int ret = 0, count = 0;
71     uint32_t uint32;
72     char *buf;
73
74     /* Check magic and version */
75     buf = ea->ea_data;
76     if (*(uint32_t *)buf != htonl(EA_MAGIC)) {
77         LOG(log_error, logtype_afpd, "unpack_header: wrong magic 0x%08x", *(uint32_t *)buf);
78         ret = -1;
79         goto exit;
80     }
81     buf += 4;
82     if (*(uint16_t *)buf != htons(EA_VERSION)) {
83         LOG(log_error, logtype_afpd, "unpack_header: wrong version 0x%04x", *(uint16_t *)buf);
84         ret = -1;
85         goto exit;
86     }
87     buf += 2;
88
89     /* Get EA count */
90     ea->ea_count = ntohs(*(uint16_t *)buf);
91     LOG(log_debug, logtype_afpd, "unpack_header: number of EAs: %u", ea->ea_count);
92     buf += 2;
93
94     if (ea->ea_count == 0)
95         return 0;
96
97     /* Allocate storage for the ea_entries array */
98     ea->ea_entries = malloc(sizeof(struct ea_entry) * ea->ea_count);
99     if ( ! ea->ea_entries) {
100         LOG(log_error, logtype_afpd, "unpack_header: OOM");
101         ret = -1;
102         goto exit;
103     }
104
105     buf = ea->ea_data + EA_HEADER_SIZE;
106     while (count < ea->ea_count) {
107         memcpy(&uint32, buf, 4); /* EA size */
108         buf += 4;
109         (*(ea->ea_entries))[count].ea_size = ntohl(uint32);
110         (*(ea->ea_entries))[count].ea_name = strdup(buf);
111         if (! (*(ea->ea_entries))[count].ea_name) {
112             LOG(log_error, logtype_afpd, "unpack_header: OOM");
113             ret = -1;
114             goto exit;
115         }
116         (*(ea->ea_entries))[count].ea_namelen = strlen((*(ea->ea_entries))[count].ea_name);
117         buf += (*(ea->ea_entries))[count].ea_namelen + 1;
118
119         LOG(log_maxdebug, logtype_afpd, "unpack_header: entry no:%u,\"%s\", size: %u, namelen: %u", count,
120             (*(ea->ea_entries))[count].ea_name,
121             (*(ea->ea_entries))[count].ea_size,
122             (*(ea->ea_entries))[count].ea_namelen);
123
124         count++;
125     }
126
127 exit:
128     return ret;
129 }
130
131 /*
132  * Function: pack_header
133  *
134  * Purpose: pack everything from struct ea into buffer at ea->ea_data
135  *
136  * Arguments:
137  *
138  *    ea      (rw) handle to struct ea
139  *
140  * Returns: 0 on success, -1 on error
141  *
142  * Effects:
143  *
144  * adjust ea->ea_count in case an ea entry deletetion is detected
145  */
146 static int pack_header(struct ea * restrict ea)
147 {
148     int count = 0, eacount = 0;
149     uint16_t uint16;
150     uint32_t uint32;
151     size_t bufsize = EA_HEADER_SIZE;
152
153     char *buf = ea->ea_data + EA_HEADER_SIZE;
154
155     LOG(log_debug, logtype_afpd, "pack_header('%s'): ea_count: %u, ea_size: %u",
156         ea->filename, ea->ea_count, ea->ea_size);
157
158     if (ea->ea_count == 0)
159         /* nothing to do, magic, version and count are still valid in buffer */
160         return 0;
161
162     while(count < ea->ea_count) { /* the names */
163         /* Check if its a deleted entry */
164         if ( ! ((*ea->ea_entries)[count].ea_name)) {
165             count++;
166             continue;
167         }
168
169         bufsize += (*(ea->ea_entries))[count].ea_namelen + 1;
170         count++;
171         eacount++;
172     }
173
174     bufsize += (eacount * 4); /* header + ea_size for each EA */
175     if (bufsize > ea->ea_size) {
176         /* we must realloc */
177         if ( ! (buf = realloc(ea->ea_data, bufsize)) ) {
178             LOG(log_error, logtype_afpd, "pack_header: OOM");
179             return -1;
180         }
181         ea->ea_data = buf;
182     }
183     ea->ea_size = bufsize;
184
185     /* copy count */
186     uint16 = htons(eacount);
187     memcpy(ea->ea_data + EA_COUNT_OFF, &uint16, 2);
188
189     count = 0;
190     buf = ea->ea_data + EA_HEADER_SIZE;
191     while (count < ea->ea_count) {
192         /* Check if its a deleted entry */
193         if ( ! ((*ea->ea_entries)[count].ea_name)) {
194             count++;
195             continue;
196         }
197
198         /* First: EA size */
199         uint32 = htonl((*(ea->ea_entries))[count].ea_size);
200         memcpy(buf, &uint32, 4);
201         buf += 4;
202
203         /* Second: EA name as C-string */
204         strcpy(buf, (*(ea->ea_entries))[count].ea_name);
205         buf += (*(ea->ea_entries))[count].ea_namelen + 1;
206
207         LOG(log_maxdebug, logtype_afpd, "pack_header: entry no:%u,\"%s\", size: %u, namelen: %u", count,
208             (*(ea->ea_entries))[count].ea_name,
209             (*(ea->ea_entries))[count].ea_size,
210             (*(ea->ea_entries))[count].ea_namelen);
211
212         count++;
213     }
214
215     ea->ea_count = eacount;
216
217     LOG(log_debug, logtype_afpd, "pack_header('%s'): ea_count: %u, ea_size: %u",
218         ea->filename, ea->ea_count, ea->ea_size);
219     
220     return 0;
221 }
222
223 /*
224  * Function: ea_path
225  *
226  * Purpose: return name of ea header filename
227  *
228  * Arguments:
229  *
230  *    ea           (r) ea handle
231  *    eaname       (r) name of EA or NULL
232  *
233  * Returns: pointer to name in static buffer
234  *
235  * Effects:
236  *
237  * Calls ad_open, copies buffer, appends "::EA" and if supplied append eanme
238  * Files: "file" -> "file/.AppleDouble/file::EA"
239  * Dirs: "dir" -> "dir/.AppleDouble/.Parent::EA"
240  * "file" with EA "myEA" -> "file/.AppleDouble/file::EA:myEA"
241  */
242 static char * ea_path(const struct ea * restrict ea,
243                       const char * restrict eaname)
244 {
245     char *adname;
246     static char pathbuf[MAXPATHLEN + 1];
247
248     /* get name of a adouble file from uname */
249     adname = ea->vol->vfs->ad_path(ea->filename, (ea->ea_flags & EA_DIR) ? ADFLAGS_DIR : 0);
250     /* copy it so we can work with it */
251     strlcpy(pathbuf, adname, MAXPATHLEN + 1);
252     /* append "::EA" */
253     strlcat(pathbuf, "::EA", MAXPATHLEN + 1);
254
255     if (eaname) {
256         strlcat(pathbuf, "::", MAXPATHLEN + 1);
257         strlcat(pathbuf, eaname, MAXPATHLEN + 1);
258     }
259
260     return pathbuf;
261 }
262
263 /*
264  * Function: ea_addentry
265  *
266  * Purpose: add one EA into ea->ea_entries[]
267  *
268  * Arguments:
269  *
270  *    ea            (rw) pointer to struct ea
271  *    uname         (r) name of file
272  *    attruname     (r) name of EA
273  *    attrsize      (r) size of ea
274  *    bitmap        (r) bitmap from FP func
275  *
276  * Returns: new number of EA entries, -1 on error
277  *
278  * Effects:
279  *
280  * Grow array ea->ea_entries[]. If ea->ea_entries is still NULL, start allocating.
281  * Otherwise realloc and put entry at the end. Increments ea->ea_count.
282  */
283 static int ea_addentry(struct ea * restrict ea,
284                        const char * restrict uname,
285                        const char * restrict attruname,
286                        size_t attrsize,
287                        int bitmap)
288 {
289     int count = 0;
290     void *tmprealloc;
291
292     /* First check if an EA of the requested name already exist */
293     if (ea->ea_count > 0) {
294         while (count < ea->ea_count) {
295             if (strcmp(attruname, (*ea->ea_entries)[count].ea_name) == 0) {
296                 LOG(log_debug, logtype_afpd, "ea_addentry('%s'): exists", attruname);
297                 if (bitmap & kXAttrCreate)
298                     /* its like O_CREAT|O_EXCL -> fail */
299                     return -1;
300                 if ( ! (bitmap & kXAttrReplace))
301                     /* replace was not requested, then its an error */
302                     return -1;
303                 break;
304             }
305             count++;
306         }
307     }
308
309     if (ea->ea_count == 0) {
310         ea->ea_entries = malloc(sizeof(struct ea_entry));
311         if ( ! ea->ea_entries) {
312             LOG(log_error, logtype_afpd, "ea_addentry: OOM");
313             return -1;
314         }
315     } else {
316         tmprealloc = realloc(ea->ea_entries, sizeof(struct ea_entry) * (ea->ea_count + 1));
317         if ( ! tmprealloc) {
318             LOG(log_error, logtype_afpd, "ea_addentry: OOM");
319             return -1;
320         }
321         ea->ea_entries = tmprealloc;
322     }
323
324     /* We've grown the array, now store the entry */
325     (*(ea->ea_entries))[ea->ea_count].ea_size = attrsize;
326     (*(ea->ea_entries))[ea->ea_count].ea_name = strdup(attruname);
327     if ( ! (*(ea->ea_entries))[ea->ea_count].ea_name) {
328         LOG(log_error, logtype_afpd, "ea_addentry: OOM");
329         goto error;
330     }
331     (*(ea->ea_entries))[ea->ea_count].ea_namelen = strlen(attruname);
332
333     ea->ea_count++;
334     return ea->ea_count;
335
336 error:
337     if (ea->ea_count == 0 && ea->ea_entries) {
338         /* We just allocated storage but had an error somewhere -> free storage*/
339         free(ea->ea_entries);
340         ea->ea_entries = NULL;
341     }
342     ea->ea_count = 0;
343     return -1;
344 }
345
346 /*
347  * Function: ea_delentry
348  *
349  * Purpose: delete one EA from ea->ea_entries[]
350  *
351  * Arguments:
352  *
353  *    ea            (rw) pointer to struct ea
354  *    uname         (r) name of EA
355  *    attruname     (r) size of ea
356  *
357  * Returns: new number of EA entries, -1 on error
358  *
359  * Effects:
360  *
361  * Remove entry from  ea->ea_entries[]. Decrement ea->ea_count.
362  * Marks it as unused just by freeing name and setting it to NULL.
363  * ea_close and pack_buffer must honor this.
364  */
365 static int ea_delentry(struct ea * restrict ea,
366                        const char * restrict uname,
367                        const char * restrict attruname)
368 {
369     int ret = 0, count = 0;
370
371     if (ea->ea_count == 0) {
372         return -1;
373     }
374
375     while (count < ea->ea_count) {
376         /* search matching EA */
377         if (strcmp(attruname, (*ea->ea_entries)[count].ea_name) == 0) {
378             free((*ea->ea_entries)[count].ea_name);
379             (*ea->ea_entries)[count].ea_name = NULL;
380
381             LOG(log_debug, logtype_afpd, "ea_delentry('%s'): deleted no %u/%u",
382                 attruname, count + 1, ea->ea_count);
383
384             break;
385         }
386         count++;
387     }
388
389     return ret;
390 }
391
392 /*
393  * Function: create_ea_header
394  *
395  * Purpose: create EA header file, only called from ea_open
396  *
397  * Arguments:
398  *
399  *    uname       (r)  filename for which we have to create a header
400  *    ea          (rw) ea handle with already allocated storage pointed to
401  *                     by ea->ea_data
402  *
403  * Returns: fd of open header file on success, -1 on error, errno semantics:
404  *          EEXIST: open with O_CREAT | O_EXCL failed
405  *
406  * Effects:
407  *
408  * Creates EA header file and initialize ea->ea_data buffer.
409  * Possibe race condition with other afpd processes:
410  * we were called because header file didn't exist in eg. ea_open. We then
411  * try to create a file with O_CREAT | O_EXCL, but the whole process in not atomic.
412  * What do we do then? Someone else is in the process of creating the header too, but
413  * it might not have finished it. That means we cant just open, read and use it!
414  * We therefor currently just break with an error.
415  * On return the header file is still r/w locked.
416  */
417 static int create_ea_header(const char * restrict uname,
418                             struct ea * restrict ea)
419 {
420     int fd = -1, err = 0;
421     char *ptr;
422
423     if ((fd = open(uname, O_RDWR | O_CREAT | O_EXCL, 0666 & ~ea->vol->v_umask)) == -1) {
424         LOG(log_error, logtype_afpd, "ea_create: open race condition with ea header for file: %s", uname);
425         return -1;
426     }
427
428     /* lock it */
429     if ((write_lock(fd, 0, SEEK_SET, 0)) != 0) {
430         LOG(log_error, logtype_afpd, "ea_create: lock race condition with ea header for file: %s", uname);
431         err = -1;
432         goto exit;
433     }
434
435     /* Now init it */
436     ptr = ea->ea_data;
437     *(uint32_t *)ptr = htonl(EA_MAGIC);
438     ptr += EA_MAGIC_LEN;
439     *(uint16_t *)ptr = htons(EA_VERSION);
440     ptr += EA_VERSION_LEN;
441     *(uint16_t *)ptr = 0;       /* count */
442
443     ea->ea_size = EA_HEADER_SIZE;
444
445 exit:
446     if (err != 0) {
447         close(fd);
448         fd = -1;
449     }
450     return fd;
451 }
452
453 /*
454  * Function: write_ea
455  *
456  * Purpose: write an EA to disk
457  *
458  * Arguments:
459  *
460  *    ea         (r) struct ea handle
461  *    attruname  (r) EA name
462  *    ibuf       (r) buffer with EA content
463  *    attrsize   (r) size of EA
464  *
465  * Returns: 0 on success, -1 on error
466  *
467  * Effects:
468  *
469  * Creates/overwrites EA file.
470  *
471  */
472 static int write_ea(const struct ea * restrict ea,
473                     const char * restrict attruname,
474                     const char * restrict ibuf,
475                     size_t attrsize)
476 {
477     int fd = -1, ret = AFP_OK;
478     struct stat st;
479     char *eaname;
480
481     eaname = ea_path(ea, attruname);
482     LOG(log_maxdebug, logtype_afpd, "write_ea: ea_apth: %s", eaname);
483
484     /* Check if it exists, remove if yes*/
485     if ((stat(eaname, &st)) == 0) {
486         if ((unlink(eaname)) != 0) {
487             if (errno == EACCES)
488                 return AFPERR_ACCESS;
489             else
490                 return AFPERR_MISC;
491         }
492     }
493
494     if ((fd = open(eaname, O_RDWR | O_CREAT | O_EXCL, 0666 & ~ea->vol->v_umask)) == -1) {
495         LOG(log_error, logtype_afpd, "write_ea: open race condition: %s", eaname);
496         return -1;
497     }
498
499     /* lock it */
500     if ((write_lock(fd, 0, SEEK_SET, 0)) != 0) {
501         LOG(log_error, logtype_afpd, "write_ea: open race condition: %s", eaname);
502         ret = -1;
503         goto exit;
504     }
505
506     if ((write(fd, ibuf, attrsize)) != attrsize) {
507         LOG(log_error, logtype_afpd, "write_ea: short write: %s", eaname);
508         ret = -1;
509         goto exit;
510     }
511
512 exit:
513     if (fd != -1)
514         close(fd); /* and unlock */
515     return ret;
516 }
517
518 /*
519  * Function: delete_ea_file
520  *
521  * Purpose: delete EA file from disk
522  *
523  * Arguments:
524  *
525  *    ea         (r) struct ea handle
526  *    attruname  (r) EA name
527  *
528  * Returns: 0 on success, -1 on error
529  */
530 static int delete_ea_file(const struct ea * restrict ea,
531                           const char *eaname)
532 {
533     int ret = 0;
534     char *eafile;
535     struct stat st;
536
537     eafile = ea_path(ea, eaname);
538
539     /* Check if it exists, remove if yes*/
540     if ((stat(eafile, &st)) == 0) {
541         if ((unlink(eafile)) != 0) {
542             LOG(log_error, logtype_afpd, "delete_ea_file('%s'): unlink: %s",
543                 eafile, strerror(errno));
544             ret = -1;
545         } else
546             LOG(log_debug, logtype_afpd, "delete_ea_file('%s'): success", eafile);
547     }
548
549     return ret;
550 }
551
552 /*
553  * Function: ea_open
554  *
555  * Purpose: open EA header file, create if it doesnt exits and called with O_CREATE
556  *
557  * Arguments:
558  *
559  *    vol         (r) current volume
560  *    uname       (r) filename for which we have to open a header
561  *    flags       (r) EA_CREATE: create if it doesn't exist (without it won't be created)
562  *                    EA_RDONLY: open read only
563  *                    EA_RDWR: open read/write
564  *                    Eiterh EA_RDONLY or EA_RDWR MUST be requested
565  *    ea          (w) pointer to a struct ea that we fill
566  *
567  * Returns: 0 on success, -1 on error
568  *
569  * Effects:
570  *
571  * opens header file and stores fd in ea->ea_fd. Size of file is put into ea->ea_size.
572  * number of EAs is stored in ea->ea_count. flags are remembered in ea->ea_flags.
573  * file is either read or write locked depending on the open flags.
574  * When you're done with struct ea you must call ea_close on it.
575  */
576 static int ea_open(const struct vol * restrict vol,
577                    const char * restrict uname,
578                    eaflags_t eaflags,
579                    struct ea * restrict ea)
580 {
581     int ret = 0;
582     char *eaname;
583     struct stat st;
584
585     /* Enforce usage rules! */
586     if ( ! (eaflags & (EA_RDONLY | EA_RDWR))) {
587         LOG(log_error, logtype_afpd, "ea_open: called without EA_RDONLY | EA_RDWR", uname);
588         return -1;
589     }
590
591     if ((stat(uname, &st)) != 0) {
592         LOG(log_debug, logtype_afpd, "ea_open: cant stat: %s", uname);
593         return -1;
594     }
595
596     /* set it all to 0 */
597     memset(ea, 0, sizeof(struct ea));
598
599     ea->vol = vol;              /* ea_close needs it */
600
601     ea->ea_flags = eaflags;
602     if (S_ISDIR(st.st_mode))
603         ea->ea_flags |=  EA_DIR;
604
605     if ( ! (ea->filename = strdup(uname))) {
606         LOG(log_error, logtype_afpd, "ea_open: OOM");
607         return -1;
608     }
609
610     eaname = ea_path(ea, NULL);
611     LOG(log_maxdebug, logtype_afpd, "ea_open: ea_path: %s", eaname);
612
613     /* Check if it exists, if not create it if EA_CREATE is in eaflags */
614     if ((stat(eaname, &st)) != 0) {
615         if (errno == ENOENT) {
616
617             /* It doesnt exist */
618
619             if ( ! (eaflags & EA_CREATE)) {
620                 /* creation was not requested, so return with error */
621                 ret = -1;
622                 goto exit;
623             }
624
625             /* Now create a header file */
626
627             /* malloc buffer for minimal on disk data */
628             ea->ea_data = malloc(EA_HEADER_SIZE);
629             if (! ea->ea_data) {
630                 LOG(log_error, logtype_afpd, "ea_open: OOM");
631                 ret = -1;
632                 goto exit;
633             }
634
635             /* create it */
636             ea->ea_fd = create_ea_header(eaname, ea);
637             if (ea->ea_fd == -1) {
638                 ret = -1;
639                 goto exit;
640             }
641
642             return 0;
643
644         } else {/* errno != ENOENT */
645             ret = -1;
646             goto exit;
647         }
648     }
649
650     /* header file exists, so read and parse it */
651
652     /* malloc buffer where we read disk file into */
653     ea->ea_size = st.st_size;
654     ea->ea_data = malloc(st.st_size);
655     if (! ea->ea_data) {
656         LOG(log_error, logtype_afpd, "ea_open: OOM");
657         ret = -1;
658         goto exit;
659     }
660
661     /* Now lock, open and read header file from disk */
662     if ((ea->ea_fd = open(eaname, (ea->ea_flags & EA_RDWR) ? O_RDWR : O_RDONLY)) == -1) {
663         LOG(log_error, logtype_afpd, "ea_open: error on open for header: %s", eaname);
664         ret = -1;
665         goto exit;
666     }
667
668     /* lock it */
669     if (ea->ea_flags & EA_RDONLY) {
670         /* read lock */
671         if ((read_lock(ea->ea_fd, 0, SEEK_SET, 0)) != 0) {
672             LOG(log_error, logtype_afpd, "ea_open: lock error on  header: %s", eaname);
673             ret = -1;
674             goto exit;
675         }
676     } else {  /* EA_RDWR */
677         /* write lock */
678         if ((write_lock(ea->ea_fd, 0, SEEK_SET, 0)) != 0) {
679             LOG(log_error, logtype_afpd, "ea_open: lock error on  header: %s", eaname);
680             ret = -1;
681             goto exit;
682         }
683     }
684
685     /* read it */
686     if ((read(ea->ea_fd, ea->ea_data, ea->ea_size)) != ea->ea_size) {
687         LOG(log_error, logtype_afpd, "ea_open: short read on header: %s", eaname);
688         ret = -1;
689         goto exit;
690     }
691
692     if ((unpack_header(ea)) != 0) {
693         LOG(log_error, logtype_afpd, "ea_open: error unpacking header for: %s", eaname);
694         ret = -1;
695         goto exit;
696     }
697
698 exit:
699     if (ret != 0) {
700         if (ea->ea_data) {
701             free(ea->ea_data);
702             ea->ea_data = NULL;
703         }
704         if (ea->ea_fd) {
705             close(ea->ea_fd);
706             ea->ea_fd = -1;
707         }
708     }
709     return ret;
710 }
711
712 /*
713  * Function: ea_close
714  *
715  * Purpose: flushes and closes an ea handle
716  *
717  * Arguments:
718  *
719  *    ea          (rw) pointer to ea handle
720  *
721  * Returns: 0 on success, -1 on error
722  *
723  * Effects:
724  *
725  * Flushes and then closes and frees all resouces held by ea handle.
726  * Pack data in ea into ea_data, then write ea_data to disk
727  */
728 static int ea_close(struct ea * restrict ea)
729 {
730     int ret = 0, count = 0;
731     char *eaname;
732     struct stat st;
733
734     LOG(log_debug, logtype_afpd, "ea_close('%s')", ea->filename);
735
736     /* pack header and write it to disk if it was opened EA_RDWR*/
737     if (ea->ea_flags & EA_RDWR) {
738         if ((pack_header(ea)) != 0) {
739             LOG(log_error, logtype_afpd, "ea_close: pack header");
740             ret = -1;
741         } else {
742             if (ea->ea_count == 0) {
743                 /* Check if EA header exists and remove it */
744                 eaname = ea_path(ea, NULL);
745                 if ((stat(eaname, &st)) == 0) {
746                     if ((unlink(eaname)) != 0) {
747                         LOG(log_error, logtype_afpd, "ea_close('%s'): unlink: %s",
748                             eaname, strerror(errno));
749                         ret = -1;
750                     }
751                     else
752                         LOG(log_debug, logtype_afpd, "ea_close(unlink '%s'): success", eaname);
753                 } else {
754                     /* stat error */
755                     if (errno != ENOENT) {
756                         LOG(log_error, logtype_afpd, "ea_close('%s'): stat: %s",
757                             eaname, strerror(errno));
758                         ret = -1;
759                     }
760                 }
761             } else { /* ea->ea_count > 0 */
762                 if ((lseek(ea->ea_fd, 0, SEEK_SET)) == -1) {
763                     LOG(log_error, logtype_afpd, "ea_close: lseek: %s", strerror(errno));
764                     ret = -1;
765                     goto exit;
766                 }
767
768                 if ((ftruncate(ea->ea_fd, 0)) == -1) {
769                     LOG(log_error, logtype_afpd, "ea_close: ftruncate: %s", strerror(errno));
770                     ret = -1;
771                     goto exit;
772                 }
773
774                 if (write(ea->ea_fd, ea->ea_data, ea->ea_size) != ea->ea_size) {
775                     LOG(log_error, logtype_afpd, "ea_close: write: %s", strerror(errno));
776                     ret = -1;
777                 }
778             }
779         }
780     }
781
782 exit:
783     /* free names */
784     while(count < ea->ea_count) {
785         if ( (*ea->ea_entries)[count].ea_name ) {
786             free((*ea->ea_entries)[count].ea_name);
787             (*ea->ea_entries)[count].ea_name = NULL;
788         }
789         count++;
790     }
791     ea->ea_count = 0;
792
793     if (ea->filename) {
794         free(ea->filename);
795         ea->filename = NULL;
796     }
797
798     if (ea->ea_entries) {
799         free(ea->ea_entries);
800         ea->ea_entries = NULL;
801     }
802
803     if (ea->ea_data) {
804         free(ea->ea_data);
805         ea->ea_data = NULL;
806     }
807     if (ea->ea_fd != -1) {
808         close(ea->ea_fd);       /* also releases the fcntl lock */
809         ea->ea_fd = -1;
810     }
811
812     return 0;
813 }
814
815
816
817 /************************************************************************************
818  * VFS funcs called from afp_ea* funcs
819  ************************************************************************************/
820
821 /*
822  * Function: get_easize
823  *
824  * Purpose: get size of an EA
825  *
826  * Arguments:
827  *
828  *    vol          (r) current volume
829  *    rbuf         (w) DSI reply buffer
830  *    rbuflen      (rw) current length of data in reply buffer
831  *    uname        (r) filename
832  *    oflag        (r) link and create flag
833  *    attruname    (r) name of attribute
834  *
835  * Returns: AFP code: AFP_OK on success or appropiate AFP error code
836  *
837  * Effects:
838  *
839  * Copies EA size into rbuf in network order. Increments *rbuflen +4.
840  */
841 int get_easize(const struct vol * restrict vol,
842                char * restrict rbuf,
843                int * restrict rbuflen,
844                const char * restrict uname,
845                int oflag,
846                const char * restrict attruname)
847 {
848     int ret = AFPERR_MISC, count = 0;
849     uint32_t uint32;
850     struct ea ea;
851
852     LOG(log_debug, logtype_afpd, "get_easize: file: %s", uname);
853
854     if ((ea_open(vol, uname, EA_RDONLY, &ea)) != 0) {
855         LOG(log_error, logtype_afpd, "get_easize: error calling ea_open for file: %s", uname);
856         return AFPERR_MISC;
857     }
858
859     while (count < ea.ea_count) {
860         if (strcmp(attruname, (*ea.ea_entries)[count].ea_name) == 0) {
861             uint32 = htonl((*ea.ea_entries)[count].ea_size);
862             memcpy(rbuf, &uint32, 4);
863             *rbuflen += 4;
864             ret = AFP_OK;
865
866             LOG(log_debug, logtype_afpd, "get_easize(\"%s\"): size: %u",
867                 attruname, (*ea.ea_entries)[count].ea_size);
868             break;
869         }
870         count++;
871     }
872
873     if ((ea_close(&ea)) != 0) {
874         LOG(log_error, logtype_afpd, "get_easize: error closing ea handle for file: %s", uname);
875         return AFPERR_MISC;
876     }
877
878     return ret;
879 }
880
881 /*
882  * Function: get_eacontent
883  *
884  * Purpose: copy EA into rbuf
885  *
886  * Arguments:
887  *
888  *    vol          (r) current volume
889  *    rbuf         (w) DSI reply buffer
890  *    rbuflen      (rw) current length of data in reply buffer
891  *    uname        (r) filename
892  *    oflag        (r) link and create flag
893  *    attruname    (r) name of attribute
894  *    maxreply     (r) maximum EA size as of current specs/real-life
895  *
896  * Returns: AFP code: AFP_OK on success or appropiate AFP error code
897  *
898  * Effects:
899  *
900  * Copies EA into rbuf. Increments *rbuflen accordingly.
901  */
902 int get_eacontent(const struct vol * restrict vol,
903                   char * restrict rbuf,
904                   int * restrict rbuflen,
905                   const char * restrict uname,
906                   int oflag,
907                   const char * restrict attruname,
908                   int maxreply)
909 {
910     int ret = AFPERR_MISC, count = 0, fd = -1;
911     uint32_t uint32;
912     size_t toread;
913     struct ea ea;
914
915     LOG(log_debug, logtype_afpd, "get_eacontent('%s/%s')", uname, attruname);
916
917     if ((ea_open(vol, uname, EA_RDONLY, &ea)) != 0) {
918         LOG(log_error, logtype_afpd, "get_eacontent('%s'): ea_open error", uname);
919         return AFPERR_MISC;
920     }
921
922     while (count < ea.ea_count) {
923         if (strcmp(attruname, (*ea.ea_entries)[count].ea_name) == 0) {
924             if ((fd = open(ea_path(&ea, attruname), O_RDONLY)) == -1) {
925                 ret = AFPERR_MISC;
926                 break;
927             }
928
929             /* Check how much the client wants, give him what we think is right */
930             maxreply -= MAX_REPLY_EXTRA_BYTES;
931             if (maxreply > MAX_EA_SIZE)
932                 maxreply = MAX_EA_SIZE;
933             toread = (maxreply < (*ea.ea_entries)[count].ea_size) ? maxreply : (*ea.ea_entries)[count].ea_size;
934             LOG(log_debug, logtype_afpd, "get_eacontent('%s'): sending %u bytes", attruname, toread);
935
936             /* Put length of EA data in reply buffer */
937             uint32 = htonl(toread);
938             memcpy(rbuf, &uint32, 4);
939             rbuf += 4;
940             *rbuflen += 4;
941
942             if ((read(fd, rbuf, toread)) != toread) {
943                 LOG(log_error, logtype_afpd, "get_eacontent('%s/%s'): short read", uname, attruname);
944                 ret = AFPERR_MISC;
945                 break;
946             }
947             *rbuflen += toread;
948             close(fd);
949
950             ret = AFP_OK;
951             break;
952         }
953         count++;
954     }
955
956     if ((ea_close(&ea)) != 0) {
957         LOG(log_error, logtype_afpd, "get_eacontent('%s'): error closing ea handle", uname);
958         return AFPERR_MISC;
959     }
960
961     return ret;
962
963 }
964
965 /*
966  * Function: list_eas
967  *
968  * Purpose: copy names of EAs into attrnamebuf
969  *
970  * Arguments:
971  *
972  *    vol          (r) current volume
973  *    attrnamebuf  (w) store names a consecutive C strings here
974  *    buflen       (rw) length of names in attrnamebuf
975  *    uname        (r) filename
976  *    oflag        (r) link and create flag
977  *
978  * Returns: AFP code: AFP_OK on success or appropiate AFP error code
979  *
980  * Effects:
981  *
982  * Copies names of all EAs of uname as consecutive C strings into rbuf.
983  * Increments *buflen accordingly.
984  */
985 int list_eas(const struct vol * restrict vol,
986              char * restrict attrnamebuf,
987              int * restrict buflen,
988              const char * restrict uname,
989              int oflag)
990 {
991     int count = 0, attrbuflen = *buflen, ret = AFP_OK, len;
992     char *buf = attrnamebuf;
993     struct ea ea;
994
995     LOG(log_debug, logtype_afpd, "list_eas: file: %s", uname);
996
997     if ((ea_open(vol, uname, EA_RDONLY, &ea)) != 0) {
998         if (errno != ENOENT) {
999             LOG(log_error, logtype_afpd, "list_eas: error calling ea_open for file: %s", uname);
1000             return AFPERR_MISC;
1001         }
1002         else
1003             return AFP_OK;
1004     }
1005
1006     while (count < ea.ea_count) {
1007         /* Convert name to CH_UTF8_MAC and directly store in in the reply buffer */
1008         if ( ( len = convert_string(vol->v_volcharset,
1009                                     CH_UTF8_MAC, 
1010                                     (*ea.ea_entries)[count].ea_name,
1011                                     (*ea.ea_entries)[count].ea_namelen,
1012                                     buf + attrbuflen,
1013                                     255))
1014              <= 0 ) {
1015             ret = AFPERR_MISC;
1016             goto exit;
1017         }
1018         if (len == 255)
1019             /* convert_string didn't 0-terminate */
1020             attrnamebuf[attrbuflen + 255] = 0;
1021
1022         LOG(log_debug7, logtype_afpd, "list_eas(%s): EA: %s",
1023             uname, (*ea.ea_entries)[count].ea_name);
1024
1025         attrbuflen += len + 1;
1026         if (attrbuflen > (ATTRNAMEBUFSIZ - 256)) {
1027             /* Next EA name could overflow, so bail out with error.
1028                FIXME: evantually malloc/memcpy/realloc whatever.
1029                Is it worth it ? */
1030             LOG(log_warning, logtype_afpd, "list_eas(%s): running out of buffer for EA names", uname);
1031             ret = AFPERR_MISC;
1032             goto exit;
1033         }
1034         count++;
1035     }
1036
1037 exit:
1038     *buflen = attrbuflen;
1039
1040     if ((ea_close(&ea)) != 0) {
1041         LOG(log_error, logtype_afpd, "list_eas: error closing ea handle for file: %s", uname);
1042         return AFPERR_MISC;
1043     }
1044
1045     return ret;
1046 }
1047
1048 /*
1049  * Function: set_ea
1050  *
1051  * Purpose: set a Solaris native EA
1052  *
1053  * Arguments:
1054  *
1055  *    vol          (r) current volume
1056  *    uname        (r) filename
1057  *    attruname    (r) EA name
1058  *    ibuf         (r) buffer with EA content
1059  *    attrsize     (r) length EA in ibuf
1060  *    oflag        (r) link and create flag
1061  *
1062  * Returns: AFP code: AFP_OK on success or appropiate AFP error code
1063  *
1064  * Effects:
1065  *
1066  * Copies names of all EAs of uname as consecutive C strings into rbuf.
1067  * Increments *rbuflen accordingly.
1068  */
1069 int set_ea(const struct vol * restrict vol,
1070            const char * restrict uname,
1071            const char * restrict attruname,
1072            const char * restrict ibuf,
1073            size_t attrsize,
1074            int oflag)
1075 {
1076     int ret = AFP_OK;
1077     struct ea ea;
1078
1079     LOG(log_debug, logtype_afpd, "set_ea: file: %s", uname);
1080
1081     if ((ea_open(vol, uname, EA_CREATE | EA_RDWR, &ea)) != 0) {
1082         LOG(log_error, logtype_afpd, "set_ea('%s'): ea_open error", uname);
1083         return AFPERR_MISC;
1084     }
1085
1086     if ((ea_addentry(&ea, uname, attruname, attrsize, oflag)) == -1) {
1087         LOG(log_error, logtype_afpd, "set_ea('%s'): ea_addentry error", uname);
1088         ret = AFPERR_MISC;
1089         goto exit;
1090     }
1091
1092     if ((write_ea(&ea, attruname, ibuf, attrsize)) != 0) {
1093         LOG(log_error, logtype_afpd, "set_ea('%s'): write_ea error", uname);
1094         ret = AFPERR_MISC;
1095         goto exit;
1096     }
1097
1098 exit:
1099     if ((ea_close(&ea)) != 0) {
1100         LOG(log_error, logtype_afpd, "set_ea('%s'): ea_close error", uname);
1101         ret = AFPERR_MISC;
1102         goto exit;
1103     }
1104
1105     return ret;
1106 }
1107
1108 /*
1109  * Function: remove_ea
1110  *
1111  * Purpose: remove a EA from a file
1112  *
1113  * Arguments:
1114  *
1115  *    vol          (r) current volume
1116  *    uname        (r) filename
1117  *    attruname    (r) EA name
1118  *    oflag        (r) link and create flag
1119  *
1120  * Returns: AFP code: AFP_OK on success or appropiate AFP error code
1121  *
1122  * Effects:
1123  *
1124  * Removes EA attruname from file uname.
1125  */
1126 int remove_ea(const struct vol * restrict vol,
1127               const char * restrict uname,
1128               const char * restrict attruname,
1129               int oflag)
1130 {
1131     int ret = AFP_OK;
1132     struct ea ea;
1133
1134     LOG(log_debug, logtype_afpd, "remove_ea('%s/%s')", uname, attruname);
1135
1136     if ((ea_open(vol, uname, EA_RDWR, &ea)) != 0) {
1137         LOG(log_error, logtype_afpd, "remove_ea('%s'): ea_open error", uname);
1138         return AFPERR_MISC;
1139     }
1140
1141     if ((ea_delentry(&ea, uname, attruname)) == -1) {
1142         LOG(log_error, logtype_afpd, "remove_ea('%s'): ea_delentry error", uname);
1143         ret = AFPERR_MISC;
1144         goto exit;
1145     }
1146
1147     if ((delete_ea_file(&ea, attruname)) != 0) {
1148         LOG(log_error, logtype_afpd, "remove_ea('%s'): delete_ea error", uname);
1149         ret = AFPERR_MISC;
1150         goto exit;
1151     }
1152
1153 exit:
1154     if ((ea_close(&ea)) != 0) {
1155         LOG(log_error, logtype_afpd, "remove_ea('%s'): ea_close error", uname);
1156         ret = AFPERR_MISC;
1157         goto exit;
1158     }
1159
1160     return ret;
1161 }
1162
1163 /**********************************************************************************
1164  * Solaris EA VFS funcs
1165  **********************************************************************************/
1166
1167 /*
1168  * Function: sol_get_easize
1169  *
1170  * Purpose: get size of an EA on Solaris native EA
1171  *
1172  * Arguments:
1173  *
1174  *    vol          (r) current volume
1175  *    rbuf         (w) DSI reply buffer
1176  *    rbuflen      (rw) current length of data in reply buffer
1177  *    uname        (r) filename
1178  *    oflag        (r) link and create flag
1179  *    attruname    (r) name of attribute
1180  *
1181  * Returns: AFP code: AFP_OK on success or appropiate AFP error code
1182  *
1183  * Effects:
1184  *
1185  * Copies EA size into rbuf in network order. Increments *rbuflen +4.
1186  */
1187 #ifdef HAVE_SOLARIS_EAS
1188 int sol_get_easize(const struct vol * restrict vol,
1189                    char * restrict rbuf,
1190                    int * restrict rbuflen,
1191                    const char * restrict uname,
1192                    int oflag,
1193                    cons char * restrict attruname)
1194 {
1195     int                 ret, attrdirfd;
1196     uint32_t            attrsize;
1197     struct stat         st;
1198
1199     LOG(log_debug7, logtype_afpd, "sol_getextattr_size(%s): attribute: \"%s\"", uname, attruname);
1200
1201     if ( -1 == (attrdirfd = attropen(uname, ".", O_RDONLY | oflag))) {
1202         if (errno == ELOOP) {
1203             /* its a symlink and client requested O_NOFOLLOW  */
1204             LOG(log_debug, logtype_afpd, "sol_getextattr_size(%s): encountered symlink with kXAttrNoFollow", uname);
1205
1206             memset(rbuf, 0, 4);
1207             *rbuflen += 4;
1208
1209             return AFP_OK;
1210         }
1211         LOG(log_error, logtype_afpd, "sol_getextattr_size: attropen error: %s", strerror(errno));
1212         return AFPERR_MISC;
1213     }
1214
1215     if ( -1 == (fstatat(attrdirfd, attruname, &st, 0))) {
1216         LOG(log_error, logtype_afpd, "sol_getextattr_size: fstatat error: %s", strerror(errno));
1217         ret = AFPERR_MISC;
1218         goto exit;
1219     }
1220     attrsize = (st.st_size > MAX_EA_SIZE) ? MAX_EA_SIZE : st.st_size;
1221
1222     /* Start building reply packet */
1223
1224     LOG(log_debug7, logtype_afpd, "sol_getextattr_size(%s): attribute: \"%s\", size: %u", uname, attruname, attrsize);
1225
1226     /* length of attribute data */
1227     attrsize = htonl(attrsize);
1228     memcpy(rbuf, &attrsize, 4);
1229     *rbuflen += 4;
1230
1231     ret = AFP_OK;
1232
1233 exit:
1234     close(attrdirfd);
1235     return ret;
1236 }
1237 #endif /* HAVE_SOLARIS_EAS */
1238
1239 /*
1240  * Function: sol_get_eacontent
1241  *
1242  * Purpose: copy Solaris native EA into rbuf
1243  *
1244  * Arguments:
1245  *
1246  *    vol          (r) current volume
1247  *    rbuf         (w) DSI reply buffer
1248  *    rbuflen      (rw) current length of data in reply buffer
1249  *    uname        (r) filename
1250  *    oflag        (r) link and create flag
1251  *    attruname    (r) name of attribute
1252  *    maxreply     (r) maximum EA size as of current specs/real-life
1253  *
1254  * Returns: AFP code: AFP_OK on success or appropiate AFP error code
1255  *
1256  * Effects:
1257  *
1258  * Copies EA into rbuf. Increments *rbuflen accordingly.
1259  */
1260 #ifdef HAVE_SOLARIS_EAS
1261 int sol_get_eacontent(const struct vol * restrict vol,
1262                       char * restrict rbuf,
1263                       int * restrict rbuflen,
1264                       const char * restrict uname,
1265                       int oflag,
1266                       char * restrict attruname,
1267                       int maxreply)
1268 {
1269     int                 ret, attrdirfd;
1270     size_t              toread, okread = 0, len;
1271     char                *datalength;
1272     struct stat         st;
1273
1274     if ( -1 == (attrdirfd = attropen(uname, attruname, O_RDONLY | oflag))) {
1275         if (errno == ELOOP) {
1276             /* its a symlink and client requested O_NOFOLLOW  */
1277             LOG(log_debug, logtype_afpd, "sol_getextattr_content(%s): encountered symlink with kXAttrNoFollow", uname);
1278
1279             memset(rbuf, 0, 4);
1280             *rbuflen += 4;
1281
1282             return AFP_OK;
1283         }
1284         LOG(log_error, logtype_afpd, "sol_getextattr_content(%s): attropen error: %s", attruname, strerror(errno));
1285         return AFPERR_MISC;
1286     }
1287
1288     if ( -1 == (fstat(attrdirfd, &st))) {
1289         LOG(log_error, logtype_afpd, "sol_getextattr_content(%s): fstatat error: %s", attruname,strerror(errno));
1290         ret = AFPERR_MISC;
1291         goto exit;
1292     }
1293
1294     /* Start building reply packet */
1295
1296     maxreply -= MAX_REPLY_EXTRA_BYTES;
1297     if (maxreply > MAX_EA_SIZE)
1298         maxreply = MAX_EA_SIZE;
1299
1300     /* But never send more than the client requested */
1301     toread = (maxreply < st.st_size) ? maxreply : st.st_size;
1302
1303     LOG(log_debug7, logtype_afpd, "sol_getextattr_content(%s): attribute: \"%s\", size: %u", uname, attruname, maxreply);
1304
1305     /* remember where we must store length of attribute data in rbuf */
1306     datalength = rbuf;
1307     rbuf += 4;
1308     *rbuflen += 4;
1309
1310     while (1) {
1311         len = read(attrdirfd, rbuf, toread);
1312         if (len == -1) {
1313             LOG(log_error, logtype_afpd, "sol_getextattr_content(%s): read error: %s", attruname, strerror(errno));
1314             ret = AFPERR_MISC;
1315             goto exit;
1316         }
1317         okread += len;
1318         rbuf += len;
1319         *rbuflen += len;
1320         if ((len == 0) || (okread == toread))
1321             break;
1322     }
1323
1324     okread = htonl((uint32_t)okread);
1325     memcpy(datalength, &okread, 4);
1326
1327     ret = AFP_OK;
1328
1329 exit:
1330     close(attrdirfd);
1331     return ret;
1332 }
1333 #endif /* HAVE_SOLARIS_EAS */
1334
1335 /*
1336  * Function: sol_list_eas
1337  *
1338  * Purpose: copy names of Solaris native EA into attrnamebuf
1339  *
1340  * Arguments:
1341  *
1342  *    vol          (r) current volume
1343  *    attrnamebuf  (w) store names a consecutive C strings here
1344  *    buflen       (rw) length of names in attrnamebuf
1345  *    uname        (r) filename
1346  *    oflag        (r) link and create flag
1347  *
1348  * Returns: AFP code: AFP_OK on success or appropiate AFP error code
1349  *
1350  * Effects:
1351  *
1352  * Copies names of all EAs of uname as consecutive C strings into rbuf.
1353  * Increments *rbuflen accordingly.
1354  */
1355 #ifdef HAVE_SOLARIS_EAS
1356 int sol_list_eas(const struct vol * restrict vol,
1357                  char * restrict attrnamebuf,
1358                  int * restrict buflen,
1359                  const char * restrict uname,
1360                  int oflag)
1361 {
1362     int ret, attrbuflen = *buflen, len, attrdirfd = 0;
1363     struct dirent *dp;
1364     DIR *dirp = NULL;
1365
1366     /* Now list file attribute dir */
1367     if ( -1 == (attrdirfd = attropen( uname, ".", O_RDONLY | oflag))) {
1368         if (errno == ELOOP) {
1369             /* its a symlink and client requested O_NOFOLLOW */
1370             ret = AFPERR_BADTYPE;
1371             goto exit;
1372         }
1373         LOG(log_error, logtype_afpd, "sol_list_extattr(%s): error opening atttribute dir: %s", uname, strerror(errno));
1374         ret = AFPERR_MISC;
1375         goto exit;
1376     }
1377
1378     if (NULL == (dirp = fdopendir(attrdirfd))) {
1379         LOG(log_error, logtype_afpd, "sol_list_extattr(%s): error opening atttribute dir: %s", uname, strerror(errno));
1380         ret = AFPERR_MISC;
1381         goto exit;
1382     }
1383
1384     while ((dp = readdir(dirp)))  {
1385         /* check if its "." or ".." */
1386         if ((strcmp(dp->d_name, ".") == 0) || (strcmp(dp->d_name, "..") == 0) ||
1387             (strcmp(dp->d_name, "SUNWattr_ro") == 0) || (strcmp(dp->d_name, "SUNWattr_rw") == 0))
1388             continue;
1389
1390         len = strlen(dp->d_name);
1391
1392         /* Convert name to CH_UTF8_MAC and directly store in in the reply buffer */
1393         if ( 0 >= ( len = convert_string(vol->v_volcharset, CH_UTF8_MAC, dp->d_name, len, attrnamebuf + attrbuflen, 255)) ) {
1394             ret = AFPERR_MISC;
1395             goto exit;
1396         }
1397         if (len == 255)
1398             /* convert_string didn't 0-terminate */
1399             attrnamebuf[attrbuflen + 255] = 0;
1400
1401         LOG(log_debug7, logtype_afpd, "sol_list_extattr(%s): attribute: %s", uname, dp->d_name);
1402
1403         attrbuflen += len + 1;
1404         if (attrbuflen > (ATTRNAMEBUFSIZ - 256)) {
1405             /* Next EA name could overflow, so bail out with error.
1406                FIXME: evantually malloc/memcpy/realloc whatever.
1407                Is it worth it ? */
1408             LOG(log_warning, logtype_afpd, "sol_list_extattr(%s): running out of buffer for EA names", uname);
1409             ret = AFPERR_MISC;
1410             goto exit;
1411         }
1412     }
1413
1414     ret = AFP_OK;
1415
1416 exit:
1417     if (dirp)
1418         closedir(dirp);
1419
1420     if (attrdirfd > 0)
1421         close(attrdirfd);
1422
1423     *buflen = attrbuflen;
1424     return ret;
1425 }
1426 #endif /* HAVE_SOLARIS_EAS */
1427
1428 /*
1429  * Function: sol_set_ea
1430  *
1431  * Purpose: set a Solaris native EA
1432  *
1433  * Arguments:
1434  *
1435  *    vol          (r) current volume
1436  *    uname        (r) filename
1437  *    attruname    (r) EA name
1438  *    ibuf         (r) buffer with EA content
1439  *    attrsize     (r) length EA in ibuf
1440  *    oflag        (r) link and create flag
1441  *
1442  * Returns: AFP code: AFP_OK on success or appropiate AFP error code
1443  *
1444  * Effects:
1445  *
1446  * Copies names of all EAs of uname as consecutive C strings into rbuf.
1447  * Increments *rbuflen accordingly.
1448  */
1449 #ifdef HAVE_SOLARIS_EAS
1450 int sol_set_ea(const struct vol * restrict vol,
1451                const char * restrict u_name,
1452                const char * restrict attruname,
1453                const char * restrict ibuf,
1454                size_t attrsize,
1455                int oflag)
1456 {
1457     int attrdirfd;
1458
1459     if ( -1 == (attrdirfd = attropen(u_name, attruname, oflag, 0666))) {
1460         if (errno == ELOOP) {
1461             /* its a symlink and client requested O_NOFOLLOW  */
1462             LOG(log_debug, logtype_afpd, "afp_setextattr(%s): encountered symlink with kXAttrNoFollow", s_path->u_name);
1463             return AFP_OK;
1464         }
1465         LOG(log_error, logtype_afpd, "afp_setextattr(%s): attropen error: %s", s_path->u_name, strerror(errno));
1466         return AFPERR_MISC;
1467     }
1468
1469     if ((write(attrdirfd, ibuf, attrsize)) != attrsize) {
1470         LOG(log_error, logtype_afpd, "afp_setextattr(%s): read error: %s", attruname, strerror(errno));
1471         return AFPERR_MISC;
1472     }
1473
1474     return AFP_OK;
1475 }
1476 #endif /* HAVE_SOLARIS_EAS */
1477
1478 /*
1479  * Function: sol_remove_ea
1480  *
1481  * Purpose: remove a Solaris native EA
1482  *
1483  * Arguments:
1484  *
1485  *    vol          (r) current volume
1486  *    uname        (r) filename
1487  *    attruname    (r) EA name
1488  *    oflag        (r) link and create flag
1489  *
1490  * Returns: AFP code: AFP_OK on success or appropiate AFP error code
1491  *
1492  * Effects:
1493  *
1494  * Removes EA attruname from file uname.
1495  */
1496 #ifdef HAVE_SOLARIS_EAS
1497 int sol_remove_ea(const struct vol * restrict vol,
1498                   const char * restrict uname,
1499                   const char * restrict attruname,
1500                   int oflag)
1501 {
1502     int attrdirfd;
1503
1504     if ( -1 == (attrdirfd = attropen(uname, ".", oflag))) {
1505         switch (errno) {
1506         case ELOOP:
1507             /* its a symlink and client requested O_NOFOLLOW  */
1508             LOG(log_debug, logtype_afpd, "afp_remextattr(%s): encountered symlink with kXAttrNoFollow", s_path->u_name);
1509             return AFP_OK;
1510         case EACCES:
1511             LOG(log_debug, logtype_afpd, "afp_remextattr(%s): unlinkat error: %s", s_path->u_name, strerror(errno));
1512             return AFPERR_ACCESS;
1513         default:
1514             LOG(log_error, logtype_afpd, "afp_remextattr(%s): attropen error: %s", s_path->u_name, strerror(errno));
1515             return AFPERR_MISC;
1516         }
1517     }
1518
1519     if ( -1 == (unlinkat(attrdirfd, attruname, 0)) ) {
1520         if (errno == EACCES) {
1521             LOG(log_debug, logtype_afpd, "afp_remextattr(%s): unlinkat error: %s", s_path->u_name, strerror(errno));
1522             return AFPERR_ACCESS;
1523         }
1524         LOG(log_error, logtype_afpd, "afp_remextattr(%s): unlinkat error: %s", s_path->u_name, strerror(errno));
1525         return AFPERR_MISC;
1526     }
1527
1528 }
1529 #endif /* HAVE_SOLARIS_EAS */