Merge branch 'v4-0-test' of ssh://git.samba.org/data/git/samba into v4-0-trivial
[bbaumbach/samba-autobuild/.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, const struct pvfs_filename *name1, 
32                         const char *name2)
33 {
34         const char *r1, *r2;
35         uint32_t mask;
36
37         if (rename(name1->full_name, name2) == -1) {
38                 return pvfs_map_errno(pvfs, errno);
39         }
40
41         if (name1->dos.attrib & FILE_ATTRIBUTE_DIRECTORY) {
42                 mask = FILE_NOTIFY_CHANGE_DIR_NAME;
43         } else {
44                 mask = FILE_NOTIFY_CHANGE_FILE_NAME;
45         }
46         /* 
47            renames to the same directory cause a OLD_NAME->NEW_NAME notify.
48            renames to a different directory are considered a remove/add 
49         */
50         r1 = strrchr_m(name1->full_name, '/');
51         r2 = strrchr_m(name2, '/');
52
53         if ((r1-name1->full_name) != (r2-name2) ||
54             strncmp(name1->full_name, name2, r1-name1->full_name) != 0) {
55                 notify_trigger(pvfs->notify_context, 
56                                NOTIFY_ACTION_REMOVED, 
57                                mask,
58                                name1->full_name);
59                 notify_trigger(pvfs->notify_context, 
60                                NOTIFY_ACTION_ADDED, 
61                                mask,
62                                name2);
63         } else {
64                 notify_trigger(pvfs->notify_context, 
65                                NOTIFY_ACTION_OLD_NAME, 
66                                mask,
67                                name1->full_name);
68                 notify_trigger(pvfs->notify_context, 
69                                NOTIFY_ACTION_NEW_NAME, 
70                                mask,
71                                name2);
72         }
73
74         /* this is a strange one. w2k3 gives an additional event for CHANGE_ATTRIBUTES
75            and CHANGE_CREATION on the new file when renaming files, but not 
76            directories */
77         if ((name1->dos.attrib & FILE_ATTRIBUTE_DIRECTORY) == 0) {
78                 notify_trigger(pvfs->notify_context, 
79                                NOTIFY_ACTION_MODIFIED, 
80                                FILE_NOTIFY_CHANGE_ATTRIBUTES|FILE_NOTIFY_CHANGE_CREATION,
81                                name2);
82         }
83         
84         return NT_STATUS_OK;
85 }
86
87
88 /*
89   resolve a wildcard rename pattern. This works on one component of the name
90 */
91 static const char *pvfs_resolve_wildcard_component(TALLOC_CTX *mem_ctx, 
92                                                    struct smb_iconv_convenience *iconv_convenience,
93                                                    const char *fname, 
94                                                    const char *pattern)
95 {
96         const char *p1, *p2;
97         char *dest, *d;
98
99         /* the length is bounded by the length of the two strings combined */
100         dest = talloc_size(mem_ctx, strlen(fname) + strlen(pattern) + 1);
101         if (dest == NULL) {
102                 return NULL;
103         }
104
105         p1 = fname;
106         p2 = pattern;
107         d = dest;
108
109         while (*p2) {
110                 codepoint_t c1, c2;
111                 size_t c_size1, c_size2;
112                 c1 = next_codepoint(iconv_convenience, p1, &c_size1);
113                 c2 = next_codepoint(iconv_convenience, p2, &c_size2);
114                 if (c2 == '?') {
115                         d += push_codepoint(iconv_convenience, d, c1);
116                 } else if (c2 == '*') {
117                         memcpy(d, p1, strlen(p1));
118                         d += strlen(p1);
119                         break;
120                 } else {
121                         d += push_codepoint(iconv_convenience, d, c2);
122                 }
123
124                 p1 += c_size1;
125                 p2 += c_size2;
126         }
127
128         *d = 0;
129
130         return dest;
131 }
132
133 /*
134   resolve a wildcard rename pattern.
135 */
136 static const char *pvfs_resolve_wildcard(TALLOC_CTX *mem_ctx, 
137                                          struct smb_iconv_convenience *iconv_convenience,
138                                          const char *fname, 
139                                          const char *pattern)
140 {
141         const char *base1, *base2;
142         const char *ext1, *ext2;
143         char *p;
144
145         /* break into base part plus extension */
146         p = strrchr_m(fname, '.');
147         if (p == NULL) {
148                 ext1 = "";
149                 base1 = fname;
150         } else {
151                 ext1 = talloc_strdup(mem_ctx, p+1);
152                 base1 = talloc_strndup(mem_ctx, fname, p-fname);
153         }
154         if (ext1 == NULL || base1 == NULL) {
155                 return NULL;
156         }
157
158         p = strrchr_m(pattern, '.');
159         if (p == NULL) {
160                 ext2 = "";
161                 base2 = fname;
162         } else {
163                 ext2 = talloc_strdup(mem_ctx, p+1);
164                 base2 = talloc_strndup(mem_ctx, pattern, p-pattern);
165         }
166         if (ext2 == NULL || base2 == NULL) {
167                 return NULL;
168         }
169
170         base1 = pvfs_resolve_wildcard_component(mem_ctx, iconv_convenience, base1, base2);
171         ext1 = pvfs_resolve_wildcard_component(mem_ctx, iconv_convenience, ext1, ext2);
172         if (base1 == NULL || ext1 == NULL) {
173                 return NULL;
174         }
175
176         if (*ext1 == 0) {
177                 return base1;
178         }
179
180         return talloc_asprintf(mem_ctx, "%s.%s", base1, ext1);
181 }
182
183 /*
184   rename one file from a wildcard set
185 */
186 static NTSTATUS pvfs_rename_one(struct pvfs_state *pvfs, 
187                                 struct ntvfs_request *req, 
188                                 const char *dir_path,
189                                 const char *fname1,
190                                 const char *fname2,
191                                 uint16_t attrib)
192 {
193         struct pvfs_filename *name1, *name2;
194         TALLOC_CTX *mem_ctx = talloc_new(req);
195         struct odb_lock *lck = NULL;
196         NTSTATUS status;
197
198         /* resolve the wildcard pattern for this name */
199         fname2 = pvfs_resolve_wildcard(mem_ctx, lp_iconv_convenience(pvfs->ntvfs->ctx->lp_ctx), fname1, fname2);
200         if (fname2 == NULL) {
201                 return NT_STATUS_NO_MEMORY;
202         }
203
204         /* get a pvfs_filename source object */
205         status = pvfs_resolve_partial(pvfs, mem_ctx, 
206                                       dir_path, fname1, &name1);
207         if (!NT_STATUS_IS_OK(status)) {
208                 goto failed;
209         }
210
211         /* make sure its matches the given attributes */
212         status = pvfs_match_attrib(pvfs, name1, attrib, 0);
213         if (!NT_STATUS_IS_OK(status)) {
214                 goto failed;
215         }
216
217         status = pvfs_can_rename(pvfs, req, name1, &lck);
218         if (!NT_STATUS_IS_OK(status)) {
219                 talloc_free(lck);
220                 goto failed;
221         }
222
223         /* get a pvfs_filename dest object */
224         status = pvfs_resolve_partial(pvfs, mem_ctx, 
225                                       dir_path, fname2, &name2);
226         if (NT_STATUS_IS_OK(status)) {
227                 status = pvfs_can_delete(pvfs, req, name2, NULL);
228                 if (!NT_STATUS_IS_OK(status)) {
229                         goto failed;
230                 }
231         }
232
233         status = NT_STATUS_OK;
234
235         fname2 = talloc_asprintf(mem_ctx, "%s/%s", dir_path, fname2);
236         if (fname2 == NULL) {
237                 return NT_STATUS_NO_MEMORY;
238         }
239
240         status = pvfs_do_rename(pvfs, name1, fname2);
241
242         if (NT_STATUS_IS_OK(status)) {
243                 status = odb_rename(lck, fname2);
244         }
245
246 failed:
247         talloc_free(mem_ctx);
248         return status;
249 }
250
251
252 /*
253   rename a set of files with wildcards
254 */
255 static NTSTATUS pvfs_rename_wildcard(struct pvfs_state *pvfs, 
256                                      struct ntvfs_request *req, 
257                                      union smb_rename *ren, 
258                                      struct pvfs_filename *name1, 
259                                      struct pvfs_filename *name2)
260 {
261         struct pvfs_dir *dir;
262         NTSTATUS status;
263         off_t ofs = 0;
264         const char *fname, *fname2, *dir_path;
265         uint16_t attrib = ren->rename.in.attrib;
266         int total_renamed = 0;
267
268         /* get list of matching files */
269         status = pvfs_list_start(pvfs, name1, req, &dir);
270         if (!NT_STATUS_IS_OK(status)) {
271                 return status;
272         }
273
274         status = NT_STATUS_NO_SUCH_FILE;
275
276         dir_path = pvfs_list_unix_path(dir);
277
278         /* only allow wildcard renames within a directory */
279         if (strncmp(dir_path, name2->full_name, strlen(dir_path)) != 0 ||
280             name2->full_name[strlen(dir_path)] != '/' ||
281             strchr(name2->full_name + strlen(dir_path) + 1, '/')) {
282                 return NT_STATUS_INVALID_PARAMETER;
283         }
284
285         fname2 = talloc_strdup(name2, name2->full_name + strlen(dir_path) + 1);
286         if (fname2 == NULL) {
287                 return NT_STATUS_NO_MEMORY;
288         }
289
290         while ((fname = pvfs_list_next(dir, &ofs))) {
291                 status = pvfs_rename_one(pvfs, req, 
292                                          dir_path,
293                                          fname, fname2, attrib);
294                 if (NT_STATUS_IS_OK(status)) {
295                         total_renamed++;
296                 }
297         }
298
299         if (total_renamed == 0) {
300                 return status;
301         }
302
303         return NT_STATUS_OK;
304 }
305
306 /*
307   rename a set of files - SMBmv interface
308 */
309 static NTSTATUS pvfs_rename_mv(struct ntvfs_module_context *ntvfs,
310                                struct ntvfs_request *req, union smb_rename *ren)
311 {
312         struct pvfs_state *pvfs = ntvfs->private_data;
313         NTSTATUS status;
314         struct pvfs_filename *name1, *name2;
315         struct odb_lock *lck = NULL;
316
317         /* resolve the cifs name to a posix name */
318         status = pvfs_resolve_name(pvfs, req, ren->rename.in.pattern1, 
319                                    PVFS_RESOLVE_WILDCARD, &name1);
320         if (!NT_STATUS_IS_OK(status)) {
321                 return status;
322         }
323
324         status = pvfs_resolve_name(pvfs, req, ren->rename.in.pattern2, 
325                                    PVFS_RESOLVE_WILDCARD, &name2);
326         if (!NT_STATUS_IS_OK(status)) {
327                 return status;
328         }
329
330         if (name1->has_wildcard || name2->has_wildcard) {
331                 return pvfs_rename_wildcard(pvfs, req, ren, name1, name2);
332         }
333
334         if (!name1->exists) {
335                 return NT_STATUS_OBJECT_NAME_NOT_FOUND;
336         }
337
338         if (strcmp(name1->full_name, name2->full_name) == 0) {
339                 return NT_STATUS_OK;
340         }
341
342         if (name2->exists) {
343                 return NT_STATUS_OBJECT_NAME_COLLISION;
344         }
345
346         status = pvfs_match_attrib(pvfs, name1, ren->rename.in.attrib, 0);
347         if (!NT_STATUS_IS_OK(status)) {
348                 return status;
349         }
350
351         status = pvfs_access_check_parent(pvfs, req, name2, SEC_DIR_ADD_FILE);
352         if (!NT_STATUS_IS_OK(status)) {
353                 return status;
354         }
355
356         status = pvfs_can_rename(pvfs, req, name1, &lck);
357         if (!NT_STATUS_IS_OK(status)) {
358                 talloc_free(lck);
359                 return status;
360         }
361
362         status = pvfs_do_rename(pvfs, name1, name2->full_name);
363         if (NT_STATUS_IS_OK(status)) {
364                 status = odb_rename(lck, name2->full_name);
365         }
366         
367         return NT_STATUS_OK;
368 }
369
370
371 /*
372   rename a set of files - ntrename interface
373 */
374 static NTSTATUS pvfs_rename_nt(struct ntvfs_module_context *ntvfs,
375                                struct ntvfs_request *req, union smb_rename *ren)
376 {
377         struct pvfs_state *pvfs = ntvfs->private_data;
378         NTSTATUS status;
379         struct pvfs_filename *name1, *name2;
380
381         switch (ren->ntrename.in.flags) {
382         case RENAME_FLAG_RENAME:
383         case RENAME_FLAG_HARD_LINK:
384         case RENAME_FLAG_COPY:
385         case RENAME_FLAG_MOVE_CLUSTER_INFORMATION:
386                 break;
387         default:
388                 return NT_STATUS_ACCESS_DENIED;
389         }
390
391         /* resolve the cifs name to a posix name */
392         status = pvfs_resolve_name(pvfs, req, ren->ntrename.in.old_name, 
393                                    PVFS_RESOLVE_WILDCARD, &name1);
394         if (!NT_STATUS_IS_OK(status)) {
395                 return status;
396         }
397
398         status = pvfs_resolve_name(pvfs, req, ren->ntrename.in.new_name, 
399                                    PVFS_RESOLVE_WILDCARD, &name2);
400         if (!NT_STATUS_IS_OK(status)) {
401                 return status;
402         }
403
404         if (name1->has_wildcard || name2->has_wildcard) {
405                 return NT_STATUS_OBJECT_PATH_SYNTAX_BAD;
406         }
407
408         if (!name1->exists) {
409                 return NT_STATUS_OBJECT_NAME_NOT_FOUND;
410         }
411
412         if (strcmp(name1->full_name, name2->full_name) == 0) {
413                 return NT_STATUS_OK;
414         }
415
416         if (name2->exists) {
417                 return NT_STATUS_OBJECT_NAME_COLLISION;
418         }
419
420         status = pvfs_match_attrib(pvfs, name1, ren->ntrename.in.attrib, 0);
421         if (!NT_STATUS_IS_OK(status)) {
422                 return status;
423         }
424
425         status = pvfs_can_rename(pvfs, req, name1, NULL);
426         if (!NT_STATUS_IS_OK(status)) {
427                 return status;
428         }
429
430         switch (ren->ntrename.in.flags) {
431         case RENAME_FLAG_RENAME:
432                 status = pvfs_access_check_parent(pvfs, req, name2, SEC_DIR_ADD_FILE);
433                 NT_STATUS_NOT_OK_RETURN(status);
434                 status = pvfs_do_rename(pvfs, name1, name2->full_name);
435                 NT_STATUS_NOT_OK_RETURN(status);
436                 break;
437
438         case RENAME_FLAG_HARD_LINK:
439                 status = pvfs_access_check_parent(pvfs, req, name2, SEC_DIR_ADD_FILE);
440                 NT_STATUS_NOT_OK_RETURN(status);
441                 if (link(name1->full_name, name2->full_name) == -1) {
442                         return pvfs_map_errno(pvfs, errno);
443                 }
444                 break;
445
446         case RENAME_FLAG_COPY:
447                 status = pvfs_access_check_parent(pvfs, req, name2, SEC_DIR_ADD_FILE);
448                 NT_STATUS_NOT_OK_RETURN(status);
449                 return pvfs_copy_file(pvfs, name1, name2);
450
451         case RENAME_FLAG_MOVE_CLUSTER_INFORMATION:
452                 return NT_STATUS_INVALID_PARAMETER;
453
454         default:
455                 return NT_STATUS_ACCESS_DENIED;
456         }
457
458         
459         return NT_STATUS_OK;
460 }
461
462 /*
463   rename a set of files - ntrename interface
464 */
465 NTSTATUS pvfs_rename(struct ntvfs_module_context *ntvfs,
466                      struct ntvfs_request *req, union smb_rename *ren)
467 {
468         switch (ren->generic.level) {
469         case RAW_RENAME_RENAME:
470                 return pvfs_rename_mv(ntvfs, req, ren);
471
472         case RAW_RENAME_NTRENAME:
473                 return pvfs_rename_nt(ntvfs, req, ren);
474
475         default:
476                 break;
477         }
478
479         return NT_STATUS_INVALID_LEVEL;
480 }
481