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