r14669: Remove duplicate source caused by running patch(1) once too often.
[tprouty/samba.git] / source / 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 2 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, write to the Free Software
19    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 */
21
22 #include "includes.h"
23
24 #undef DBGC_CLASS
25 #define DBGC_CLASS DBGC_DMAPI
26
27 #if defined(HAVE_LIBDM)
28 #if (defined(HAVE_XFS_DMAPI_H) || defined(HAVE_SYS_DMI_H))
29 #define USE_DMAPI 1
30 #endif
31 #endif
32
33 #ifndef USE_DMAPI
34
35 int dmapi_init_session(void) { return -1; }
36 uint32 dmapi_file_flags(const char * const path) { return 0; }
37 BOOL dmapi_have_session(void) { return False; }
38
39 #else /* USE_DMAPI */
40
41 #ifdef HAVE_XFS_DMAPI_H
42 #include <xfs/dmapi.h>
43 #endif
44
45 #ifdef HAVE_SYS_DMI_H
46 #include <sys/dmi.h>
47 #endif
48
49 #define DMAPI_SESSION_NAME "samba"
50 #define DMAPI_TRACE 10
51
52 static dm_sessid_t dmapi_session = DM_NO_SESSION;
53
54 /* Initialise the DMAPI interface. Make sure that we only end up initialising
55  * once per process to avoid resource leaks across different DMAPI
56  * implementations.
57  */
58 static int init_dmapi_service(void)
59 {
60         static pid_t lastpid;
61
62         pid_t mypid;
63
64         mypid = sys_getpid();
65         if (mypid != lastpid) {
66                 char *version;
67
68                 lastpid = mypid;
69                 if (dm_init_service(&version) < 0) {
70                         return -1;
71                 }
72
73                 DEBUG(0, ("Initializing DMAPI: %s\n", version));
74         }
75
76         return 0;
77 }
78
79 BOOL dmapi_have_session(void)
80 {
81         return dmapi_session != DM_NO_SESSION;
82 }
83
84 static dm_sessid_t *realloc_session_list(dm_sessid_t * sessions, int count)
85 {
86         dm_sessid_t *nsessions;
87
88         nsessions = TALLOC_REALLOC_ARRAY(NULL, sessions, dm_sessid_t, count);
89         if (nsessions == NULL) {
90                 TALLOC_FREE(sessions);
91                 return NULL;
92         }
93
94         return nsessions;
95 }
96
97 /* Initialise DMAPI session. The session is persistant kernel state, so it
98  * might already exist, in which case we merely want to reconnect to it. This
99  * function should be called as root.
100  */
101 int dmapi_init_session(void)
102 {
103         char    buf[DM_SESSION_INFO_LEN];
104         size_t  buflen;
105
106         uint        nsessions = 10;
107         dm_sessid_t *sessions = NULL;
108
109         int i, err;
110
111         /* If we aren't root, something in the following will fail due to lack
112          * of privileges. Aborting seems a little extreme.
113          */
114         SMB_WARN(getuid() == 0, "dmapi_init_session must be called as root");
115
116         dmapi_session = DM_NO_SESSION;
117         if (init_dmapi_service() < 0) {
118                 return -1;
119         }
120
121 retry:
122
123         if ((sessions = realloc_session_list(sessions, nsessions)) == NULL) {
124                 return -1;
125         }
126
127         err = dm_getall_sessions(nsessions, sessions, &nsessions);
128         if (err < 0) {
129                 if (errno == E2BIG) {
130                         nsessions *= 2;
131                         goto retry;
132                 }
133
134                 DEBUGADD(DMAPI_TRACE,
135                         ("failed to retrieve DMAPI sessions: %s\n",
136                         strerror(errno)));
137                 TALLOC_FREE(sessions);
138                 return -1;
139         }
140
141         for (i = 0; i < nsessions; ++i) {
142                 err = dm_query_session(sessions[i], sizeof(buf), buf, &buflen);
143                 buf[sizeof(buf) - 1] = '\0';
144                 if (err == 0 && strcmp(DMAPI_SESSION_NAME, buf) == 0) {
145                         dmapi_session = sessions[i];
146                         DEBUGADD(DMAPI_TRACE,
147                                 ("attached to existing DMAPI session "
148                                  "named '%s'\n", buf));
149                         break;
150                 }
151         }
152
153         TALLOC_FREE(sessions);
154
155         /* No session already defined. */
156         if (dmapi_session == DM_NO_SESSION) {
157                 err = dm_create_session(DM_NO_SESSION, DMAPI_SESSION_NAME,
158                                         &dmapi_session);
159                 if (err < 0) {
160                         DEBUGADD(DMAPI_TRACE,
161                                 ("failed to create new DMAPI session: %s\n",
162                                 strerror(errno)));
163                         dmapi_session = DM_NO_SESSION;
164                         return -1;
165                 }
166
167                 DEBUGADD(DMAPI_TRACE,
168                         ("created new DMAPI session named '%s'\n",
169                         DMAPI_SESSION_NAME));
170         }
171
172         /* Note that we never end the DMAPI session. This enables child
173          * processes to continue to use the session after we exit. It also lets
174          * you run a second Samba server on different ports without any
175          * conflict.
176          */
177
178         return 0;
179 }
180
181 /* Reattach to an existing dmapi session. Called from service processes that
182  * might not be running as root.
183  */
184 static int reattach_dmapi_session(void)
185 {
186         char    buf[DM_SESSION_INFO_LEN];
187         size_t  buflen;
188
189         if (dmapi_session != DM_NO_SESSION ) {
190                 become_root();
191
192                 /* NOTE: On Linux, this call opens /dev/dmapi, costing us a
193                  * file descriptor. Ideally, we would close this when we fork.
194                  */
195                 if (init_dmapi_service() < 0) {
196                         dmapi_session = DM_NO_SESSION;
197                         unbecome_root();
198                         return -1;
199                 }
200
201                 if (dm_query_session(dmapi_session, sizeof(buf),
202                             buf, &buflen) < 0) {
203                         /* Session is stale. Disable DMAPI. */
204                         dmapi_session = DM_NO_SESSION;
205                         unbecome_root();
206                         return -1;
207                 }
208
209                 set_effective_capability(DMAPI_ACCESS_CAPABILITY);
210
211                 DEBUG(DMAPI_TRACE, ("reattached DMAPI session\n"));
212                 unbecome_root();
213         }
214
215         return 0;
216 }
217
218 uint32 dmapi_file_flags(const char * const path)
219 {
220         static int attached = 0;
221
222         int             err;
223         dm_eventset_t   events = {0};
224         uint            nevents;
225
226         void    *dm_handle;
227         size_t  dm_handle_len;
228
229         uint32  flags = 0;
230
231         /* If a DMAPI session has been initialised, then we need to make sure
232          * we are attached to it and have the correct privileges. This is
233          * necessary to be able to do DMAPI operations across a fork(2). If
234          * it fails, there is no liklihood of that failure being transient.
235          *
236          * Note that this use of the static attached flag relies on the fact
237          * that dmapi_file_flags() is never called prior to forking the
238          * per-client server process.
239          */
240         if (dmapi_have_session() && !attached) {
241                 attached++;
242                 if (reattach_dmapi_session() < 0) {
243                         return 0;
244                 }
245         }
246
247         err = dm_path_to_handle(CONST_DISCARD(char *, path),
248                 &dm_handle, &dm_handle_len);
249         if (err < 0) {
250                 DEBUG(DMAPI_TRACE, ("dm_path_to_handle(%s): %s\n",
251                             path, strerror(errno)));
252
253                 if (errno != EPERM) {
254                         return 0;
255                 }
256
257                 /* Linux capabilities are broken in that changing our
258                  * user ID will clobber out effective capabilities irrespective
259                  * of whether we have set PR_SET_KEEPCAPS. Fortunately, the
260                  * capabilities are not removed from our permitted set, so we
261                  * can re-acquire them if necessary.
262                  */
263
264                 set_effective_capability(DMAPI_ACCESS_CAPABILITY);
265
266                 err = dm_path_to_handle(CONST_DISCARD(char *, path),
267                         &dm_handle, &dm_handle_len);
268                 if (err < 0) {
269                         DEBUG(DMAPI_TRACE,
270                             ("retrying dm_path_to_handle(%s): %s\n",
271                             path, strerror(errno)));
272                         return 0;
273                 }
274         }
275
276         err = dm_get_eventlist(dmapi_session, dm_handle, dm_handle_len,
277                 DM_NO_TOKEN, DM_EVENT_MAX, &events, &nevents);
278         if (err < 0) {
279                 DEBUG(DMAPI_TRACE, ("dm_get_eventlist(%s): %s\n",
280                             path, strerror(errno)));
281                 dm_handle_free(dm_handle, dm_handle_len);
282                 return 0;
283         }
284
285         /* We figure that the only reason a DMAPI application would be
286          * interested in trapping read events is that part of the file is
287          * offline.
288          */
289         DEBUG(DMAPI_TRACE, ("DMAPI event list for %s is %#llx\n",
290                     path, events));
291         if (DMEV_ISSET(DM_EVENT_READ, events)) {
292                 flags = FILE_ATTRIBUTE_OFFLINE;
293         }
294
295         dm_handle_free(dm_handle, dm_handle_len);
296
297         if (flags & FILE_ATTRIBUTE_OFFLINE) {
298                 DEBUG(DMAPI_TRACE, ("%s is OFFLINE\n", path));
299         }
300
301         return flags;
302 }
303
304 #endif /* USE_DMAPI */