7a0a2fc1f589c921fd2c355072796164fff75bc0
[amitay/samba.git] / source3 / smbd / dmapi.c
1 /* 
2    Unix SMB/CIFS implementation.
3    DMAPI Support routines
4
5    Copyright (C) James Peach 2006
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 3 of the License, or
10    (at your option) any later version.
11    
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16    
17    You should have received a copy of the GNU General Public License
18    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "includes.h"
22 #include "smbd/globals.h"
23
24 #undef DBGC_CLASS
25 #define DBGC_CLASS DBGC_DMAPI
26
27 #ifndef USE_DMAPI
28
29 uint32 dmapi_file_flags(const char * const path) { return 0; }
30 bool dmapi_have_session(void) { return False; }
31 const void * dmapi_get_current_session(void) { return NULL; }
32
33 #else /* USE_DMAPI */
34
35 #ifdef HAVE_XFS_DMAPI_H
36 #include <xfs/dmapi.h>
37 #elif defined(HAVE_SYS_DMI_H)
38 #include <sys/dmi.h>
39 #elif defined(HAVE_SYS_JFSDMAPI_H)
40 #include <sys/jfsdmapi.h>
41 #elif defined(HAVE_SYS_DMAPI_H)
42 #include <sys/dmapi.h>
43 #elif defined(HAVE_DMAPI_H)
44 #include <dmapi.h>
45 #endif
46
47 #define DMAPI_SESSION_NAME "samba"
48 #define DMAPI_TRACE 10
49
50 struct smbd_dmapi_context {
51         dm_sessid_t session;
52         unsigned session_num;
53 };
54
55 /* 
56    Initialise DMAPI session. The session is persistant kernel state, 
57    so it might already exist, in which case we merely want to 
58    reconnect to it. This function should be called as root.
59 */
60 static int dmapi_init_session(struct smbd_dmapi_context *ctx)
61 {
62         char    buf[DM_SESSION_INFO_LEN];
63         size_t  buflen;
64         uint        nsessions = 5;
65         dm_sessid_t *sessions = NULL;
66         char    *version;
67         char    *session_name;
68         TALLOC_CTX *tmp_ctx = talloc_new(NULL);
69
70         int i, err;
71
72         if (ctx->session_num == 0) {
73                 session_name = talloc_strdup(tmp_ctx, DMAPI_SESSION_NAME);
74         } else {
75                 session_name = talloc_asprintf(tmp_ctx, "%s%u", DMAPI_SESSION_NAME,
76                                                ctx->session_num);
77         }
78
79         if (session_name == NULL) {
80                 DEBUG(0,("Out of memory in dmapi_init_session\n"));
81                 talloc_free(tmp_ctx);
82                 return -1;
83         }
84  
85
86         if (dm_init_service(&version) < 0) {
87                 DEBUG(0, ("dm_init_service failed - disabling DMAPI\n"));
88                 talloc_free(tmp_ctx);
89                 return -1;
90         }
91
92         ZERO_STRUCT(buf);
93
94         /* Fetch kernel DMAPI sessions until we get any of them */
95         do {
96                 dm_sessid_t *new_sessions;
97                 nsessions *= 2;
98                 new_sessions = TALLOC_REALLOC_ARRAY(tmp_ctx, sessions, 
99                                                     dm_sessid_t, nsessions);
100                 if (new_sessions == NULL) {
101                         talloc_free(tmp_ctx);
102                         return -1;
103                 }
104
105                 sessions = new_sessions;
106                 err = dm_getall_sessions(nsessions, sessions, &nsessions);
107         } while (err == -1 && errno == E2BIG);
108
109         if (err == -1) {
110                 DEBUGADD(DMAPI_TRACE,
111                         ("failed to retrieve DMAPI sessions: %s\n",
112                         strerror(errno)));
113                 talloc_free(tmp_ctx);
114                 return -1;
115         }
116
117         /* Look through existing kernel DMAPI sessions to find out ours */
118         for (i = 0; i < nsessions; ++i) {
119                 err = dm_query_session(sessions[i], sizeof(buf), buf, &buflen);
120                 buf[sizeof(buf) - 1] = '\0';
121                 if (err == 0 && strcmp(session_name, buf) == 0) {
122                         ctx->session = sessions[i];
123                         DEBUGADD(DMAPI_TRACE,
124                                 ("attached to existing DMAPI session "
125                                  "named '%s'\n", buf));
126                         break;
127                 }
128         }
129
130         /* No session already defined. */
131         if (ctx->session == DM_NO_SESSION) {
132                 err = dm_create_session(DM_NO_SESSION, 
133                                         session_name,
134                                         &ctx->session);
135                 if (err < 0) {
136                         DEBUGADD(DMAPI_TRACE,
137                                 ("failed to create new DMAPI session: %s\n",
138                                 strerror(errno)));
139                         ctx->session = DM_NO_SESSION;
140                         talloc_free(tmp_ctx);
141                         return -1;
142                 }
143
144                 DEBUG(0, ("created new DMAPI session named '%s' for %s\n",
145                           session_name, version));
146         }
147
148         if (ctx->session != DM_NO_SESSION) {
149                 set_effective_capability(DMAPI_ACCESS_CAPABILITY);
150         }
151
152         /* 
153            Note that we never end the DMAPI session. It gets re-used if possiblie. 
154            DMAPI session is a kernel resource that is usually lives until server reboot
155            and doesn't get destroed when an application finishes.
156
157            However, we free list of references to DMAPI sessions we've got from the kernel
158            as it is not needed anymore once we have found (or created) our session.
159          */
160
161         talloc_free(tmp_ctx);
162         return 0;
163 }
164
165 /*
166   Return a pointer to our DMAPI session, if available.
167   This assumes that you have called dmapi_have_session() first.
168 */
169 const void *dmapi_get_current_session(void)
170 {
171         if (!dmapi_ctx) {
172                 return NULL;
173         }
174
175         if (dmapi_ctx->session == DM_NO_SESSION) {
176                 return NULL;
177         }
178
179         return (void *)&dmapi_ctx->session;
180 }
181         
182 /*
183   dmapi_have_session() must be the first DMAPI call you make in Samba. It will
184   initialize DMAPI, if available, and tell you if you can get a DMAPI session.
185   This should be called in the client-specific child process.
186 */
187
188 bool dmapi_have_session(void)
189 {
190         if (!dmapi_ctx) {
191                 dmapi_ctx = talloc(NULL, struct smbd_dmapi_context);
192                 if (!dmapi_ctx) {
193                         exit_server("unable to allocate smbd_dmapi_context");
194                 }
195                 dmapi_ctx->session = DM_NO_SESSION;
196                 dmapi_ctx->session_num = 0;
197
198                 become_root();
199                 dmapi_init_session(dmapi_ctx);
200                 unbecome_root();
201
202         }
203
204         return dmapi_ctx->session != DM_NO_SESSION;
205 }
206
207 /*
208   only call this when you get back an EINVAL error indicating that the
209   session you are using is invalid. This destroys the existing session
210   and creates a new one.
211  */
212 bool dmapi_new_session(void)
213 {
214         if (dmapi_have_session()) {
215                 /* try to destroy the old one - this may not succeed */
216                 dm_destroy_session(dmapi_ctx->session);
217         }
218         dmapi_ctx->session = DM_NO_SESSION;
219         become_root();
220         dmapi_ctx->session_num++;
221         dmapi_init_session(dmapi_ctx);
222         unbecome_root();
223         return dmapi_ctx->session != DM_NO_SESSION;
224 }
225
226 /* 
227     only call this when exiting from master smbd process. DMAPI sessions
228     are long-lived kernel resources we ought to share across smbd processes.
229     However, we must free them when all smbd processes are finished to
230     allow other subsystems clean up properly. Not freeing DMAPI session
231     blocks certain HSM implementations from proper shutdown.
232 */
233 bool dmapi_destroy_session(void)
234 {
235         if (!dmapi_ctx) {
236                 return true;
237         }
238         if (dmapi_ctx->session != DM_NO_SESSION) {
239                 become_root();
240                 if (0 == dm_destroy_session(dmapi_ctx->session)) {
241                         dmapi_ctx->session_num--;
242                         dmapi_ctx->session = DM_NO_SESSION;
243                 } else {
244                         DEBUG(0,("Couldn't destroy DMAPI session: %s\n",
245                                  strerror(errno)));
246                 }
247                 unbecome_root();
248         }
249         return dmapi_ctx->session == DM_NO_SESSION;
250 }
251
252
253 /* 
254    This is default implementation of dmapi_file_flags() that is 
255    called from VFS is_offline() call to know whether file is offline.
256    For GPFS-specific version see modules/vfs_tsmsm.c. It might be
257    that approach on quering existence of a specific attribute that
258    is used in vfs_tsmsm.c will work with other DMAPI-based HSM 
259    implementations as well.
260 */
261 uint32 dmapi_file_flags(const char * const path)
262 {
263         int             err;
264         dm_eventset_t   events = {0};
265         uint            nevents;
266
267         dm_sessid_t     dmapi_session;
268         const void      *dmapi_session_ptr;
269         void            *dm_handle = NULL;
270         size_t          dm_handle_len = 0;
271
272         uint32          flags = 0;
273
274         dmapi_session_ptr = dmapi_get_current_session();
275         if (dmapi_session_ptr == NULL) {
276                 return 0;
277         }
278
279         dmapi_session = *(dm_sessid_t *)dmapi_session_ptr;
280         if (dmapi_session == DM_NO_SESSION) {
281                 return 0;
282         }
283
284         /* AIX has DMAPI but no POSIX capablities support. In this case,
285          * we need to be root to do DMAPI manipulations.
286          */
287 #ifndef HAVE_POSIX_CAPABILITIES
288         become_root();
289 #endif
290
291         err = dm_path_to_handle(CONST_DISCARD(char *, path),
292                 &dm_handle, &dm_handle_len);
293         if (err < 0) {
294                 DEBUG(DMAPI_TRACE, ("dm_path_to_handle(%s): %s\n",
295                             path, strerror(errno)));
296
297                 if (errno != EPERM) {
298                         goto done;
299                 }
300
301                 /* Linux capabilities are broken in that changing our
302                  * user ID will clobber out effective capabilities irrespective
303                  * of whether we have set PR_SET_KEEPCAPS. Fortunately, the
304                  * capabilities are not removed from our permitted set, so we
305                  * can re-acquire them if necessary.
306                  */
307
308                 set_effective_capability(DMAPI_ACCESS_CAPABILITY);
309
310                 err = dm_path_to_handle(CONST_DISCARD(char *, path),
311                         &dm_handle, &dm_handle_len);
312                 if (err < 0) {
313                         DEBUG(DMAPI_TRACE,
314                             ("retrying dm_path_to_handle(%s): %s\n",
315                             path, strerror(errno)));
316                         goto done;
317                 }
318         }
319
320         err = dm_get_eventlist(dmapi_session, dm_handle, dm_handle_len,
321                 DM_NO_TOKEN, DM_EVENT_MAX, &events, &nevents);
322         if (err < 0) {
323                 DEBUG(DMAPI_TRACE, ("dm_get_eventlist(%s): %s\n",
324                             path, strerror(errno)));
325                 dm_handle_free(dm_handle, dm_handle_len);
326                 goto done;
327         }
328
329         /* We figure that the only reason a DMAPI application would be
330          * interested in trapping read events is that part of the file is
331          * offline.
332          */
333         DEBUG(DMAPI_TRACE, ("DMAPI event list for %s\n", path));
334         if (DMEV_ISSET(DM_EVENT_READ, events)) {
335                 flags = FILE_ATTRIBUTE_OFFLINE;
336         }
337
338         dm_handle_free(dm_handle, dm_handle_len);
339
340         if (flags & FILE_ATTRIBUTE_OFFLINE) {
341                 DEBUG(DMAPI_TRACE, ("%s is OFFLINE\n", path));
342         }
343
344 done:
345
346 #ifndef HAVE_POSIX_CAPABILITIES
347         unbecome_root();
348 #endif
349
350         return flags;
351 }
352
353
354 #endif /* USE_DMAPI */