1 /*
2    BAREOS® - Backup Archiving REcovery Open Sourced
3 
4    Copyright (C) 2000-2012 Free Software Foundation Europe e.V.
5    Copyright (C) 2013-2014 Planets Communications B.V.
6    Copyright (C) 2013-2021 Bareos GmbH & Co. KG
7 
8    This program is Free Software; you can redistribute it and/or
9    modify it under the terms of version three of the GNU Affero General Public
10    License as published by the Free Software Foundation and included
11    in the file LICENSE.
12 
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16    General Public License for more details.
17 
18    You should have received a copy of the GNU Affero General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21    02110-1301, USA.
22 */
23 /*
24  * Kern Sibbald, MM
25  */
26 /**
27  * @file
28  * Windows File API device abstraction.
29  */
30 
31 #include "include/bareos.h"
32 #include "lib/berrno.h"
33 #include "stored/device_control_record.h"
34 #include "stored/stored.h"
35 #include "win32_file_device.h"
36 
37 namespace storagedaemon {
38 
39 /**
40  * (Un)mount the device (For a FILE device)
41  */
do_mount(DeviceControlRecord * dcr,bool mount,int dotimeout)42 static bool do_mount(DeviceControlRecord* dcr, bool mount, int dotimeout)
43 {
44   DeviceResource* device_resource = dcr->dev->device_resource;
45   PoolMem ocmd(PM_FNAME);
46   POOLMEM* results;
47   DIR* dp;
48   char* icmd;
49   struct dirent *entry, *result;
50   int status, tries, name_max, count;
51   BErrNo be;
52 
53   if (mount) {
54     icmd = device_resource->mount_command;
55   } else {
56     icmd = device_resource->unmount_command;
57   }
58 
59   dcr->dev->EditMountCodes(ocmd, icmd);
60 
61   Dmsg2(100, "do_mount: cmd=%s mounted=%d\n", ocmd.c_str(),
62         dcr->dev->IsMounted());
63 
64   if (dotimeout) {
65     /* Try at most 10 times to (un)mount the device. This should perhaps be
66      * configurable. */
67     tries = 10;
68   } else {
69     tries = 1;
70   }
71   results = GetMemory(4000);
72 
73   /* If busy retry each second */
74   Dmsg1(100, "do_mount run_prog=%s\n", ocmd.c_str());
75   while ((status = RunProgramFullOutput(ocmd.c_str(),
76                                         dcr->dev->max_open_wait / 2, results))
77          != 0) {
78     /* Doesn't work with internationalization (This is not a problem) */
79     if (mount && fnmatch("*is already mounted on*", results, 0) == 0) { break; }
80     if (!mount && fnmatch("* not mounted*", results, 0) == 0) { break; }
81     if (tries-- > 0) {
82       /* Sometimes the device cannot be mounted because it is already mounted.
83        * Try to unmount it, then remount it */
84       if (mount) {
85         Dmsg1(400, "Trying to unmount the device %s...\n",
86               dcr->dev->print_name());
87         do_mount(dcr, 0, 0);
88       }
89       Bmicrosleep(1, 0);
90       continue;
91     }
92     Dmsg5(100, "Device %s cannot be %smounted. status=%d result=%s ERR=%s\n",
93           dcr->dev->print_name(), (mount ? "" : "un"), status, results,
94           be.bstrerror(status));
95     Mmsg(dcr->dev->errmsg, _("Device %s cannot be %smounted. ERR=%s\n"),
96          dcr->dev->print_name(), (mount ? "" : "un"), be.bstrerror(status));
97 
98     /*
99      * Now, just to be sure it is not mounted, try to read the filesystem.
100      */
101     name_max = pathconf(".", _PC_NAME_MAX);
102     if (name_max < 1024) { name_max = 1024; }
103 
104     if (!(dp = opendir(device_resource->mount_point))) {
105       BErrNo be;
106       dcr->dev->dev_errno = errno;
107       Dmsg3(100, "do_mount: failed to open dir %s (dev=%s), ERR=%s\n",
108             device_resource->mount_point, dcr->dev->print_name(),
109             be.bstrerror());
110       goto get_out;
111     }
112 
113     entry = (struct dirent*)malloc(sizeof(struct dirent) + name_max + 1000);
114     count = 0;
115     while (1) {
116 #ifdef USE_READDIR_R
117       if ((Readdir_r(dp, entry, &result) != 0) || (result == NULL)) {
118 #else
119       result = readdir(dp);
120       if (result == NULL) {
121 #endif
122 
123         dcr->dev->dev_errno = EIO;
124         Dmsg2(129,
125               "do_mount: failed to find suitable file in dir %s (dev=%s)\n",
126               device_resource->mount_point, dcr->dev->print_name());
127         break;
128       }
129       if (!bstrcmp(result->d_name, ".") && !bstrcmp(result->d_name, "..")
130           && !bstrcmp(result->d_name, ".keep")) {
131         count++; /* result->d_name != ., .. or .keep (Gentoo-specific) */
132         break;
133       } else {
134         Dmsg2(129, "do_mount: ignoring %s in %s\n", result->d_name,
135               device_resource->mount_point);
136       }
137     }
138     free(entry);
139     closedir(dp);
140 
141     Dmsg1(100,
142           "do_mount: got %d files in the mount point (not counting ., .. and "
143           ".keep)\n",
144           count);
145 
146     if (count > 0) {
147       /* If we got more than ., .. and .keep */
148       /*   there must be something mounted */
149       if (mount) {
150         Dmsg1(100, "Did Mount by count=%d\n", count);
151         break;
152       } else {
153         /* An unmount request. We failed to unmount - report an error */
154         FreePoolMemory(results);
155         Dmsg0(200, "== error mount=1 wanted unmount\n");
156         return false;
157       }
158     }
159   get_out:
160     FreePoolMemory(results);
161     Dmsg0(200, "============ mount=0\n");
162     return false;
163   }
164 
165   FreePoolMemory(results);
166   Dmsg1(200, "============ mount=%d\n", mount);
167   return true;
168 }
169 
170 /**
171  * Mount the device.
172  *
173  * If timeout, wait until the mount command returns 0.
174  * If !timeout, try to mount the device only once.
175  */
176 bool win32_file_device::MountBackend(DeviceControlRecord* dcr, int timeout)
177 {
178   bool retval = true;
179 
180   if (RequiresMount() && device_resource->mount_command) {
181     retval = do_mount(dcr, true, timeout);
182   }
183 
184   return retval;
185 }
186 
187 /**
188  * Unmount the device
189  *
190  * If timeout, wait until the unmount command returns 0.
191  * If !timeout, try to unmount the device only once.
192  */
193 bool win32_file_device::UnmountBackend(DeviceControlRecord* dcr, int timeout)
194 {
195   bool retval = true;
196 
197   if (RequiresMount() && device_resource->unmount_command) {
198     retval = do_mount(dcr, false, timeout);
199   }
200 
201   return retval;
202 }
203 
204 int win32_file_device::d_open(const char* pathname, int flags, int mode)
205 {
206   return ::open(pathname, flags, mode);
207 }
208 
209 ssize_t win32_file_device::d_read(int fd, void* buffer, size_t count)
210 {
211   return ::read(fd, buffer, count);
212 }
213 
214 ssize_t win32_file_device::d_write(int fd, const void* buffer, size_t count)
215 {
216   return ::write(fd, buffer, count);
217 }
218 
219 int win32_file_device::d_close(int fd) { return ::close(fd); }
220 
221 int win32_file_device::d_ioctl(int fd, ioctl_req_t request, char* op)
222 {
223   return -1;
224 }
225 
226 boffset_t win32_file_device::d_lseek(DeviceControlRecord* dcr,
227                                      boffset_t offset,
228                                      int whence)
229 {
230   return ::_lseeki64(fd, (__int64)offset, whence);
231 }
232 
233 bool win32_file_device::d_truncate(DeviceControlRecord* dcr)
234 {
235   struct stat st;
236 
237   if (ftruncate(fd, 0) != 0) {
238     BErrNo be;
239 
240     Mmsg2(errmsg, _("Unable to truncate device %s. ERR=%s\n"), print_name(),
241           be.bstrerror());
242     return false;
243   }
244 
245   /*
246    * Check for a successful ftruncate() and issue a work-around for devices
247    * (mostly cheap NAS) that don't support truncation.
248    * Workaround supplied by Martin Schmid as a solution to bug #1011.
249    * 1. close file
250    * 2. delete file
251    * 3. open new file with same mode
252    * 4. change ownership to original
253    */
254   if (fstat(fd, &st) != 0) {
255     BErrNo be;
256 
257     Mmsg2(errmsg, _("Unable to stat device %s. ERR=%s\n"), print_name(),
258           be.bstrerror());
259     return false;
260   }
261 
262   if (st.st_size != 0) { /* ftruncate() didn't work */
263     PoolMem archive_name(PM_FNAME);
264 
265     PmStrcpy(archive_name, archive_device_string);
266     if (!IsPathSeparator(
267             archive_name.c_str()[strlen(archive_name.c_str()) - 1])) {
268       PmStrcat(archive_name, "/");
269     }
270     PmStrcat(archive_name, dcr->VolumeName);
271 
272     Mmsg2(errmsg,
273           _("Device %s doesn't support ftruncate(). Recreating file %s.\n"),
274           print_name(), archive_name.c_str());
275 
276     /*
277      * Close file and blow it away
278      */
279     ::close(fd);
280     SecureErase(dcr->jcr, archive_name.c_str());
281 
282     /*
283      * Recreate the file -- of course, empty
284      */
285     oflags = O_CREAT | O_RDWR | O_BINARY;
286     if ((fd = ::open(archive_name.c_str(), oflags, st.st_mode)) < 0) {
287       BErrNo be;
288 
289       dev_errno = errno;
290       Mmsg2(errmsg, _("Could not reopen: %s, ERR=%s\n"), archive_name.c_str(),
291             be.bstrerror());
292       Emsg0(M_FATAL, 0, errmsg);
293 
294       return false;
295     }
296 
297     /*
298      * Reset proper owner
299      */
300     chown(archive_name.c_str(), st.st_uid, st.st_gid);
301   }
302 
303   return true;
304 }
305 
306 win32_file_device::win32_file_device() {}
307 
308 } /* namespace storagedaemon */
309