284e199fc5537a7d1b52b69aa9143cea341a4b53
[ira/wip.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 "includes.h"
23 #include "onefs.h"
24 #include "onefs_config.h"
25
26 #include <sys/isi_enc.h>
27
28 /*
29  * OneFS stores streams without the explicit :$DATA at the end, so this strips
30  * it off.  All onefs_stream functions must call through this instead of
31  * split_ntfs_stream_name directly.
32  */
33 NTSTATUS onefs_split_ntfs_stream_name(TALLOC_CTX *mem_ctx, const char *fname,
34                                       char **pbase, char **pstream)
35 {
36         NTSTATUS status;
37         char *stream;
38
39         status = split_ntfs_stream_name(mem_ctx, fname, pbase, pstream);
40         if (!NT_STATUS_IS_OK(status)) {
41                 return status;
42         }
43
44         /* Default $DATA stream.  */
45         if (pstream == NULL || *pstream == NULL) {
46                 return NT_STATUS_OK;
47         }
48
49         /* Strip off the $DATA. */
50         stream = strrchr_m(*pstream, ':');
51         SMB_ASSERT(stream);
52         stream[0] = '\0';
53
54         return NT_STATUS_OK;
55 }
56
57 int onefs_is_stream(const char *path, char **pbase, char **pstream,
58                     bool *is_stream)
59 {
60         (*is_stream) = is_ntfs_stream_name(path);
61
62         if (!(*is_stream)) {
63                 return 0;
64         }
65
66         if (!NT_STATUS_IS_OK(onefs_split_ntfs_stream_name(talloc_tos(), path,
67                                                           pbase, pstream))) {
68                 DEBUG(10, ("onefs_split_ntfs_stream_name failed\n"));
69                 errno = ENOMEM;
70                 return -1;
71         }
72
73         return 0;
74 }
75
76 int onefs_close(vfs_handle_struct *handle, struct files_struct *fsp)
77 {
78         int ret2, ret = 0;
79
80         if (fsp->base_fsp) {
81                 ret = SMB_VFS_NEXT_CLOSE(handle, fsp->base_fsp);
82         }
83         ret2 = SMB_VFS_NEXT_CLOSE(handle, fsp);
84
85         return ret ? ret : ret2;
86 }
87
88 /*
89  * Get the ADS directory fd for a file.
90  */
91 static int get_stream_dir_fd(connection_struct *conn, const char *base,
92                              int *base_fdp)
93 {
94         int base_fd;
95         int dir_fd;
96         int saved_errno;
97
98         /* If a valid base_fdp was given, use it. */
99         if (base_fdp && *base_fdp >= 0) {
100                 base_fd = *base_fdp;
101         } else {
102                 base_fd = onefs_sys_create_file(conn,
103                                                 -1,
104                                                 base,
105                                                 0,
106                                                 0,
107                                                 0,
108                                                 0,
109                                                 0,
110                                                 0,
111                                                 INTERNAL_OPEN_ONLY,
112                                                 0,
113                                                 NULL,
114                                                 0,
115                                                 NULL);
116                 if (base_fd < 0) {
117                         return -1;
118                 }
119         }
120
121         /* Open the ADS directory. */
122         dir_fd = onefs_sys_create_file(conn,
123                                         base_fd,
124                                         ".",
125                                         0,
126                                         FILE_READ_DATA,
127                                         0,
128                                         0,
129                                         0,
130                                         0,
131                                         INTERNAL_OPEN_ONLY,
132                                         0,
133                                         NULL,
134                                         0,
135                                         NULL);
136
137         /* Close base_fd if it's not need or on error. */
138         if (!base_fdp || dir_fd < 0) {
139                 saved_errno = errno;
140                 close(base_fd);
141                 errno = saved_errno;
142         }
143
144         /* Set the out base_fdp if successful and it was requested. */
145         if (base_fdp && dir_fd >= 0) {
146                 *base_fdp = base_fd;
147         }
148
149         return dir_fd;
150 }
151
152 int onefs_rename(vfs_handle_struct *handle, const char *oldname,
153                  const char *newname)
154 {
155         TALLOC_CTX *frame = NULL;
156         int ret = -1;
157         int dir_fd = -1;
158         int saved_errno;
159         bool old_is_stream;
160         bool new_is_stream;
161         char *obase = NULL;
162         char *osname = NULL;
163         char *nbase = NULL;
164         char *nsname = NULL;
165
166         START_PROFILE(syscall_rename_at);
167
168         frame = talloc_stackframe();
169
170         ret = onefs_is_stream(oldname, &obase, &osname, &old_is_stream);
171         if (ret) {
172                 END_PROFILE(syscall_rename_at);
173                 return ret;
174         }
175
176         ret = onefs_is_stream(newname, &nbase, &nsname, &new_is_stream);
177         if (ret) {
178                 END_PROFILE(syscall_rename_at);
179                 return ret;
180         }
181
182         if (!old_is_stream && !new_is_stream) {
183                 ret = SMB_VFS_NEXT_RENAME(handle, oldname, newname);
184                 END_PROFILE(syscall_rename_at);
185                 return ret;
186         }
187
188         dir_fd = get_stream_dir_fd(handle->conn, obase, NULL);
189         if (dir_fd < -1) {
190                 goto done;
191         }
192
193         DEBUG(8,("onefs_rename called for %s : %s  => %s : %s\n",
194                 obase, osname,  nbase, nsname));
195
196         /* Handle rename of stream to default stream specially. */
197         if (nsname == NULL) {
198                 ret = enc_renameat(dir_fd, osname, ENC_DEFAULT, AT_FDCWD,
199                                    nbase, ENC_DEFAULT);
200         } else {
201                 ret = enc_renameat(dir_fd, osname, ENC_DEFAULT, dir_fd, nsname,
202                                    ENC_DEFAULT);
203         }
204
205  done:
206         END_PROFILE(syscall_rename_at);
207
208         saved_errno = errno;
209         if (dir_fd >= 0) {
210                 close(dir_fd);
211         }
212         errno = saved_errno;
213         TALLOC_FREE(frame);
214         return ret;
215 }
216
217 /*
218  * Merge a base file's sbuf into the a streams's sbuf.
219  */
220 static void merge_stat(SMB_STRUCT_STAT *stream_sbuf,
221                        const SMB_STRUCT_STAT *base_sbuf)
222 {
223         int dos_flags = (UF_DOS_NOINDEX | UF_DOS_ARCHIVE |
224             UF_DOS_HIDDEN | UF_DOS_RO | UF_DOS_SYSTEM);
225         stream_sbuf->st_ex_mtime = base_sbuf->st_ex_mtime;
226         stream_sbuf->st_ex_ctime = base_sbuf->st_ex_ctime;
227         stream_sbuf->st_ex_atime = base_sbuf->st_ex_atime;
228         stream_sbuf->st_ex_flags &= ~dos_flags;
229         stream_sbuf->st_ex_flags |= base_sbuf->st_ex_flags & dos_flags;
230 }
231
232 /* fake timestamps */
233 static void onefs_adjust_stat_time(struct connection_struct *conn,
234                                    const char *fname, SMB_STRUCT_STAT *sbuf)
235 {
236         struct onefs_vfs_share_config cfg;
237         struct timeval tv_now = {0, 0};
238         bool static_mtime = False;
239         bool static_atime = False;
240
241         if (!onefs_get_config(SNUM(conn),
242                               ONEFS_VFS_CONFIG_FAKETIMESTAMPS, &cfg)) {
243                 return;
244         }
245
246         if (IS_MTIME_STATIC_PATH(conn, &cfg, fname)) {
247                 sbuf->st_ex_mtime = sbuf->st_ex_btime;
248                 static_mtime = True;
249         }
250         if (IS_ATIME_STATIC_PATH(conn, &cfg, fname)) {
251                 sbuf->st_ex_atime = sbuf->st_ex_btime;
252                 static_atime = True;
253         }
254
255         if (IS_CTIME_NOW_PATH(conn, &cfg, fname)) {
256                 if (cfg.ctime_slop < 0) {
257                         sbuf->st_ex_btime.tv_sec = INT_MAX - 1;
258                 } else {
259                         GetTimeOfDay(&tv_now);
260                         sbuf->st_ex_btime.tv_sec = tv_now.tv_sec +
261                             cfg.ctime_slop;
262                 }
263         }
264
265         if (!static_mtime && IS_MTIME_NOW_PATH(conn,&cfg,fname)) {
266                 if (cfg.mtime_slop < 0) {
267                         sbuf->st_ex_mtime.tv_sec = INT_MAX - 1;
268                 } else {
269                         if (tv_now.tv_sec == 0)
270                                 GetTimeOfDay(&tv_now);
271                         sbuf->st_ex_mtime.tv_sec = tv_now.tv_sec +
272                             cfg.mtime_slop;
273                 }
274         }
275         if (!static_atime && IS_ATIME_NOW_PATH(conn,&cfg,fname)) {
276                 if (cfg.atime_slop < 0) {
277                         sbuf->st_ex_atime.tv_sec = INT_MAX - 1;
278                 } else {
279                         if (tv_now.tv_sec == 0)
280                                 GetTimeOfDay(&tv_now);
281                         sbuf->st_ex_atime.tv_sec = tv_now.tv_sec +
282                             cfg.atime_slop;
283                 }
284         }
285 }
286
287 static int stat_stream(struct connection_struct *conn, const char *base,
288                        const char *stream, SMB_STRUCT_STAT *sbuf, int flags)
289 {
290         SMB_STRUCT_STAT base_sbuf;
291         int base_fd = -1, dir_fd, ret, saved_errno;
292
293         dir_fd = get_stream_dir_fd(conn, base, &base_fd);
294         if (dir_fd < 0) {
295                 return -1;
296         }
297
298         /* Stat the stream. */
299         ret = onefs_sys_fstat_at(dir_fd, stream, sbuf, flags);
300         if (ret != -1) {
301                 /* Now stat the base file and merge the results. */
302                 ret = onefs_sys_fstat(base_fd, &base_sbuf);
303                 if (ret != -1) {
304                         merge_stat(sbuf, &base_sbuf);
305                 }
306         }
307
308         saved_errno = errno;
309         close(dir_fd);
310         close(base_fd);
311         errno = saved_errno;
312         return ret;
313 }
314
315 int onefs_stat(vfs_handle_struct *handle, const char *path,
316                SMB_STRUCT_STAT *sbuf)
317 {
318         int ret;
319         bool is_stream;
320         char *base = NULL;
321         char *stream = NULL;
322
323         ret = onefs_is_stream(path, &base, &stream, &is_stream);
324         if (ret)
325                 return ret;
326
327         if (!is_stream) {
328                 ret = onefs_sys_stat(path, sbuf);
329         } else if (!stream) {
330                 /* If it's the ::$DATA stream just stat the base file name. */
331                 ret = onefs_sys_stat(base, sbuf);
332         } else {
333                 ret = stat_stream(handle->conn, base, stream, sbuf, 0);
334         }
335
336         onefs_adjust_stat_time(handle->conn, path, sbuf);
337         return ret;
338 }
339
340 int onefs_fstat(vfs_handle_struct *handle, struct files_struct *fsp,
341                 SMB_STRUCT_STAT *sbuf)
342 {
343         SMB_STRUCT_STAT base_sbuf;
344         int ret;
345
346         /* Stat the stream, by calling next_fstat on the stream's fd. */
347         ret = onefs_sys_fstat(fsp->fh->fd, sbuf);
348         if (ret == -1) {
349                 return ret;
350         }
351
352         /* Stat the base file and merge the results. */
353         if (fsp != NULL && fsp->base_fsp != NULL) {
354                 ret = onefs_sys_fstat(fsp->base_fsp->fh->fd, &base_sbuf);
355                 if (ret != -1) {
356                         merge_stat(sbuf, &base_sbuf);
357                 }
358         }
359
360         onefs_adjust_stat_time(handle->conn, fsp->fsp_name, sbuf);
361         return ret;
362 }
363
364 int onefs_lstat(vfs_handle_struct *handle, const char *path,
365                 SMB_STRUCT_STAT *sbuf)
366 {
367         int ret;
368         bool is_stream;
369         char *base = NULL;
370         char *stream = NULL;
371
372         ret = onefs_is_stream(path, &base, &stream, &is_stream);
373         if (ret)
374                 return ret;
375
376         if (!is_stream) {
377                 ret = onefs_sys_lstat(path, sbuf);
378         } else if (!stream) {
379                 /* If it's the ::$DATA stream just stat the base file name. */
380                 ret = onefs_sys_lstat(base, sbuf);
381         } else {
382                 ret = stat_stream(handle->conn, base, stream, sbuf,
383                                   AT_SYMLINK_NOFOLLOW);
384         }
385
386         onefs_adjust_stat_time(handle->conn, path, sbuf);
387         return ret;
388 }
389
390 int onefs_unlink(vfs_handle_struct *handle, const char *path)
391 {
392         int ret;
393         bool is_stream;
394         char *base = NULL;
395         char *stream = NULL;
396         int dir_fd, saved_errno;
397
398         ret = onefs_is_stream(path, &base, &stream, &is_stream);
399         if (ret) {
400                 return ret;
401         }
402
403         if (!is_stream) {
404                 return SMB_VFS_NEXT_UNLINK(handle, path);
405         }
406
407         /* If it's the ::$DATA stream just unlink the base file name. */
408         if (!stream) {
409                 return SMB_VFS_NEXT_UNLINK(handle, base);
410         }
411
412         dir_fd = get_stream_dir_fd(handle->conn, base, NULL);
413         if (dir_fd < 0) {
414                 return -1;
415         }
416
417         ret = enc_unlinkat(dir_fd, stream, ENC_DEFAULT, 0);
418
419         saved_errno = errno;
420         close(dir_fd);
421         errno = saved_errno;
422         return ret;
423 }
424
425 int onefs_vtimes_streams(vfs_handle_struct *handle, const char *fname,
426                          int flags, struct timespec times[3])
427 {
428         int ret;
429         bool is_stream;
430         char *base;
431         char *stream;
432         int dirfd;
433         int saved_errno;
434
435         START_PROFILE(syscall_ntimes);
436
437         ret = onefs_is_stream(fname, &base, &stream, &is_stream);
438         if (ret)
439                 return ret;
440
441         if (!is_stream) {
442                 ret = vtimes(fname, times, flags);
443                 return ret;
444         }
445
446         dirfd = get_stream_dir_fd(handle->conn, base, NULL);
447         if (dirfd < -1) {
448                 return -1;
449         }
450
451         ret = enc_vtimesat(dirfd, stream, ENC_DEFAULT, times, flags);
452
453         END_PROFILE(syscall_ntimes);
454
455         saved_errno = errno;
456         close(dirfd);
457         errno = saved_errno;
458         return ret;
459 }
460
461 int onefs_chflags(vfs_handle_struct *handle, const char *path,
462                   unsigned int flags)
463 {
464         char *base = NULL;
465         char *stream = NULL;
466
467         if (!NT_STATUS_IS_OK(onefs_split_ntfs_stream_name(talloc_tos(), path,
468                                                           &base, &stream))) {
469                 DEBUG(10, ("onefs_split_ntfs_stream_name failed\n"));
470                 errno = ENOMEM;
471                 return -1;
472         }
473
474         /*
475          * Only set the attributes on the base file.  ifs_createfile handles
476          * file creation attribute semantics.
477          */
478         return SMB_VFS_NEXT_CHFLAGS(handle, base, flags);
479 }
480
481 /*
482  * Streaminfo enumeration functionality
483  */
484 struct streaminfo_state {
485         TALLOC_CTX *mem_ctx;
486         vfs_handle_struct *handle;
487         unsigned int num_streams;
488         struct stream_struct *streams;
489         NTSTATUS status;
490 };
491
492 static bool add_one_stream(TALLOC_CTX *mem_ctx, unsigned int *num_streams,
493                            struct stream_struct **streams,
494                            const char *name, SMB_OFF_T size,
495                            SMB_OFF_T alloc_size)
496 {
497         struct stream_struct *tmp;
498
499         tmp = TALLOC_REALLOC_ARRAY(mem_ctx, *streams, struct stream_struct,
500                                    (*num_streams)+1);
501         if (tmp == NULL) {
502                 return false;
503         }
504
505         tmp[*num_streams].name = talloc_asprintf(mem_ctx, ":%s:%s", name,
506                                                  "$DATA");
507         if (tmp[*num_streams].name == NULL) {
508                 return false;
509         }
510
511         tmp[*num_streams].size = size;
512         tmp[*num_streams].alloc_size = alloc_size;
513
514         *streams = tmp;
515         *num_streams += 1;
516         return true;
517 }
518
519 static NTSTATUS walk_onefs_streams(connection_struct *conn, files_struct *fsp,
520                                    const char *fname,
521                                    struct streaminfo_state *state,
522                                    SMB_STRUCT_STAT *base_sbuf)
523 {
524         NTSTATUS status = NT_STATUS_OK;
525         bool opened_base_fd = false;
526         int base_fd = -1;
527         int dir_fd = -1;
528         int stream_fd = -1;
529         int ret;
530         SMB_STRUCT_DIR *dirp = NULL;
531         SMB_STRUCT_DIRENT *dp = NULL;
532         files_struct fake_fs;
533         struct fd_handle fake_fh;
534         SMB_STRUCT_STAT stream_sbuf;
535
536         ZERO_STRUCT(fake_fh);
537         ZERO_STRUCT(fake_fs);
538
539         /* If the base file is already open, use its fd. */
540         if ((fsp != NULL) && (fsp->fh->fd != -1)) {
541                 base_fd = fsp->fh->fd;
542         } else {
543                 opened_base_fd = true;
544         }
545
546         dir_fd = get_stream_dir_fd(conn, fname, &base_fd);
547         if (dir_fd < 0) {
548                 return map_nt_error_from_unix(errno);
549         }
550
551         /* Open the ADS directory. */
552         if ((dirp = fdopendir(dir_fd)) == NULL) {
553                 DEBUG(0, ("Error on opendir %s. errno=%d (%s)\n",
554                           fname, errno, strerror(errno)));
555                 status = map_nt_error_from_unix(errno);
556                 goto out;
557         }
558
559         /* Initialize the dir state struct and add it to the list.
560          * This is a layer violation, and really should be handled by a
561          * VFS_FDOPENDIR() call which would properly setup the dir state.
562          * But since this is all within the onefs.so module, we cheat for
563          * now and call directly into the readdirplus code.
564          * NOTE: This state MUST be freed by a proper VFS_CLOSEDIR() call. */
565         ret = onefs_rdp_add_dir_state(conn, dirp);
566         if (ret) {
567                 DEBUG(0, ("Error adding dir_state to the list\n"));
568                 status = map_nt_error_from_unix(errno);
569                 goto out;
570         }
571
572         fake_fs.conn = conn;
573         fake_fs.fh = &fake_fh;
574         fake_fs.fsp_name = SMB_STRDUP(fname);
575
576         /* Iterate over the streams in the ADS directory. */
577         while ((dp = SMB_VFS_READDIR(conn, dirp, NULL)) != NULL) {
578                 /* Skip the "." and ".." entries */
579                 if ((strcmp(dp->d_name, ".") == 0) ||
580                     (strcmp(dp->d_name, "..") == 0))
581                         continue;
582
583                 /* Open actual stream */
584                 if ((stream_fd = onefs_sys_create_file(conn,
585                                                          base_fd,
586                                                          dp->d_name,
587                                                          0,
588                                                          0,
589                                                          0,
590                                                          0,
591                                                          0,
592                                                          0,
593                                                          INTERNAL_OPEN_ONLY,
594                                                          0,
595                                                          NULL,
596                                                          0,
597                                                          NULL)) == -1) {
598                         DEBUG(0, ("Error opening stream %s:%s. "
599                                   "errno=%d (%s)\n", fname, dp->d_name, errno,
600                                   strerror(errno)));
601                         continue;
602                 }
603
604                 /* Figure out the stat info. */
605                 fake_fh.fd = stream_fd;
606                 ret = SMB_VFS_FSTAT(&fake_fs, &stream_sbuf);
607                 close(stream_fd);
608
609                 if (ret) {
610                         DEBUG(0, ("Error fstating stream %s:%s. "
611                                   "errno=%d (%s)\n", fname, dp->d_name, errno,
612                                   strerror(errno)));
613                         continue;
614                 }
615
616                 merge_stat(&stream_sbuf, base_sbuf);
617
618                 if (!add_one_stream(state->mem_ctx,
619                                     &state->num_streams, &state->streams,
620                                     dp->d_name, stream_sbuf.st_ex_size,
621                                     SMB_VFS_GET_ALLOC_SIZE(conn, NULL,
622                                                            &stream_sbuf))) {
623                         state->status = NT_STATUS_NO_MEMORY;
624                         break;
625                 }
626         }
627
628 out:
629         /* Cleanup everything that was opened. */
630         if (dirp != NULL) {
631                 SMB_VFS_CLOSEDIR(conn, dirp);
632         }
633         if (dir_fd >= 0) {
634                 close(dir_fd);
635         }
636         if (opened_base_fd) {
637                 SMB_ASSERT(base_fd >= 0);
638                 close(base_fd);
639         }
640
641         SAFE_FREE(fake_fs.fsp_name);
642         return status;
643 }
644
645 NTSTATUS onefs_streaminfo(vfs_handle_struct *handle,
646                           struct files_struct *fsp,
647                           const char *fname,
648                           TALLOC_CTX *mem_ctx,
649                           unsigned int *num_streams,
650                           struct stream_struct **streams)
651 {
652         SMB_STRUCT_STAT sbuf;
653         int ret;
654         NTSTATUS status;
655         struct streaminfo_state state;
656
657         /* Get a valid stat. */
658         if ((fsp != NULL) && (fsp->fh->fd != -1)) {
659                 if (is_ntfs_stream_name(fsp->fsp_name)) {
660                         return NT_STATUS_INVALID_PARAMETER;
661                 }
662                 ret = SMB_VFS_FSTAT(fsp, &sbuf);
663         } else {
664                 if (is_ntfs_stream_name(fname)) {
665                         return NT_STATUS_INVALID_PARAMETER;
666                 }
667                 ret = SMB_VFS_STAT(handle->conn, fname, &sbuf);
668         }
669
670         if (ret == -1) {
671                 return map_nt_error_from_unix(errno);
672         }
673
674         state.streams = NULL;
675         state.num_streams = 0;
676
677         if (lp_parm_bool(SNUM(handle->conn), PARM_ONEFS_TYPE,
678                 PARM_IGNORE_STREAMS, PARM_IGNORE_STREAMS_DEFAULT)) {
679                 goto out;
680         }
681
682         /* Add the default stream. */
683         if (S_ISREG(sbuf.st_ex_mode)) {
684                 if (!add_one_stream(mem_ctx,
685                                     &state.num_streams, &state.streams,
686                                     "", sbuf.st_ex_size,
687                                     SMB_VFS_GET_ALLOC_SIZE(handle->conn, fsp,
688                                                            &sbuf))) {
689                         return NT_STATUS_NO_MEMORY;
690                 }
691         }
692
693         state.mem_ctx = mem_ctx;
694         state.handle = handle;
695         state.status = NT_STATUS_OK;
696
697         /* If there are more streams, add them too. */
698         if (sbuf.st_ex_flags & UF_HASADS) {
699
700                 status = walk_onefs_streams(handle->conn, fsp, fname,
701                     &state, &sbuf);
702
703                 if (!NT_STATUS_IS_OK(status)) {
704                         TALLOC_FREE(state.streams);
705                         return status;
706                 }
707
708                 if (!NT_STATUS_IS_OK(state.status)) {
709                         TALLOC_FREE(state.streams);
710                         return state.status;
711                 }
712         }
713  out:
714         *num_streams = state.num_streams;
715         *streams = state.streams;
716         return NT_STATUS_OK;
717 }