2 $Id: extattrs.c,v 1.2 2009-03-03 13:51:25 franklahm Exp $
3 Copyright (c) 2009 Frank Lahm <franklahm@gmail.com>
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.
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.
16 /* According to man fsattr.5 we must define _ATFILE_SOURCE */
17 #define _ATFILE_SOURCE
21 #endif /* HAVE_CONFIG_H */
29 #include <sys/types.h>
34 #include <atalk/adouble.h>
35 #include <atalk/afp.h>
36 #include <atalk/logger.h>
41 #include "directory.h"
45 static char *ea_finderinfo = "com.apple.FinderInfo";
46 static char *ea_resourcefork = "com.apple.ResourceFork";
48 /* This should be big enough to consecutively store the names of all attributes */
49 #define ATTRNAMEBUFSIZ 4096
50 static char attrnamebuf[ATTRNAMEBUFSIZ];
52 static void hexdump(void *m, size_t l) {
59 len = sprintf(bufp, "%02x ", *p++);
63 if ((count % 16) == 0) {
64 LOG(log_debug9, logtype_afpd, "%s", buf);
70 static int getextattr_size(char *rbuf, int *rbuflen, char *uname, int oflag, char *attruname)
76 LOG(log_debug7, logtype_afpd, "getextattr_size(%s): attribute: \"%s\"", uname, attruname);
78 if ( -1 == (attrdirfd = attropen(uname, ".", O_RDONLY | oflag))) {
80 /* its a symlink and client requested O_NOFOLLOW */
81 LOG(log_debug, logtype_afpd, "getextattr_size(%s): encountered symlink with kXAttrNoFollow", uname);
88 LOG(log_error, logtype_afpd, "getextattr_size: attropen error: %s", strerror(errno));
92 if ( -1 == (fstatat(attrdirfd, attruname, &st, 0))) {
93 LOG(log_error, logtype_afpd, "getextattr_size: fstatat error: %s", strerror(errno));
97 attrsize = (st.st_size > MAX_EA_SIZE) ? MAX_EA_SIZE : st.st_size;
99 /* Start building reply packet */
101 LOG(log_debug7, logtype_afpd, "getextattr_size(%s): attribute: \"%s\", size: %u", uname, attruname, attrsize);
103 /* length of attribute data */
104 attrsize = htonl(attrsize);
105 memcpy(rbuf, &attrsize, 4);
115 static int getextattr_content(char *rbuf, int *rbuflen, char *uname, int oflag, char *attruname, int maxreply)
118 size_t toread, okread = 0, len;
122 if ( -1 == (attrdirfd = attropen(uname, attruname, O_RDONLY | oflag))) {
123 if (errno == ELOOP) {
124 /* its a symlink and client requested O_NOFOLLOW */
125 LOG(log_debug, logtype_afpd, "getextattr_content(%s): encountered symlink with kXAttrNoFollow", uname);
132 LOG(log_error, logtype_afpd, "getextattr_content(%s): attropen error: %s", attruname, strerror(errno));
136 if ( -1 == (fstat(attrdirfd, &st))) {
137 LOG(log_error, logtype_afpd, "getextattr_content(%s): fstatat error: %s", attruname,strerror(errno));
142 /* Start building reply packet */
144 maxreply -= MAX_REPLY_EXTRA_BYTES;
145 if (maxreply > MAX_EA_SIZE)
146 maxreply = MAX_EA_SIZE;
148 /* But never send more than the client requested */
149 toread = (maxreply < st.st_size) ? maxreply : st.st_size;
151 LOG(log_debug7, logtype_afpd, "getextattr_content(%s): attribute: \"%s\", size: %u", uname, attruname, maxreply);
153 /* remember where we must store length of attribute data in rbuf */
159 len = read(attrdirfd, rbuf, toread);
161 LOG(log_error, logtype_afpd, "getextattr_content(%s): read error: %s", attruname, strerror(errno));
168 if ((len == 0) || (okread == toread))
172 okread = htonl((uint32_t)okread);
173 memcpy(datalength, &okread, 4);
182 int list_extattr(AFPObj *obj, char *attrnamebuf, int *buflen, char *uname, int oflag)
184 int ret, attrbuflen = *buflen, len, attrdirfd = 0;
188 /* Now list file attribute dir */
189 if ( -1 == (attrdirfd = attropen( uname, ".", O_RDONLY | oflag))) {
190 if (errno == ELOOP) {
191 /* its a symlink and client requested O_NOFOLLOW */
192 ret = AFPERR_BADTYPE;
195 LOG(log_error, logtype_afpd, "list_extattr(%s): error opening atttribute dir: %s", uname, strerror(errno));
200 if (NULL == (dirp = fdopendir(attrdirfd))) {
201 LOG(log_error, logtype_afpd, "list_extattr(%s): error opening atttribute dir: %s", uname, strerror(errno));
206 while ((dp = readdir(dirp))) {
207 /* check if its "." or ".." */
208 if ((strcmp(dp->d_name, ".") == 0) || (strcmp(dp->d_name, "..") == 0) ||
209 (strcmp(dp->d_name, "SUNWattr_ro") == 0) || (strcmp(dp->d_name, "SUNWattr_rw") == 0))
212 len = strlen(dp->d_name);
214 if ( 0 >= ( len = convert_string(obj->options.unixcharset, CH_UTF8_MAC, dp->d_name, len, attrnamebuf + attrbuflen, 255)) ) {
219 /* convert_string didn't 0-terminate */
220 attrnamebuf[attrbuflen + 255] = 0;
222 LOG(log_debug7, logtype_afpd, "list_extattr(%s): attribute: %s", uname, dp->d_name);
224 attrbuflen += len + 1;
225 if (attrbuflen > (ATTRNAMEBUFSIZ - 256)) {
226 /* Next EA name could overflow, so bail out with error.
227 FIXME: evantually malloc/memcpy/realloc whatever.
229 LOG(log_warning, logtype_afpd, "list_extattr(%s): running out of buffer for EA names", uname);
244 *buflen = attrbuflen;
248 /***************************************
250 ****************************************/
253 Note: we're being called twice. Firstly the client only want the size of all
254 EA names, secondly it wants these names. In order to avoid scanning EAs twice
255 we cache them in a static buffer.
257 int afp_listextattr(AFPObj *obj, char *ibuf, int ibuflen _U_, char *rbuf, int *rbuflen)
259 int count, ret, oflag = 0;
260 uint16_t vid, bitmap;
261 uint32_t did, maxreply;
265 struct adouble ad, *adp = NULL;
267 char *uname, *FinderInfo;
268 static int buf_valid = 0, attrbuflen = 0;
271 LOG(log_debug9, logtype_afpd, "afp_listextattr: BEGIN");
277 /* Get MaxReplySize first */
278 memcpy( &maxreply, ibuf + 14, 4);
279 maxreply = ntohl( maxreply );
282 If its the first request with maxreply=0 or if we didn't mark our buffers valid for
283 whatever reason (just a safety check, it should be valid), then scan for attributes
285 if ((maxreply == 0) || (buf_valid == 0)) {
289 memcpy( &vid, ibuf, 2);
291 if (NULL == ( vol = getvolbyvid( vid )) ) {
292 LOG(log_error, logtype_afpd, "afp_listextattr: getvolbyvid error: %s", strerror(errno));
293 return AFPERR_ACCESS;
296 memcpy( &did, ibuf, 4);
298 if (NULL == ( dir = dirlookup( vol, did )) ) {
299 LOG(log_error, logtype_afpd, "afp_listextattr: dirlookup error: %s", strerror(errno));
303 memcpy( &bitmap, ibuf, 2);
304 bitmap = ntohs( bitmap );
307 if (bitmap & kXAttrNoFollow)
310 /* Skip ReqCount, StartIndex and maxreply*/
314 if (NULL == ( s_path = cname( vol, dir, &ibuf )) ) {
315 LOG(log_error, logtype_afpd, "afp_listextattr: cname error: %s", strerror(errno));
318 uname = s_path->u_name;
321 We have to check the FinderInfo for the file, because if they aren't all 0
322 we must return the synthetic attribute "com.apple.FinderInfo".
323 Note: the client will never (never seen in traces) request that attribute
326 if ((of = of_findname(s_path))) {
329 ad_init(&ad, vol->v_adouble, vol->v_ad_options);
333 if ( ad_metadata( uname, 0, adp) < 0 ) {
336 LOG(log_error, logtype_afpd, "afp_listextattr(%s): %s: check resource fork permission?",
337 uname, strerror(errno));
338 return AFPERR_ACCESS;
340 LOG(log_error, logtype_afpd, "afp_listextattr(%s): error getting metadata: %s", uname, strerror(errno));
345 FinderInfo = ad_entry(adp, ADEID_FINDERI);
347 LOG(log_debug9, logtype_afpd, "afp_listextattr(%s): FinderInfo:", uname);
348 hexdump( FinderInfo, 32);
351 /* Now scan FinderInfo if its all 0 */
355 /* FinderInfo contains some non 0 bytes -> include "com.apple.FinderInfo" */
356 strcpy(attrnamebuf, ea_finderinfo);
357 attrbuflen += strlen(ea_finderinfo) + 1;
358 LOG(log_debug7, logtype_afpd, "afp_listextattr(%s): sending com.apple.FinderInfo", uname);
363 /* Now check for Ressource fork and add virtual EA "com.apple.ResourceFork" if size > 0 */
364 LOG(log_debug7, logtype_afpd, "afp_listextattr(%s): Ressourcefork size: %u", uname, adp->ad_eid[ADEID_RFORK].ade_len);
365 if (adp->ad_eid[ADEID_RFORK].ade_len > 0) {
366 LOG(log_debug7, logtype_afpd, "afp_listextattr(%s): sending com.apple.RessourceFork.", uname);
367 strcpy(attrnamebuf + attrbuflen, ea_resourcefork);
368 attrbuflen += strlen(ea_resourcefork) + 1;
371 ret = list_extattr(obj, attrnamebuf, &attrbuflen, uname, oflag);
374 /* its a symlink and client requested O_NOFOLLOW */
375 LOG(log_debug, logtype_afpd, "afp_listextattr(%s): encountered symlink with kXAttrNoFollow", uname);
388 /* Start building reply packet */
389 bitmap = htons(bitmap);
390 memcpy( rbuf, &bitmap, 2);
394 attrbuflen = htonl(attrbuflen);
395 memcpy( rbuf, &attrbuflen, 4);
399 /* Only copy buffer if the client asked for it (2nd request, maxreply>0)
400 and we didnt have an error (buf_valid) */
401 if (maxreply && buf_valid) {
402 memcpy( rbuf, attrnamebuf, attrbuflen);
403 *rbuflen += attrbuflen;
413 ad_close_metadata( adp);
415 LOG(log_debug9, logtype_afpd, "afp_listextattr: END");
419 int afp_getextattr(AFPObj *obj _U_, char *ibuf, int ibuflen _U_, char *rbuf, int *rbuflen)
422 uint16_t vid, bitmap;
423 uint32_t did, maxreply, attrnamelen;
424 char attrmname[256], attruname[256];
431 LOG(log_debug9, logtype_afpd, "afp_getextattr: BEGIN");
437 memcpy( &vid, ibuf, 2);
439 if (NULL == ( vol = getvolbyvid( vid )) ) {
440 LOG(log_error, logtype_afpd, "afp_getextattr: getvolbyvid error: %s", strerror(errno));
441 return AFPERR_ACCESS;
444 memcpy( &did, ibuf, 4);
446 if (NULL == ( dir = dirlookup( vol, did )) ) {
447 LOG(log_error, logtype_afpd, "afp_getextattr: dirlookup error: %s", strerror(errno));
451 memcpy( &bitmap, ibuf, 2);
452 bitmap = ntohs( bitmap );
454 if (bitmap & kXAttrNoFollow)
455 oflag = AT_SYMLINK_NOFOLLOW;
457 /* Skip Offset and ReqCount */
461 memcpy(&maxreply, ibuf, 4);
462 maxreply = ntohl(maxreply);
466 if (NULL == ( s_path = cname( vol, dir, &ibuf )) ) {
467 LOG(log_error, logtype_afpd, "afp_getextattr: cname error: %s", strerror(errno));
471 if ((unsigned long)ibuf & 1)
474 /* get length of EA name */
475 memcpy(&attrnamelen, ibuf, 2);
476 attrnamelen = ntohs(attrnamelen);
478 if (attrnamelen > 255)
479 /* dont fool with us */
482 /* we must copy the name as its not 0-terminated and I DONT WANT TO WRITE to ibuf */
483 strncpy(attrmname, ibuf, attrnamelen);
484 attrmname[attrnamelen] = 0;
486 LOG(log_debug, logtype_afpd, "afp_getextattr(%s): EA: %s", s_path->u_name, attrmname);
488 /* Convert EA name in utf8 to unix charset */
489 if ( 0 >= ( attrnamelen = convert_string(CH_UTF8_MAC, obj->options.unixcharset,attrmname, attrnamelen, attruname, 255)) )
492 if (attrnamelen == 255)
493 /* convert_string didn't 0-terminate */
496 /* write bitmap now */
497 bitmap = htons(bitmap);
498 memcpy(rbuf, &bitmap, 2);
504 if its 0 we must return the size of the requested attribute,
505 if its non 0 we must return the attribute.
508 ret = getextattr_size(rbuf, rbuflen, s_path->u_name, oflag, attruname);
510 ret = getextattr_content(rbuf, rbuflen, s_path->u_name, oflag, attruname, maxreply);
513 LOG(log_debug9, logtype_afpd, "afp_getextattr: END");
519 int afp_setextattr(AFPObj *obj _U_, char *ibuf, int ibuflen _U_, char *rbuf, int *rbuflen)
521 int len, oflag = O_CREAT | O_WRONLY, attrdirfd;
522 uint16_t vid, bitmap;
523 uint32_t did, attrnamelen, attrsize;
524 char attrmname[256], attruname[256];
530 LOG(log_debug9, logtype_afpd, "afp_setextattr: BEGIN");
536 memcpy( &vid, ibuf, 2);
538 if (NULL == ( vol = getvolbyvid( vid )) ) {
539 LOG(log_error, logtype_afpd, "afp_setextattr: getvolbyvid error: %s", strerror(errno));
540 return AFPERR_ACCESS;
543 memcpy( &did, ibuf, 4);
545 if (NULL == ( dir = dirlookup( vol, did )) ) {
546 LOG(log_error, logtype_afpd, "afp_setextattr: dirlookup error: %s", strerror(errno));
550 memcpy( &bitmap, ibuf, 2);
551 bitmap = ntohs( bitmap );
553 if (bitmap & kXAttrNoFollow)
554 oflag |= AT_SYMLINK_NOFOLLOW;
555 if (bitmap & kXAttrCreate)
557 else if (bitmap & kXAttrReplace)
564 if (NULL == ( s_path = cname( vol, dir, &ibuf )) ) {
565 LOG(log_error, logtype_afpd, "afp_setextattr: cname error: %s", strerror(errno));
569 if ((unsigned long)ibuf & 1)
572 /* get length of EA name */
573 memcpy(&attrnamelen, ibuf, 2);
574 attrnamelen = ntohs(attrnamelen);
576 if (attrnamelen > 255)
579 /* we must copy the name as its not 0-terminated and we cant write to ibuf */
580 strncpy(attrmname, ibuf, attrnamelen);
581 attrmname[attrnamelen] = 0;
584 /* Convert EA name in utf8 to unix charset */
585 if ( 0 >= ( attrnamelen = convert_string(CH_UTF8_MAC, obj->options.unixcharset,attrmname, attrnamelen, attruname, 255)) )
588 if (attrnamelen == 255)
589 /* convert_string didn't 0-terminate */
593 memcpy(&attrsize, ibuf, 4);
594 attrsize = ntohl(attrsize);
596 if (attrsize > MAX_EA_SIZE)
597 /* we arbitrarily make this fatal */
600 LOG(log_debug, logtype_afpd, "afp_setextattr(%s): EA: %s, size: %u", s_path->u_name, attrmname, attrsize);
602 if ( -1 == (attrdirfd = attropen(s_path->u_name, attruname, oflag, 0666))) {
603 if (errno == ELOOP) {
604 /* its a symlink and client requested O_NOFOLLOW */
605 LOG(log_debug, logtype_afpd, "afp_setextattr(%s): encountered symlink with kXAttrNoFollow", s_path->u_name);
608 LOG(log_error, logtype_afpd, "afp_setextattr(%s): attropen error: %s", s_path->u_name, strerror(errno));
613 len = write(attrdirfd, ibuf, attrsize);
615 LOG(log_error, logtype_afpd, "afp_setextattr(%s): read error: %s", attruname, strerror(errno));
625 LOG(log_debug9, logtype_afpd, "afp_setextattr: END");
631 int afp_remextattr(AFPObj *obj _U_, char *ibuf, int ibuflen _U_, char *rbuf, int *rbuflen)
633 int oflag = O_RDONLY, attrdirfd;
634 uint16_t vid, bitmap;
635 uint32_t did, attrnamelen;
636 char attrmname[256], attruname[256];
642 LOG(log_debug9, logtype_afpd, "afp_remextattr: BEGIN");
648 memcpy( &vid, ibuf, 2);
650 if (NULL == ( vol = getvolbyvid( vid )) ) {
651 LOG(log_error, logtype_afpd, "afp_remextattr: getvolbyvid error: %s", strerror(errno));
652 return AFPERR_ACCESS;
655 memcpy( &did, ibuf, 4);
657 if (NULL == ( dir = dirlookup( vol, did )) ) {
658 LOG(log_error, logtype_afpd, "afp_remextattr: dirlookup error: %s", strerror(errno));
662 memcpy( &bitmap, ibuf, 2);
663 bitmap = ntohs( bitmap );
665 if (bitmap & kXAttrNoFollow)
666 oflag |= AT_SYMLINK_NOFOLLOW;
669 if (NULL == ( s_path = cname( vol, dir, &ibuf )) ) {
670 LOG(log_error, logtype_afpd, "afp_setextattr: cname error: %s", strerror(errno));
674 if ((unsigned long)ibuf & 1)
677 /* get length of EA name */
678 memcpy(&attrnamelen, ibuf, 2);
679 attrnamelen = ntohs(attrnamelen);
681 if (attrnamelen > 255)
684 /* we must copy the name as its not 0-terminated and we cant write to ibuf */
685 strncpy(attrmname, ibuf, attrnamelen);
686 attrmname[attrnamelen] = 0;
689 /* Convert EA name in utf8 to unix charset */
690 if ( 0 >= ( attrnamelen = convert_string(CH_UTF8_MAC, obj->options.unixcharset,attrmname, attrnamelen, attruname, 255)) )
693 if (attrnamelen == 255)
694 /* convert_string didn't 0-terminate */
697 LOG(log_debug, logtype_afpd, "afp_remextattr(%s): EA: %s", s_path->u_name, attrmname);
699 if ( -1 == (attrdirfd = attropen(s_path->u_name, ".", oflag))) {
702 /* its a symlink and client requested O_NOFOLLOW */
703 LOG(log_debug, logtype_afpd, "afp_remextattr(%s): encountered symlink with kXAttrNoFollow", s_path->u_name);
706 LOG(log_debug, logtype_afpd, "afp_remextattr(%s): unlinkat error: %s", s_path->u_name, strerror(errno));
707 return AFPERR_ACCESS;
709 LOG(log_error, logtype_afpd, "afp_remextattr(%s): attropen error: %s", s_path->u_name, strerror(errno));
714 if ( -1 == (unlinkat(attrdirfd, attruname, 0)) ) {
715 if (errno == EACCES) {
716 LOG(log_debug, logtype_afpd, "afp_remextattr(%s): unlinkat error: %s", s_path->u_name, strerror(errno));
717 return AFPERR_ACCESS;
719 LOG(log_error, logtype_afpd, "afp_remextattr(%s): unlinkat error: %s", s_path->u_name, strerror(errno));
724 LOG(log_debug9, logtype_afpd, "afp_remextattr: END");
732 #endif /* HAVE_EXT_ATTRS */