s3: Add OneFS alternate data streams implementation
[samba.git] / source3 / modules / onefs_streams.c
1 /*
2  * Unix SMB/CIFS implementation.
3  *
4  * Support for OneFS Alternate Data Streams
5  *
6  * Copyright (C) Tim Prouty, 2008
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 "onefs.h"
23 #include <sys/isi_enc.h>
24
25 /*
26  * OneFS stores streams without the explicit :$DATA at the end, so this strips
27  * it off.  All onefs_stream functions must call through this instead of
28  * split_ntfs_stream_name directly.
29  */
30 NTSTATUS onefs_split_ntfs_stream_name(TALLOC_CTX *mem_ctx, const char *fname,
31                                       char **pbase, char **pstream)
32 {
33         NTSTATUS status;
34         char *stream;
35
36         status = split_ntfs_stream_name(mem_ctx, fname, pbase, pstream);
37         if (!NT_STATUS_IS_OK(status)) {
38                 return status;
39         }
40
41         /* Default $DATA stream.  */
42         if (pstream == NULL || *pstream == NULL) {
43                 return NT_STATUS_OK;
44         }
45
46         /* Strip off the $DATA. */
47         stream = strrchr_m(*pstream, ':');
48         SMB_ASSERT(stream);
49         stream[0] = '\0';
50
51         return NT_STATUS_OK;
52 }
53
54 int onefs_close(vfs_handle_struct *handle, struct files_struct *fsp)
55 {
56         int ret2, ret = 0;
57
58         if (fsp->base_fsp) {
59                 ret = SMB_VFS_NEXT_CLOSE(handle, fsp->base_fsp);
60         }
61         ret2 = SMB_VFS_NEXT_CLOSE(handle, fsp);
62
63         return ret ? ret : ret2;
64 }
65
66 /*
67  * Get the ADS directory fd for a file.
68  */
69 static int get_stream_dir_fd(connection_struct *conn, const char *base,
70                              int *base_fdp)
71 {
72         int base_fd;
73         int dir_fd;
74         int saved_errno;
75
76         /* If a valid base_fdp was given, use it. */
77         if (base_fdp && *base_fdp >= 0) {
78                 base_fd = *base_fdp;
79         } else {
80                 base_fd = onefs_sys_create_file(conn,
81                                                 -1,
82                                                 base,
83                                                 0,
84                                                 0,
85                                                 0,
86                                                 0,
87                                                 0,
88                                                 0,
89                                                 INTERNAL_OPEN_ONLY,
90                                                 0,
91                                                 NULL,
92                                                 0,
93                                                 NULL);
94                 if (base_fd < 0) {
95                         return -1;
96                 }
97         }
98
99         /* Open the ADS directory. */
100         dir_fd = onefs_sys_create_file(conn,
101                                         base_fd,
102                                         ".",
103                                         0,
104                                         FILE_READ_DATA,
105                                         0,
106                                         0,
107                                         0,
108                                         0,
109                                         INTERNAL_OPEN_ONLY,
110                                         0,
111                                         NULL,
112                                         0,
113                                         NULL);
114
115         /* Close base_fd if it's not need or on error. */
116         if (!base_fdp || dir_fd < 0) {
117                 saved_errno = errno;
118                 close(base_fd);
119                 errno = saved_errno;
120         }
121
122         /* Set the out base_fdp if successful and it was requested. */
123         if (base_fdp && dir_fd >= 0) {
124                 *base_fdp = base_fd;
125         }
126
127         return dir_fd;
128 }
129
130 int onefs_rename(vfs_handle_struct *handle, const char *oldname,
131                  const char *newname)
132 {
133         TALLOC_CTX *frame = NULL;
134         int ret = -1;
135         int dir_fd, saved_errno;
136         bool old_is_stream;
137         bool new_is_stream;
138         char *obase = NULL;
139         char *osname = NULL;
140         char *nbase = NULL;
141         char *nsname = NULL;
142
143         old_is_stream = is_ntfs_stream_name(oldname);
144         new_is_stream = is_ntfs_stream_name(newname);
145
146         if (!old_is_stream && !new_is_stream) {
147                 return SMB_VFS_NEXT_RENAME(handle, oldname, newname);
148         }
149
150         frame = talloc_stackframe();
151
152         if (!NT_STATUS_IS_OK(onefs_split_ntfs_stream_name(talloc_tos(),
153                                                           oldname, &obase,
154                                                           &osname))) {
155                 errno = ENOMEM;
156                 goto done;
157         }
158
159         if (!NT_STATUS_IS_OK(onefs_split_ntfs_stream_name(talloc_tos(),
160                                                           newname, &nbase,
161                                                           &nsname))) {
162                 errno = ENOMEM;
163                 goto done;
164         }
165
166         dir_fd = get_stream_dir_fd(handle->conn, obase, NULL);
167         if (dir_fd < -1) {
168                 goto done;
169         }
170
171         DEBUG(8,("onefs_rename called for %s : %s  => %s : %s\n",
172                 obase, osname,  nbase, nsname));
173
174         /* Handle rename of stream to default stream specially. */
175         if (nsname == NULL) {
176                 ret = enc_renameat(dir_fd, osname, ENC_DEFAULT, AT_FDCWD,
177                                    nbase, ENC_DEFAULT);
178         } else {
179                 ret = enc_renameat(dir_fd, osname, ENC_DEFAULT, dir_fd, nsname,
180                                    ENC_DEFAULT);
181         }
182
183  done:
184         saved_errno = errno;
185         close(dir_fd);
186         errno = saved_errno;
187         TALLOC_FREE(frame);
188         return ret;
189 }
190
191 /*
192  * Merge a base file's sbuf into the a streams's sbuf.
193  */
194 static void merge_stat(SMB_STRUCT_STAT *stream_sbuf,
195                        const SMB_STRUCT_STAT *base_sbuf)
196 {
197         int dos_flags = (UF_DOS_NOINDEX | UF_DOS_ARCHIVE |
198             UF_DOS_HIDDEN | UF_DOS_RO | UF_DOS_SYSTEM);
199         stream_sbuf->st_mtime = base_sbuf->st_mtime;
200         stream_sbuf->st_ctime = base_sbuf->st_ctime;
201         stream_sbuf->st_atime = base_sbuf->st_atime;
202         stream_sbuf->st_flags &= ~dos_flags;
203         stream_sbuf->st_flags |= base_sbuf->st_flags & dos_flags;
204 }
205
206 static int stat_stream(vfs_handle_struct *handle, const char *base,
207                        const char *stream, SMB_STRUCT_STAT *sbuf, int flags)
208 {
209         SMB_STRUCT_STAT base_sbuf;
210         int base_fd = -1, dir_fd, ret, saved_errno;
211
212         dir_fd = get_stream_dir_fd(handle->conn, base, &base_fd);
213         if (dir_fd < 0) {
214                 return -1;
215         }
216
217         /* Stat the stream. */
218         ret = enc_fstatat(dir_fd, stream, ENC_DEFAULT, sbuf, flags);
219         if (ret != -1) {
220                 /* Now stat the base file and merge the results. */
221                 ret = sys_fstat(base_fd, &base_sbuf);
222                 if (ret != -1) {
223                         merge_stat(sbuf, &base_sbuf);
224                 }
225         }
226
227         saved_errno = errno;
228         close(dir_fd);
229         close(base_fd);
230         errno = saved_errno;
231         return ret;
232 }
233
234 int onefs_stat(vfs_handle_struct *handle, const char *path,
235                SMB_STRUCT_STAT *sbuf)
236 {
237         char *base = NULL;
238         char *stream = NULL;
239
240         if (!is_ntfs_stream_name(path)) {
241                 return SMB_VFS_NEXT_STAT(handle, path, sbuf);
242         }
243
244         if (!NT_STATUS_IS_OK(onefs_split_ntfs_stream_name(talloc_tos(), path,
245                                                           &base, &stream))) {
246                 DEBUG(10, ("onefs_split_ntfs_stream_name failed\n"));
247                 errno = ENOMEM;
248                 return -1;
249         }
250
251         /* If it's the ::$DATA stream just stat the base file name. */
252         if (!stream) {
253                 return SMB_VFS_NEXT_STAT(handle, base, sbuf);
254         }
255
256         return stat_stream(handle, base, stream, sbuf, 0);
257 }
258
259 int onefs_fstat(vfs_handle_struct *handle, struct files_struct *fsp,
260                 SMB_STRUCT_STAT *sbuf)
261 {
262         SMB_STRUCT_STAT base_sbuf;
263         int ret;
264
265         /* Stat the stream, by calling next_fstat on the stream's fd. */
266         ret = SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf);
267         if (ret == -1) {
268                 return ret;
269         }
270
271         /* Stat the base file and merge the results. */
272         if (fsp != NULL && fsp->base_fsp != NULL) {
273                 ret = sys_fstat(fsp->base_fsp->fh->fd, &base_sbuf);
274                 if (ret != -1) {
275                         merge_stat(sbuf, &base_sbuf);
276                 }
277         }
278
279         return ret;
280 }
281
282 int onefs_lstat(vfs_handle_struct *handle, const char *path,
283                 SMB_STRUCT_STAT *sbuf)
284 {
285         char *base = NULL;
286         char *stream = NULL;
287
288         if (!is_ntfs_stream_name(path)) {
289                 return SMB_VFS_NEXT_LSTAT(handle, path, sbuf);
290         }
291
292         if (!NT_STATUS_IS_OK(onefs_split_ntfs_stream_name(talloc_tos(), path,
293                                                           &base, &stream))) {
294                 DEBUG(10, ("onefs_split_ntfs_stream_name failed\n"));
295                 errno = ENOMEM;
296                 return -1;
297         }
298
299         /* If it's the ::$DATA stream just stat the base file name. */
300         if (!stream) {
301                 return SMB_VFS_NEXT_LSTAT(handle, base, sbuf);
302         }
303
304         return stat_stream(handle, base, stream, sbuf, AT_SYMLINK_NOFOLLOW);
305 }
306
307 int onefs_unlink(vfs_handle_struct *handle, const char *path)
308 {
309         char *base = NULL;
310         char *stream = NULL;
311         int dir_fd, ret, saved_errno;
312
313         if (!is_ntfs_stream_name(path)) {
314                 return SMB_VFS_NEXT_UNLINK(handle, path);
315         }
316
317         if (!NT_STATUS_IS_OK(onefs_split_ntfs_stream_name(talloc_tos(), path,
318                                                           &base, &stream))) {
319                 DEBUG(10, ("onefs_split_ntfs_stream_name failed\n"));
320                 errno = ENOMEM;
321                 return -1;
322         }
323
324         /* If it's the ::$DATA stream just unlink the base file name. */
325         if (!stream) {
326                 return SMB_VFS_NEXT_UNLINK(handle, base);
327         }
328
329         dir_fd = get_stream_dir_fd(handle->conn, base, NULL);
330         if (dir_fd < 0) {
331                 return -1;
332         }
333
334         ret = enc_unlinkat(dir_fd, stream, ENC_DEFAULT, 0);
335
336         saved_errno = errno;
337         close(dir_fd);
338         errno = saved_errno;
339         return ret;
340 }
341
342 int onefs_chflags(vfs_handle_struct *handle, const char *path,
343                   unsigned int flags)
344 {
345         char *base = NULL;
346         char *stream = NULL;
347
348         if (!NT_STATUS_IS_OK(onefs_split_ntfs_stream_name(talloc_tos(), path,
349                                                           &base, &stream))) {
350                 DEBUG(10, ("onefs_split_ntfs_stream_name failed\n"));
351                 errno = ENOMEM;
352                 return -1;
353         }
354
355         /*
356          * Only set the attributes on the base file.  ifs_createfile handles
357          * file creation attribute semantics.
358          */
359         return SMB_VFS_NEXT_CHFLAGS(handle, base, flags);
360 }
361
362 /*
363  * Streaminfo enumeration functionality
364  */
365 struct streaminfo_state {
366         TALLOC_CTX *mem_ctx;
367         vfs_handle_struct *handle;
368         unsigned int num_streams;
369         struct stream_struct *streams;
370         NTSTATUS status;
371 };
372
373 static bool add_one_stream(TALLOC_CTX *mem_ctx, unsigned int *num_streams,
374                            struct stream_struct **streams,
375                            const char *name, SMB_OFF_T size,
376                            SMB_OFF_T alloc_size)
377 {
378         struct stream_struct *tmp;
379
380         tmp = TALLOC_REALLOC_ARRAY(mem_ctx, *streams, struct stream_struct,
381                                    (*num_streams)+1);
382         if (tmp == NULL) {
383                 return false;
384         }
385
386         tmp[*num_streams].name = talloc_asprintf(mem_ctx, ":%s:%s", name,
387                                                  "$DATA");
388         if (tmp[*num_streams].name == NULL) {
389                 return false;
390         }
391
392         tmp[*num_streams].size = size;
393         tmp[*num_streams].alloc_size = alloc_size;
394
395         *streams = tmp;
396         *num_streams += 1;
397         return true;
398 }
399
400 static NTSTATUS walk_onefs_streams(connection_struct *conn, files_struct *fsp,
401                                    const char *fname,
402                                    struct streaminfo_state *state,
403                                    SMB_STRUCT_STAT *base_sbuf)
404 {
405         NTSTATUS status = NT_STATUS_OK;
406         bool opened_base_fd = false;
407         int base_fd = -1;
408         int dir_fd = -1;
409         int stream_fd = -1;
410         int ret;
411         SMB_STRUCT_DIR *dirp = NULL;
412         SMB_STRUCT_DIRENT *dp = NULL;
413         files_struct fake_fs;
414         struct fd_handle fake_fh;
415         SMB_STRUCT_STAT stream_sbuf;
416
417         ZERO_STRUCT(fake_fh);
418         ZERO_STRUCT(fake_fs);
419
420         /* If the base file is already open, use its fd. */
421         if ((fsp != NULL) && (fsp->fh->fd != -1)) {
422                 base_fd = fsp->fh->fd;
423         } else {
424                 opened_base_fd = true;
425         }
426
427         dir_fd = get_stream_dir_fd(conn, fname, &base_fd);
428         if (dir_fd < 0) {
429                 return map_nt_error_from_unix(errno);
430         }
431
432         /* Open the ADS directory. */
433         if ((dirp = fdopendir(dir_fd)) == NULL) {
434                 DEBUG(0, ("Error on opendir %s. errno=%d (%s)\n",
435                           fname, errno, strerror(errno)));
436                 status = map_nt_error_from_unix(errno);
437                 goto out;
438         }
439
440         fake_fs.conn = conn;
441         fake_fs.fh = &fake_fh;
442         fake_fs.fsp_name = SMB_STRDUP(fname);
443
444         /* Iterate over the streams in the ADS directory. */
445         while ((dp = SMB_VFS_READDIR(conn, dirp)) != NULL) {
446                 /* Skip the "." and ".." entries */
447                 if ((strcmp(dp->d_name, ".") == 0) ||
448                     (strcmp(dp->d_name, "..") == 0))
449                         continue;
450
451                 /* Open actual stream */
452                 if ((stream_fd = onefs_sys_create_file(conn,
453                                                          base_fd,
454                                                          dp->d_name,
455                                                          0,
456                                                          0,
457                                                          0,
458                                                          0,
459                                                          0,
460                                                          0,
461                                                          INTERNAL_OPEN_ONLY,
462                                                          0,
463                                                          NULL,
464                                                          0,
465                                                          NULL)) == -1) {
466                         DEBUG(0, ("Error opening stream %s:%s. "
467                                   "errno=%d (%s)\n", fname, dp->d_name, errno,
468                                   strerror(errno)));
469                         continue;
470                 }
471
472                 /* Figure out the stat info. */
473                 fake_fh.fd = stream_fd;
474                 ret = SMB_VFS_FSTAT(&fake_fs, &stream_sbuf);
475                 close(stream_fd);
476
477                 if (ret) {
478                         DEBUG(0, ("Error fstating stream %s:%s. "
479                                   "errno=%d (%s)\n", fname, dp->d_name, errno,
480                                   strerror(errno)));
481                         continue;
482                 }
483
484                 merge_stat(&stream_sbuf, base_sbuf);
485
486                 if (!add_one_stream(state->mem_ctx,
487                                     &state->num_streams, &state->streams,
488                                     dp->d_name, stream_sbuf.st_size,
489                                     get_allocation_size(conn, NULL,
490                                                         &stream_sbuf))) {
491                         state->status = NT_STATUS_NO_MEMORY;
492                         break;
493                 }
494         }
495
496 out:
497         /* Cleanup everything that was opened. */
498         if (dirp != NULL) {
499                 SMB_VFS_CLOSEDIR(conn, dirp);
500         }
501         if (dir_fd >= 0) {
502                 close(dir_fd);
503         }
504         if (opened_base_fd) {
505                 SMB_ASSERT(base_fd >= 0);
506                 close(base_fd);
507         }
508
509         SAFE_FREE(fake_fs.fsp_name);
510         return status;
511 }
512
513 NTSTATUS onefs_streaminfo(vfs_handle_struct *handle,
514                           struct files_struct *fsp,
515                           const char *fname,
516                           TALLOC_CTX *mem_ctx,
517                           unsigned int *num_streams,
518                           struct stream_struct **streams)
519 {
520         SMB_STRUCT_STAT sbuf;
521         int ret;
522         NTSTATUS status;
523         struct streaminfo_state state;
524
525         /* Get a valid stat. */
526         if ((fsp != NULL) && (fsp->fh->fd != -1)) {
527                 if (is_ntfs_stream_name(fsp->fsp_name)) {
528                         return NT_STATUS_INVALID_PARAMETER;
529                 }
530                 ret = SMB_VFS_FSTAT(fsp, &sbuf);
531         } else {
532                 if (is_ntfs_stream_name(fname)) {
533                         return NT_STATUS_INVALID_PARAMETER;
534                 }
535                 ret = SMB_VFS_STAT(handle->conn, fname, &sbuf);
536         }
537
538         if (ret == -1) {
539                 return map_nt_error_from_unix(errno);
540         }
541
542         state.streams = NULL;
543         state.num_streams = 0;
544
545         /* Add the default stream. */
546         if (S_ISREG(sbuf.st_mode)) {
547                 if (!add_one_stream(mem_ctx,
548                                     &state.num_streams, &state.streams,
549                                     "", sbuf.st_size,
550                                     get_allocation_size(handle->conn, fsp,
551                                                         &sbuf))) {
552                         return NT_STATUS_NO_MEMORY;
553                 }
554         }
555
556         state.mem_ctx = mem_ctx;
557         state.handle = handle;
558         state.status = NT_STATUS_OK;
559
560         /* If there are more streams, add them too. */
561         if (sbuf.st_flags & UF_HASADS) {
562
563                 status = walk_onefs_streams(handle->conn, fsp, fname,
564                     &state, &sbuf);
565
566                 if (!NT_STATUS_IS_OK(status)) {
567                         TALLOC_FREE(state.streams);
568                         return status;
569                 }
570
571                 if (!NT_STATUS_IS_OK(state.status)) {
572                         TALLOC_FREE(state.streams);
573                         return state.status;
574                 }
575         }
576
577         *num_streams = state.num_streams;
578         *streams = state.streams;
579         return NT_STATUS_OK;
580 }