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