xref: /reactos/base/services/nfsd/ea.c (revision c2c66aff)
1 /* NFSv4.1 client for Windows
2  * Copyright � 2012 The Regents of the University of Michigan
3  *
4  * Olga Kornievskaia <aglo@umich.edu>
5  * Casey Bodley <cbodley@umich.edu>
6  *
7  * This library is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU Lesser General Public License as published by
9  * the Free Software Foundation; either version 2.1 of the License, or (at
10  * your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful, but
13  * without any warranty; without even the implied warranty of merchantability
14  * or fitness for a particular purpose.  See the GNU Lesser General Public
15  * License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public License
18  * along with this library; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20  */
21 
22 #include <windows.h>
23 #include <stdio.h>
24 #include <strsafe.h>
25 
26 #include "from_kernel.h"
27 #include "nfs41_ops.h"
28 #include "delegation.h"
29 #include "upcall.h"
30 #include "daemon_debug.h"
31 
32 
33 #define EALVL 2 /* dprintf level for extended attribute logging */
34 
35 
set_ea_value(IN nfs41_session * session,IN nfs41_path_fh * parent,IN state_owner4 * owner,IN PFILE_FULL_EA_INFORMATION ea)36 static int set_ea_value(
37     IN nfs41_session *session,
38     IN nfs41_path_fh *parent,
39     IN state_owner4 *owner,
40     IN PFILE_FULL_EA_INFORMATION ea)
41 {
42     nfs41_path_fh file = { 0 };
43     nfs41_file_info createattrs;
44     open_claim4 claim;
45     stateid_arg stateid;
46     open_delegation4 delegation = { 0 };
47     nfs41_write_verf verf;
48     uint32_t bytes_written;
49     int status;
50 
51     /* don't allow values larger than NFS4_EASIZE */
52     if (ea->EaValueLength > NFS4_EASIZE) {
53         eprintf("trying to write extended attribute value of size %d, "
54             "max allowed %d\n", ea->EaValueLength, NFS4_EASIZE);
55         status = NFS4ERR_FBIG;
56         goto out;
57     }
58     /* remove the file on empty value */
59     if (ea->EaValueLength == 0) {
60         nfs41_component name;
61         name.name = ea->EaName;
62         name.len = ea->EaNameLength;
63         nfs41_remove(session, parent, &name, 0);
64         status = NFS4_OK;
65         goto out;
66     }
67 
68     claim.claim = CLAIM_NULL;
69     claim.u.null.filename = &file.name;
70     file.name.name = ea->EaName;
71     file.name.len = ea->EaNameLength;
72 
73     createattrs.attrmask.count = 2;
74     createattrs.attrmask.arr[0] = FATTR4_WORD0_SIZE;
75     createattrs.attrmask.arr[1] = FATTR4_WORD1_MODE;
76     createattrs.size = 0;
77     createattrs.mode = 0664;
78 
79     status = nfs41_open(session, parent, &file, owner, &claim,
80         OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
81         OPEN4_SHARE_DENY_BOTH, OPEN4_CREATE, UNCHECKED4,
82         &createattrs, TRUE, &stateid.stateid, &delegation, NULL);
83     if (status) {
84         eprintf("nfs41_open() failed with %s\n", nfs_error_string(status));
85         goto out;
86     }
87 
88     status = nfs41_write(session, &file, &stateid,
89         (unsigned char*)ea->EaName + ea->EaNameLength + 1,
90         ea->EaValueLength, 0, FILE_SYNC4, &bytes_written,
91         &verf, NULL);
92     if (status) {
93         eprintf("nfs41_write() failed with %s\n", nfs_error_string(status));
94         goto out_close;
95     }
96 
97 out_close:
98     nfs41_close(session, &file, &stateid);
99 out:
100     return status;
101 }
102 
is_cygwin_ea(PFILE_FULL_EA_INFORMATION ea)103 static int is_cygwin_ea(
104     PFILE_FULL_EA_INFORMATION ea)
105 {
106     return (strncmp("NfsV3Attributes", ea->EaName, ea->EaNameLength) == 0
107             && sizeof("NfsV3Attributes")-1 == ea->EaNameLength)
108         || (strncmp("NfsActOnLink", ea->EaName, ea->EaNameLength) == 0
109             && sizeof("NfsActOnLink")-1 == ea->EaNameLength)
110         || (strncmp("NfsSymlinkTargetName", ea->EaName, ea->EaNameLength) == 0
111             && sizeof("NfsSymlinkTargetName")-1 == ea->EaNameLength);
112 }
113 
114 #define NEXT_ENTRY(ea) ((PBYTE)(ea) + (ea)->NextEntryOffset)
115 
nfs41_ea_set(IN nfs41_open_state * state,IN PFILE_FULL_EA_INFORMATION ea)116 int nfs41_ea_set(
117     IN nfs41_open_state *state,
118     IN PFILE_FULL_EA_INFORMATION ea)
119 {
120     nfs41_path_fh attrdir = { 0 };
121     int status;
122 
123     status = nfs41_rpc_openattr(state->session, &state->file, TRUE, &attrdir.fh);
124     if (status) {
125         eprintf("nfs41_rpc_openattr() failed with error %s\n",
126             nfs_error_string(status));
127         goto out;
128     }
129 
130     while (status == NFS4_OK) {
131         if (!is_cygwin_ea(ea))
132             status = set_ea_value(state->session, &attrdir, &state->owner, ea);
133 
134         if (ea->NextEntryOffset == 0)
135             break;
136         ea = (PFILE_FULL_EA_INFORMATION)NEXT_ENTRY(ea);
137     }
138 out:
139     return status;
140 }
141 
142 
143 /* NFS41_EA_SET */
parse_setexattr(unsigned char * buffer,uint32_t length,nfs41_upcall * upcall)144 static int parse_setexattr(unsigned char *buffer, uint32_t length, nfs41_upcall *upcall)
145 {
146     int status;
147     setexattr_upcall_args *args = &upcall->args.setexattr;
148 
149     status = get_name(&buffer, &length, &args->path);
150     if (status) goto out;
151     status = safe_read(&buffer, &length, &args->mode, sizeof(args->mode));
152     if (status) goto out;
153     status = safe_read(&buffer, &length, &args->buf_len, sizeof(args->buf_len));
154     if (status) goto out;
155     args->buf = buffer;
156 
157     dprintf(1, "parsing NFS41_EA_SET: mode=%o\n", args->mode);
158 out:
159     return status;
160 }
161 
handle_setexattr(nfs41_upcall * upcall)162 static int handle_setexattr(nfs41_upcall *upcall)
163 {
164     int status;
165     setexattr_upcall_args *args = &upcall->args.setexattr;
166     nfs41_open_state *state = upcall->state_ref;
167     PFILE_FULL_EA_INFORMATION ea =
168         (PFILE_FULL_EA_INFORMATION)args->buf;
169 
170     /* break read delegations before SETATTR */
171     nfs41_delegation_return(state->session, &state->file,
172         OPEN_DELEGATE_READ, FALSE);
173 
174     if (strncmp("NfsV3Attributes", ea->EaName, ea->EaNameLength) == 0
175             && sizeof("NfsV3Attributes")-1 == ea->EaNameLength) {
176         nfs41_file_info info;
177         stateid_arg stateid;
178 
179         nfs41_open_stateid_arg(state, &stateid);
180 
181         info.mode = args->mode;
182         info.attrmask.arr[0] = 0;
183         info.attrmask.arr[1] = FATTR4_WORD1_MODE;
184         info.attrmask.count = 2;
185 
186         status = nfs41_setattr(state->session, &state->file, &stateid, &info);
187         if (status) {
188             dprintf(1, "nfs41_setattr() failed with error %s.\n",
189                 nfs_error_string(status));
190             goto out;
191         }
192 
193         args->ctime = info.change;
194         goto out;
195     }
196 
197     status = nfs41_ea_set(state, ea);
198 out:
199     return nfs_to_windows_error(status, ERROR_NOT_SUPPORTED);
200 }
201 
marshall_setexattr(unsigned char * buffer,uint32_t * length,nfs41_upcall * upcall)202 static int marshall_setexattr(unsigned char *buffer, uint32_t *length, nfs41_upcall *upcall)
203 {
204     setexattr_upcall_args *args = &upcall->args.setexattr;
205     return safe_write(&buffer, length, &args->ctime, sizeof(args->ctime));
206 }
207 
208 
209 /* NFS41_EA_GET */
parse_getexattr(unsigned char * buffer,uint32_t length,nfs41_upcall * upcall)210 static int parse_getexattr(unsigned char *buffer, uint32_t length, nfs41_upcall *upcall)
211 {
212     int status;
213     getexattr_upcall_args *args = &upcall->args.getexattr;
214 
215     status = get_name(&buffer, &length, &args->path);
216     if (status) goto out;
217     status = safe_read(&buffer, &length, &args->eaindex, sizeof(args->eaindex));
218     if (status) goto out;
219     status = safe_read(&buffer, &length, &args->restart, sizeof(args->restart));
220     if (status) goto out;
221     status = safe_read(&buffer, &length, &args->single, sizeof(args->single));
222     if (status) goto out;
223     status = safe_read(&buffer, &length, &args->buf_len, sizeof(args->buf_len));
224     if (status) goto out;
225     status = safe_read(&buffer, &length, &args->ealist_len, sizeof(args->ealist_len));
226     if (status) goto out;
227     args->ealist = args->ealist_len ? buffer : NULL;
228 
229     dprintf(1, "parsing NFS41_EA_GET: buf_len=%d Index %d Restart %d "
230         "Single %d\n", args->buf_len,args->eaindex, args->restart, args->single);
231 out:
232     return status;
233 }
234 
235 #define READDIR_LEN_INITIAL 8192
236 #define READDIR_LEN_MIN 2048
237 
238 /* call readdir repeatedly to get a complete list of entries */
read_entire_dir(IN nfs41_session * session,IN nfs41_path_fh * eadir,OUT unsigned char ** buffer_out,OUT uint32_t * length_out)239 static int read_entire_dir(
240     IN nfs41_session *session,
241     IN nfs41_path_fh *eadir,
242     OUT unsigned char **buffer_out,
243     OUT uint32_t *length_out)
244 {
245     nfs41_readdir_cookie cookie = { 0 };
246     bitmap4 attr_request;
247     nfs41_readdir_entry *last_entry;
248     unsigned char *buffer;
249     uint32_t buffer_len, len, total_len;
250     bool_t eof;
251     int status = NO_ERROR;
252 
253     attr_request.count = 0; /* don't request attributes */
254 
255     /* allocate the buffer for readdir entries */
256     buffer_len = READDIR_LEN_INITIAL;
257     buffer = calloc(1, buffer_len);
258     if (buffer == NULL) {
259         status = GetLastError();
260         goto out;
261     }
262 
263     last_entry = NULL;
264     total_len = 0;
265     eof = FALSE;
266 
267     while (!eof) {
268         len = buffer_len - total_len;
269         if (len < READDIR_LEN_MIN) {
270             const ptrdiff_t diff = (unsigned char*)last_entry - buffer;
271             /* realloc the buffer to fit more entries */
272             unsigned char *tmp = realloc(buffer, buffer_len * 2);
273             if (tmp == NULL) {
274                 status = GetLastError();
275                 goto out_free;
276             }
277 
278             if (last_entry) /* fix last_entry pointer */
279                 last_entry = (nfs41_readdir_entry*)(tmp + diff);
280             buffer = tmp;
281             buffer_len *= 2;
282             len = buffer_len - total_len;
283         }
284 
285         /* fetch the next group of entries */
286         status = nfs41_readdir(session, eadir, &attr_request,
287             &cookie, buffer + total_len, &len, &eof);
288         if (status)
289             goto out_free;
290 
291         if (last_entry == NULL) {
292             /* initialize last_entry to the front of the list */
293             last_entry = (nfs41_readdir_entry*)(buffer + total_len);
294         } else if (len) {
295             /* link the previous list to the new one */
296             last_entry->next_entry_offset = (uint32_t)FIELD_OFFSET(
297                 nfs41_readdir_entry, name) + last_entry->name_len;
298         }
299 
300         /* find the new last entry */
301         while (last_entry->next_entry_offset) {
302             last_entry = (nfs41_readdir_entry*)((char*)last_entry +
303                 last_entry->next_entry_offset);
304         }
305 
306         cookie.cookie = last_entry->cookie;
307         total_len += len;
308     }
309 
310     *buffer_out = buffer;
311     *length_out = total_len;
312 out:
313     return status;
314 
315 out_free:
316     free(buffer);
317     goto out;
318 }
319 
320 #define ALIGNED_EASIZE(len) (align4(sizeof(FILE_GET_EA_INFORMATION) + len))
321 
calculate_ea_list_length(IN const unsigned char * position,IN uint32_t remaining)322 static uint32_t calculate_ea_list_length(
323     IN const unsigned char *position,
324     IN uint32_t remaining)
325 {
326     const nfs41_readdir_entry *entry;
327     uint32_t length = 0;
328 
329     while (remaining) {
330         entry = (const nfs41_readdir_entry*)position;
331         length += ALIGNED_EASIZE(entry->name_len);
332 
333         if (!entry->next_entry_offset)
334             break;
335 
336         position += entry->next_entry_offset;
337         remaining -= entry->next_entry_offset;
338     }
339     return length;
340 }
341 
populate_ea_list(IN const unsigned char * position,OUT PFILE_GET_EA_INFORMATION ea_list)342 static void populate_ea_list(
343     IN const unsigned char *position,
344     OUT PFILE_GET_EA_INFORMATION ea_list)
345 {
346     const nfs41_readdir_entry *entry;
347     PFILE_GET_EA_INFORMATION ea = ea_list, prev = NULL;
348 
349     for (;;) {
350         entry = (const nfs41_readdir_entry*)position;
351         StringCchCopyA(ea->EaName, entry->name_len, entry->name);
352         ea->EaNameLength = (UCHAR)entry->name_len - 1;
353 
354         if (!entry->next_entry_offset) {
355             ea->NextEntryOffset = 0;
356             break;
357         }
358 
359         prev = ea;
360         ea->NextEntryOffset = ALIGNED_EASIZE(ea->EaNameLength);
361         ea = (PFILE_GET_EA_INFORMATION)NEXT_ENTRY(ea);
362         position += entry->next_entry_offset;
363     }
364 }
365 
get_ea_list(IN OUT nfs41_open_state * state,IN nfs41_path_fh * eadir,OUT PFILE_GET_EA_INFORMATION * ealist_out,OUT uint32_t * eaindex_out)366 static int get_ea_list(
367     IN OUT nfs41_open_state *state,
368     IN nfs41_path_fh *eadir,
369     OUT PFILE_GET_EA_INFORMATION *ealist_out,
370     OUT uint32_t *eaindex_out)
371 {
372     unsigned char *entry_list;
373     PFILE_GET_EA_INFORMATION ea_list;
374     uint32_t entry_len, ea_size;
375     int status = NO_ERROR;
376 
377     EnterCriticalSection(&state->ea.lock);
378 
379     if (state->ea.list != INVALID_HANDLE_VALUE) {
380         /* use cached ea names */
381         *ealist_out = state->ea.list;
382         *eaindex_out = state->ea.index;
383         goto out;
384     }
385 
386     /* read the entire directory into a nfs41_readdir_entry buffer */
387     status = read_entire_dir(state->session, eadir, &entry_list, &entry_len);
388     if (status)
389         goto out;
390 
391     ea_size = calculate_ea_list_length(entry_list, entry_len);
392     if (ea_size == 0) {
393         *ealist_out = state->ea.list = NULL;
394         goto out_free;
395     }
396     ea_list = calloc(1, ea_size);
397     if (ea_list == NULL) {
398         status = GetLastError();
399         goto out_free;
400     }
401 
402     populate_ea_list(entry_list, ea_list);
403 
404     *ealist_out = state->ea.list = ea_list;
405     *eaindex_out = state->ea.index;
406 out_free:
407     free(entry_list); /* allocated by read_entire_dir() */
408 out:
409     LeaveCriticalSection(&state->ea.lock);
410     return status;
411 }
412 
get_ea_value(IN nfs41_session * session,IN nfs41_path_fh * parent,IN state_owner4 * owner,OUT PFILE_FULL_EA_INFORMATION ea,IN uint32_t length,OUT uint32_t * needed)413 static int get_ea_value(
414     IN nfs41_session *session,
415     IN nfs41_path_fh *parent,
416     IN state_owner4 *owner,
417     OUT PFILE_FULL_EA_INFORMATION ea,
418     IN uint32_t length,
419     OUT uint32_t *needed)
420 {
421     nfs41_path_fh file = { 0 };
422     open_claim4 claim;
423     stateid_arg stateid;
424     open_delegation4 delegation = { 0 };
425     nfs41_file_info info;
426     unsigned char *buffer;
427     uint32_t diff, bytes_read;
428     bool_t eof;
429     int status;
430 
431     if (parent->fh.len == 0) /* no named attribute directory */
432         goto out_empty;
433 
434     claim.claim = CLAIM_NULL;
435     claim.u.null.filename = &file.name;
436     file.name.name = ea->EaName;
437     file.name.len = ea->EaNameLength;
438 
439     status = nfs41_open(session, parent, &file, owner, &claim,
440         OPEN4_SHARE_ACCESS_READ | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
441         OPEN4_SHARE_DENY_WRITE, OPEN4_NOCREATE, UNCHECKED4, NULL, TRUE,
442         &stateid.stateid, &delegation, &info);
443     if (status) {
444         eprintf("nfs41_open() failed with %s\n", nfs_error_string(status));
445         if (status == NFS4ERR_NOENT)
446             goto out_empty;
447         goto out;
448     }
449 
450     if (info.size > NFS4_EASIZE) {
451         status = NFS4ERR_FBIG;
452         eprintf("EA value for '%s' longer than maximum %u "
453             "(%llu bytes), returning %s\n", ea->EaName, NFS4_EASIZE,
454             info.size, nfs_error_string(status));
455         goto out_close;
456     }
457 
458     buffer = (unsigned char*)ea->EaName + ea->EaNameLength + 1;
459     diff = (uint32_t)(buffer - (unsigned char*)ea);
460 
461     /* make sure we have room for the value */
462     if (length < diff + info.size) {
463         *needed = (uint32_t)(sizeof(FILE_FULL_EA_INFORMATION) +
464             ea->EaNameLength + info.size);
465         status = NFS4ERR_TOOSMALL;
466         goto out_close;
467     }
468 
469     /* read directly into the ea buffer */
470     status = nfs41_read(session, &file, &stateid,
471         0, length - diff, buffer, &bytes_read, &eof);
472     if (status) {
473         eprintf("nfs41_read() failed with %s\n", nfs_error_string(status));
474         goto out_close;
475     }
476     if (!eof) {
477         *needed = (uint32_t)(sizeof(FILE_FULL_EA_INFORMATION) +
478             ea->EaNameLength + NFS4_EASIZE);
479         status = NFS4ERR_TOOSMALL;
480         goto out_close;
481     }
482 
483     ea->EaValueLength = (USHORT)bytes_read;
484 
485 out_close:
486     nfs41_close(session, &file, &stateid);
487 out:
488     return status;
489 
490 out_empty: /* return an empty value */
491     ea->EaValueLength = 0;
492     status = NFS4_OK;
493     goto out;
494 }
495 
empty_ea_error(IN uint32_t index,IN BOOLEAN restart)496 static int empty_ea_error(
497     IN uint32_t index,
498     IN BOOLEAN restart)
499 {
500     /* choose an error value depending on the arguments */
501     if (index)
502         return ERROR_INVALID_EA_HANDLE;
503 
504     if (!restart)
505         return ERROR_NO_MORE_FILES;  /* -> STATUS_NO_MORE_EAS */
506 
507     return ERROR_FILE_NOT_FOUND; /* -> STATUS_NO_EAS_ON_FILE */
508 }
509 
overflow_error(IN OUT getexattr_upcall_args * args,IN PFILE_FULL_EA_INFORMATION prev,IN uint32_t needed)510 static int overflow_error(
511     IN OUT getexattr_upcall_args *args,
512     IN PFILE_FULL_EA_INFORMATION prev,
513     IN uint32_t needed)
514 {
515     if (prev) {
516         /* unlink the overflowing entry, but copy the entries that fit */
517         prev->NextEntryOffset = 0;
518         args->overflow = ERROR_BUFFER_OVERFLOW;
519     } else {
520         /* no entries fit; return only the length needed */
521         args->buf_len = needed;
522         args->overflow = ERROR_INSUFFICIENT_BUFFER;
523     }
524 
525     /* in either case, the upcall must return NO_ERROR so we
526      * can copy this information down to the driver */
527     return NO_ERROR;
528 }
529 
handle_getexattr(nfs41_upcall * upcall)530 static int handle_getexattr(nfs41_upcall *upcall)
531 {
532     getexattr_upcall_args *args = &upcall->args.getexattr;
533     PFILE_GET_EA_INFORMATION query = (PFILE_GET_EA_INFORMATION)args->ealist;
534     PFILE_FULL_EA_INFORMATION ea, prev = NULL;
535     nfs41_open_state *state = upcall->state_ref;
536     nfs41_path_fh parent = { 0 };
537     uint32_t remaining, needed, index = 0;
538     int status;
539 
540     status = nfs41_rpc_openattr(state->session, &state->file, FALSE, &parent.fh);
541     if (status == NFS4ERR_NOENT) { /* no named attribute directory */
542         dprintf(EALVL, "no named attribute directory for '%s'\n", args->path);
543         if (query == NULL) {
544             status = empty_ea_error(args->eaindex, args->restart);
545             goto out;
546         }
547     } else if (status) {
548         eprintf("nfs41_rpc_openattr() failed with %s\n",
549             nfs_error_string(status));
550         status = nfs_to_windows_error(status, ERROR_EAS_NOT_SUPPORTED);
551         goto out;
552     }
553 
554     if (query == NULL) {
555         /* if no names are queried, use READDIR to list them all */
556         uint32_t i;
557         status = get_ea_list(state, &parent, &query, &index);
558         if (status)
559             goto out;
560 
561         if (query == NULL) { /* the file has no EAs */
562             dprintf(EALVL, "empty named attribute directory for '%s'\n",
563                 args->path);
564             status = empty_ea_error(args->eaindex, args->restart);
565             goto out;
566         }
567 
568         if (args->eaindex)
569             index = args->eaindex - 1; /* convert to zero-based index */
570         else if (args->restart)
571             index = 0;
572 
573         /* advance the list to the specified index */
574         for (i = 0; i < index; i++) {
575             if (query->NextEntryOffset == 0) {
576                 if (args->eaindex)
577                     status = ERROR_INVALID_EA_HANDLE;
578                 else
579                     status = ERROR_NO_MORE_FILES; /* STATUS_NO_MORE_EAS */
580                 goto out;
581             }
582             query = (PFILE_GET_EA_INFORMATION)NEXT_ENTRY(query);
583         }
584     }
585 
586     /* returned ea information can't exceed the downcall buffer size */
587     if (args->buf_len > UPCALL_BUF_SIZE - 2 * sizeof(uint32_t))
588         args->buf_len = UPCALL_BUF_SIZE - 2 * sizeof(uint32_t);
589 
590     args->buf = malloc(args->buf_len);
591     if (args->buf == NULL) {
592         status = GetLastError();
593         goto out;
594     }
595 
596     ea = (PFILE_FULL_EA_INFORMATION)args->buf;
597     remaining = args->buf_len;
598 
599     for (;;) {
600         /* make sure we have room for at least the name */
601         needed = sizeof(FILE_FULL_EA_INFORMATION) + query->EaNameLength;
602         if (needed > remaining) {
603             status = overflow_error(args, prev, needed + NFS4_EASIZE);
604             goto out;
605         }
606 
607         ea->EaNameLength = query->EaNameLength;
608         StringCchCopy(ea->EaName, ea->EaNameLength + 1, query->EaName);
609         ea->Flags = 0;
610 
611         /* read the value from file */
612         status = get_ea_value(state->session, &parent,
613             &state->owner, ea, remaining, &needed);
614         if (status == NFS4ERR_TOOSMALL) {
615             status = overflow_error(args, prev, needed);
616             goto out;
617         }
618         if (status) {
619             status = nfs_to_windows_error(status, ERROR_EA_FILE_CORRUPT);
620             goto out_free;
621         }
622 
623         needed = align4(FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) +
624             ea->EaNameLength + 1 + ea->EaValueLength);
625 
626         if (remaining < needed) {
627             /* align4 may push NextEntryOffset past our buffer, but we
628              * were still able to fit the ea value.  set remaining = 0
629              * so we'll fail on the next ea (if any) */
630             remaining = 0;
631         } else
632             remaining -= needed;
633 
634         index++;
635         if (query->NextEntryOffset == 0 || args->single)
636             break;
637 
638         prev = ea;
639         ea->NextEntryOffset = needed;
640         ea = (PFILE_FULL_EA_INFORMATION)NEXT_ENTRY(ea);
641         query = (PFILE_GET_EA_INFORMATION)NEXT_ENTRY(query);
642     }
643 
644     ea->NextEntryOffset = 0;
645     args->buf_len -= remaining;
646 out:
647     if (args->ealist == NULL) { /* update the ea index */
648         EnterCriticalSection(&state->ea.lock);
649         state->ea.index = index;
650         if (status == NO_ERROR && !args->overflow && !args->single) {
651             /* listing was completed, free the cache */
652             free(state->ea.list);
653             state->ea.list = INVALID_HANDLE_VALUE;
654         }
655         LeaveCriticalSection(&state->ea.lock);
656     }
657     return status;
658 
659 out_free:
660     free(args->buf);
661     goto out;
662 }
663 
marshall_getexattr(unsigned char * buffer,uint32_t * length,nfs41_upcall * upcall)664 static int marshall_getexattr(unsigned char *buffer, uint32_t *length, nfs41_upcall *upcall)
665 {
666     int status = NO_ERROR;
667     getexattr_upcall_args *args = &upcall->args.getexattr;
668 
669     status = safe_write(&buffer, length, &args->overflow, sizeof(args->overflow));
670     if (status) goto out;
671     status = safe_write(&buffer, length, &args->buf_len, sizeof(args->buf_len));
672     if (status) goto out;
673     if (args->overflow == ERROR_INSUFFICIENT_BUFFER)
674         goto out;
675     status = safe_write(&buffer, length, args->buf, args->buf_len);
676     if (status) goto out;
677 out:
678     free(args->buf);
679     return status;
680 }
681 
682 
683 const nfs41_upcall_op nfs41_op_setexattr = {
684     parse_setexattr,
685     handle_setexattr,
686     marshall_setexattr
687 };
688 
689 const nfs41_upcall_op nfs41_op_getexattr = {
690     parse_getexattr,
691     handle_getexattr,
692     marshall_getexattr
693 };
694