pvfs_rename: implement RAW_RENAME_NTTRANS as noop as w2k3
[samba.git] / source4 / ntvfs / posix / pvfs_rename.c
1 /* 
2    Unix SMB/CIFS implementation.
3
4    POSIX NTVFS backend - rename
5
6    Copyright (C) Andrew Tridgell 2004
7
8    This program is free software; you can redistribute it and/or modify
9    it under the terms of the GNU General Public License as published by
10    the Free Software Foundation; either version 3 of the License, or
11    (at your option) any later version.
12    
13    This program is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16    GNU General Public License for more details.
17    
18    You should have received a copy of the GNU General Public License
19    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 */
21
22 #include "includes.h"
23 #include "vfs_posix.h"
24 #include "librpc/gen_ndr/security.h"
25 #include "param/param.h"
26
27
28 /*
29   do a file rename, and send any notify triggers
30 */
31 NTSTATUS pvfs_do_rename(struct pvfs_state *pvfs,
32                         struct odb_lock *lck,
33                         const struct pvfs_filename *name1,
34                         const char *name2)
35 {
36         const char *r1, *r2;
37         uint32_t mask;
38         NTSTATUS status;
39
40         if (rename(name1->full_name, name2) == -1) {
41                 return pvfs_map_errno(pvfs, errno);
42         }
43
44         status = odb_rename(lck, name2);
45         NT_STATUS_NOT_OK_RETURN(status);
46
47         if (name1->dos.attrib & FILE_ATTRIBUTE_DIRECTORY) {
48                 mask = FILE_NOTIFY_CHANGE_DIR_NAME;
49         } else {
50                 mask = FILE_NOTIFY_CHANGE_FILE_NAME;
51         }
52         /* 
53            renames to the same directory cause a OLD_NAME->NEW_NAME notify.
54            renames to a different directory are considered a remove/add 
55         */
56         r1 = strrchr_m(name1->full_name, '/');
57         r2 = strrchr_m(name2, '/');
58
59         if ((r1-name1->full_name) != (r2-name2) ||
60             strncmp(name1->full_name, name2, r1-name1->full_name) != 0) {
61                 notify_trigger(pvfs->notify_context, 
62                                NOTIFY_ACTION_REMOVED, 
63                                mask,
64                                name1->full_name);
65                 notify_trigger(pvfs->notify_context, 
66                                NOTIFY_ACTION_ADDED, 
67                                mask,
68                                name2);
69         } else {
70                 notify_trigger(pvfs->notify_context, 
71                                NOTIFY_ACTION_OLD_NAME, 
72                                mask,
73                                name1->full_name);
74                 notify_trigger(pvfs->notify_context, 
75                                NOTIFY_ACTION_NEW_NAME, 
76                                mask,
77                                name2);
78         }
79
80         /* this is a strange one. w2k3 gives an additional event for CHANGE_ATTRIBUTES
81            and CHANGE_CREATION on the new file when renaming files, but not 
82            directories */
83         if ((name1->dos.attrib & FILE_ATTRIBUTE_DIRECTORY) == 0) {
84                 notify_trigger(pvfs->notify_context, 
85                                NOTIFY_ACTION_MODIFIED, 
86                                FILE_NOTIFY_CHANGE_ATTRIBUTES|FILE_NOTIFY_CHANGE_CREATION,
87                                name2);
88         }
89         
90         return NT_STATUS_OK;
91 }
92
93
94 /*
95   resolve a wildcard rename pattern. This works on one component of the name
96 */
97 static const char *pvfs_resolve_wildcard_component(TALLOC_CTX *mem_ctx, 
98                                                    struct smb_iconv_convenience *iconv_convenience,
99                                                    const char *fname, 
100                                                    const char *pattern)
101 {
102         const char *p1, *p2;
103         char *dest, *d;
104
105         /* the length is bounded by the length of the two strings combined */
106         dest = talloc_size(mem_ctx, strlen(fname) + strlen(pattern) + 1);
107         if (dest == NULL) {
108                 return NULL;
109         }
110
111         p1 = fname;
112         p2 = pattern;
113         d = dest;
114
115         while (*p2) {
116                 codepoint_t c1, c2;
117                 size_t c_size1, c_size2;
118                 c1 = next_codepoint(iconv_convenience, p1, &c_size1);
119                 c2 = next_codepoint(iconv_convenience, p2, &c_size2);
120                 if (c2 == '?') {
121                         d += push_codepoint(iconv_convenience, d, c1);
122                 } else if (c2 == '*') {
123                         memcpy(d, p1, strlen(p1));
124                         d += strlen(p1);
125                         break;
126                 } else {
127                         d += push_codepoint(iconv_convenience, d, c2);
128                 }
129
130                 p1 += c_size1;
131                 p2 += c_size2;
132         }
133
134         *d = 0;
135
136         return dest;
137 }
138
139 /*
140   resolve a wildcard rename pattern.
141 */
142 static const char *pvfs_resolve_wildcard(TALLOC_CTX *mem_ctx, 
143                                          struct smb_iconv_convenience *iconv_convenience,
144                                          const char *fname, 
145                                          const char *pattern)
146 {
147         const char *base1, *base2;
148         const char *ext1, *ext2;
149         char *p;
150
151         /* break into base part plus extension */
152         p = strrchr_m(fname, '.');
153         if (p == NULL) {
154                 ext1 = "";
155                 base1 = fname;
156         } else {
157                 ext1 = talloc_strdup(mem_ctx, p+1);
158                 base1 = talloc_strndup(mem_ctx, fname, p-fname);
159         }
160         if (ext1 == NULL || base1 == NULL) {
161                 return NULL;
162         }
163
164         p = strrchr_m(pattern, '.');
165         if (p == NULL) {
166                 ext2 = "";
167                 base2 = fname;
168         } else {
169                 ext2 = talloc_strdup(mem_ctx, p+1);
170                 base2 = talloc_strndup(mem_ctx, pattern, p-pattern);
171         }
172         if (ext2 == NULL || base2 == NULL) {
173                 return NULL;
174         }
175
176         base1 = pvfs_resolve_wildcard_component(mem_ctx, iconv_convenience, base1, base2);
177         ext1 = pvfs_resolve_wildcard_component(mem_ctx, iconv_convenience, ext1, ext2);
178         if (base1 == NULL || ext1 == NULL) {
179                 return NULL;
180         }
181
182         if (*ext1 == 0) {
183                 return base1;
184         }
185
186         return talloc_asprintf(mem_ctx, "%s.%s", base1, ext1);
187 }
188
189 /*
190   retry an rename after a sharing violation
191 */
192 static void pvfs_retry_rename(struct pvfs_odb_retry *r,
193                               struct ntvfs_module_context *ntvfs,
194                               struct ntvfs_request *req,
195                               void *_io,
196                               void *private_data,
197                               enum pvfs_wait_notice reason)
198 {
199         union smb_rename *io = talloc_get_type(_io, union smb_rename);
200         NTSTATUS status = NT_STATUS_INTERNAL_ERROR;
201
202         talloc_free(r);
203
204         switch (reason) {
205         case PVFS_WAIT_CANCEL:
206 /*TODO*/
207                 status = NT_STATUS_CANCELLED;
208                 break;
209         case PVFS_WAIT_TIMEOUT:
210                 /* if it timed out, then give the failure
211                    immediately */
212 /*TODO*/
213                 status = NT_STATUS_SHARING_VIOLATION;
214                 break;
215         case PVFS_WAIT_EVENT:
216
217                 /* try the open again, which could trigger another retry setup
218                    if it wants to, so we have to unmark the async flag so we
219                    will know if it does a second async reply */
220                 req->async_states->state &= ~NTVFS_ASYNC_STATE_ASYNC;
221
222                 status = pvfs_rename(ntvfs, req, io);
223                 if (req->async_states->state & NTVFS_ASYNC_STATE_ASYNC) {
224                         /* the 2nd try also replied async, so we don't send
225                            the reply yet */
226                         return;
227                 }
228
229                 /* re-mark it async, just in case someone up the chain does
230                    paranoid checking */
231                 req->async_states->state |= NTVFS_ASYNC_STATE_ASYNC;
232                 break;
233         }
234
235         /* send the reply up the chain */
236         req->async_states->status = status;
237         req->async_states->send_fn(req);
238 }
239
240 /*
241   setup for a rename retry after a sharing violation
242   or a non granted oplock
243 */
244 static NTSTATUS pvfs_rename_setup_retry(struct ntvfs_module_context *ntvfs,
245                                         struct ntvfs_request *req,
246                                         union smb_rename *io,
247                                         struct odb_lock *lck,
248                                         NTSTATUS status)
249 {
250         struct pvfs_state *pvfs = ntvfs->private_data;
251         struct timeval end_time;
252
253         if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) {
254                 end_time = timeval_add(&req->statistics.request_time,
255                                        0, pvfs->sharing_violation_delay);
256         } else if (NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) {
257                 end_time = timeval_add(&req->statistics.request_time,
258                                        pvfs->oplock_break_timeout, 0);
259         } else {
260                 return NT_STATUS_INTERNAL_ERROR;
261         }
262
263         return pvfs_odb_retry_setup(ntvfs, req, lck, end_time, io, NULL,
264                                     pvfs_retry_rename);
265 }
266
267 /*
268   rename one file from a wildcard set
269 */
270 static NTSTATUS pvfs_rename_one(struct pvfs_state *pvfs, 
271                                 struct ntvfs_request *req, 
272                                 const char *dir_path,
273                                 const char *fname1,
274                                 const char *fname2,
275                                 uint16_t attrib)
276 {
277         struct pvfs_filename *name1, *name2;
278         TALLOC_CTX *mem_ctx = talloc_new(req);
279         struct odb_lock *lck = NULL;
280         NTSTATUS status;
281
282         /* resolve the wildcard pattern for this name */
283         fname2 = pvfs_resolve_wildcard(mem_ctx, lp_iconv_convenience(pvfs->ntvfs->ctx->lp_ctx), fname1, fname2);
284         if (fname2 == NULL) {
285                 return NT_STATUS_NO_MEMORY;
286         }
287
288         /* get a pvfs_filename source object */
289         status = pvfs_resolve_partial(pvfs, mem_ctx, 
290                                       dir_path, fname1, &name1);
291         if (!NT_STATUS_IS_OK(status)) {
292                 goto failed;
293         }
294
295         /* make sure its matches the given attributes */
296         status = pvfs_match_attrib(pvfs, name1, attrib, 0);
297         if (!NT_STATUS_IS_OK(status)) {
298                 goto failed;
299         }
300
301         status = pvfs_can_rename(pvfs, req, name1, &lck);
302         if (!NT_STATUS_IS_OK(status)) {
303                 talloc_free(lck);
304                 goto failed;
305         }
306
307         /* get a pvfs_filename dest object */
308         status = pvfs_resolve_partial(pvfs, mem_ctx, 
309                                       dir_path, fname2, &name2);
310         if (NT_STATUS_IS_OK(status)) {
311                 status = pvfs_can_delete(pvfs, req, name2, NULL);
312                 if (!NT_STATUS_IS_OK(status)) {
313                         goto failed;
314                 }
315         }
316
317         status = NT_STATUS_OK;
318
319         fname2 = talloc_asprintf(mem_ctx, "%s/%s", dir_path, fname2);
320         if (fname2 == NULL) {
321                 return NT_STATUS_NO_MEMORY;
322         }
323
324         status = pvfs_do_rename(pvfs, lck, name1, fname2);
325
326 failed:
327         talloc_free(mem_ctx);
328         return status;
329 }
330
331
332 /*
333   rename a set of files with wildcards
334 */
335 static NTSTATUS pvfs_rename_wildcard(struct pvfs_state *pvfs, 
336                                      struct ntvfs_request *req, 
337                                      union smb_rename *ren, 
338                                      struct pvfs_filename *name1, 
339                                      struct pvfs_filename *name2)
340 {
341         struct pvfs_dir *dir;
342         NTSTATUS status;
343         off_t ofs = 0;
344         const char *fname, *fname2, *dir_path;
345         uint16_t attrib = ren->rename.in.attrib;
346         int total_renamed = 0;
347
348         /* get list of matching files */
349         status = pvfs_list_start(pvfs, name1, req, &dir);
350         if (!NT_STATUS_IS_OK(status)) {
351                 return status;
352         }
353
354         status = NT_STATUS_NO_SUCH_FILE;
355
356         dir_path = pvfs_list_unix_path(dir);
357
358         /* only allow wildcard renames within a directory */
359         if (strncmp(dir_path, name2->full_name, strlen(dir_path)) != 0 ||
360             name2->full_name[strlen(dir_path)] != '/' ||
361             strchr(name2->full_name + strlen(dir_path) + 1, '/')) {
362                 return NT_STATUS_INVALID_PARAMETER;
363         }
364
365         fname2 = talloc_strdup(name2, name2->full_name + strlen(dir_path) + 1);
366         if (fname2 == NULL) {
367                 return NT_STATUS_NO_MEMORY;
368         }
369
370         while ((fname = pvfs_list_next(dir, &ofs))) {
371                 status = pvfs_rename_one(pvfs, req, 
372                                          dir_path,
373                                          fname, fname2, attrib);
374                 if (NT_STATUS_IS_OK(status)) {
375                         total_renamed++;
376                 }
377         }
378
379         if (total_renamed == 0) {
380                 return status;
381         }
382
383         return NT_STATUS_OK;
384 }
385
386 /*
387   rename a set of files - SMBmv interface
388 */
389 static NTSTATUS pvfs_rename_mv(struct ntvfs_module_context *ntvfs,
390                                struct ntvfs_request *req, union smb_rename *ren)
391 {
392         struct pvfs_state *pvfs = ntvfs->private_data;
393         NTSTATUS status;
394         struct pvfs_filename *name1, *name2;
395         struct odb_lock *lck = NULL;
396
397         /* resolve the cifs name to a posix name */
398         status = pvfs_resolve_name(pvfs, req, ren->rename.in.pattern1, 
399                                    PVFS_RESOLVE_WILDCARD, &name1);
400         if (!NT_STATUS_IS_OK(status)) {
401                 return status;
402         }
403
404         status = pvfs_resolve_name(pvfs, req, ren->rename.in.pattern2, 
405                                    PVFS_RESOLVE_WILDCARD, &name2);
406         if (!NT_STATUS_IS_OK(status)) {
407                 return status;
408         }
409
410         if (name1->has_wildcard || name2->has_wildcard) {
411                 return pvfs_rename_wildcard(pvfs, req, ren, name1, name2);
412         }
413
414         if (!name1->exists) {
415                 return NT_STATUS_OBJECT_NAME_NOT_FOUND;
416         }
417
418         if (strcmp(name1->full_name, name2->full_name) == 0) {
419                 return NT_STATUS_OK;
420         }
421
422         if (name2->exists) {
423                 return NT_STATUS_OBJECT_NAME_COLLISION;
424         }
425
426         status = pvfs_match_attrib(pvfs, name1, ren->rename.in.attrib, 0);
427         if (!NT_STATUS_IS_OK(status)) {
428                 return status;
429         }
430
431         status = pvfs_access_check_parent(pvfs, req, name2, SEC_DIR_ADD_FILE);
432         if (!NT_STATUS_IS_OK(status)) {
433                 return status;
434         }
435
436         status = pvfs_can_rename(pvfs, req, name1, &lck);
437         /*
438          * on a sharing violation we need to retry when the file is closed by
439          * the other user, or after 1 second
440          * on a non granted oplock we need to retry when the file is closed by
441          * the other user, or after 30 seconds
442          */
443         if ((NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION) ||
444              NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) &&
445             (req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) {
446                 return pvfs_rename_setup_retry(pvfs->ntvfs, req, ren, lck, status);
447         }
448
449         if (!NT_STATUS_IS_OK(status)) {
450                 return status;
451         }
452
453         status = pvfs_do_rename(pvfs, lck, name1, name2->full_name);
454         if (!NT_STATUS_IS_OK(status)) {
455                 return status;
456         }
457         
458         return NT_STATUS_OK;
459 }
460
461
462 /*
463   rename a set of files - ntrename interface
464 */
465 static NTSTATUS pvfs_rename_nt(struct ntvfs_module_context *ntvfs,
466                                struct ntvfs_request *req, union smb_rename *ren)
467 {
468         struct pvfs_state *pvfs = ntvfs->private_data;
469         NTSTATUS status;
470         struct pvfs_filename *name1, *name2;
471         struct odb_lock *lck = NULL;
472
473         switch (ren->ntrename.in.flags) {
474         case RENAME_FLAG_RENAME:
475         case RENAME_FLAG_HARD_LINK:
476         case RENAME_FLAG_COPY:
477         case RENAME_FLAG_MOVE_CLUSTER_INFORMATION:
478                 break;
479         default:
480                 return NT_STATUS_ACCESS_DENIED;
481         }
482
483         /* resolve the cifs name to a posix name */
484         status = pvfs_resolve_name(pvfs, req, ren->ntrename.in.old_name, 
485                                    PVFS_RESOLVE_WILDCARD, &name1);
486         if (!NT_STATUS_IS_OK(status)) {
487                 return status;
488         }
489
490         status = pvfs_resolve_name(pvfs, req, ren->ntrename.in.new_name, 
491                                    PVFS_RESOLVE_WILDCARD, &name2);
492         if (!NT_STATUS_IS_OK(status)) {
493                 return status;
494         }
495
496         if (name1->has_wildcard || name2->has_wildcard) {
497                 return NT_STATUS_OBJECT_PATH_SYNTAX_BAD;
498         }
499
500         if (!name1->exists) {
501                 return NT_STATUS_OBJECT_NAME_NOT_FOUND;
502         }
503
504         if (strcmp(name1->full_name, name2->full_name) == 0) {
505                 return NT_STATUS_OK;
506         }
507
508         if (name2->exists) {
509                 return NT_STATUS_OBJECT_NAME_COLLISION;
510         }
511
512         status = pvfs_match_attrib(pvfs, name1, ren->ntrename.in.attrib, 0);
513         if (!NT_STATUS_IS_OK(status)) {
514                 return status;
515         }
516
517         status = pvfs_can_rename(pvfs, req, name1, &lck);
518         /*
519          * on a sharing violation we need to retry when the file is closed by
520          * the other user, or after 1 second
521          * on a non granted oplock we need to retry when the file is closed by
522          * the other user, or after 30 seconds
523          */
524         if ((NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION) ||
525              NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) &&
526             (req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) {
527                 return pvfs_rename_setup_retry(pvfs->ntvfs, req, ren, lck, status);
528         }
529         if (!NT_STATUS_IS_OK(status)) {
530                 return status;
531         }
532
533         switch (ren->ntrename.in.flags) {
534         case RENAME_FLAG_RENAME:
535                 status = pvfs_access_check_parent(pvfs, req, name2, SEC_DIR_ADD_FILE);
536                 NT_STATUS_NOT_OK_RETURN(status);
537                 status = pvfs_do_rename(pvfs, lck, name1, name2->full_name);
538                 NT_STATUS_NOT_OK_RETURN(status);
539                 break;
540
541         case RENAME_FLAG_HARD_LINK:
542                 status = pvfs_access_check_parent(pvfs, req, name2, SEC_DIR_ADD_FILE);
543                 NT_STATUS_NOT_OK_RETURN(status);
544                 if (link(name1->full_name, name2->full_name) == -1) {
545                         return pvfs_map_errno(pvfs, errno);
546                 }
547                 break;
548
549         case RENAME_FLAG_COPY:
550                 status = pvfs_access_check_parent(pvfs, req, name2, SEC_DIR_ADD_FILE);
551                 NT_STATUS_NOT_OK_RETURN(status);
552                 return pvfs_copy_file(pvfs, name1, name2);
553
554         case RENAME_FLAG_MOVE_CLUSTER_INFORMATION:
555                 return NT_STATUS_INVALID_PARAMETER;
556
557         default:
558                 return NT_STATUS_ACCESS_DENIED;
559         }
560
561         
562         return NT_STATUS_OK;
563 }
564
565 /*
566   rename a set of files - ntrename interface
567 */
568 NTSTATUS pvfs_rename(struct ntvfs_module_context *ntvfs,
569                      struct ntvfs_request *req, union smb_rename *ren)
570 {
571         struct pvfs_state *pvfs = ntvfs->private_data;
572         struct pvfs_file *f;
573
574         switch (ren->generic.level) {
575         case RAW_RENAME_RENAME:
576                 return pvfs_rename_mv(ntvfs, req, ren);
577
578         case RAW_RENAME_NTRENAME:
579                 return pvfs_rename_nt(ntvfs, req, ren);
580
581         case RAW_RENAME_NTTRANS:
582                 f = pvfs_find_fd(pvfs, req, ren->nttrans.in.file.ntvfs);
583                 if (!f) {
584                         return NT_STATUS_INVALID_HANDLE;
585                 }
586
587                 /* wk23 ignores the request */
588                 return NT_STATUS_OK;
589
590         default:
591                 break;
592         }
593
594         return NT_STATUS_INVALID_LEVEL;
595 }
596