1/* 2 Unmount utility program for Linux CIFS VFS (virtual filesystem) client 3 Copyright (C) 2005 Steve French (sfrench@us.ibm.com) 4 5 This program is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published by 7 the Free Software Foundation; either version 3 of the License, or 8 (at your option) any later version. 9 10 This program is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with this program. If not, see <http://www.gnu.org/licenses/>. */ 17 18#ifndef _GNU_SOURCE 19#define _GNU_SOURCE 20#endif 21 22#include <stdlib.h> 23#include <stdio.h> 24#include <unistd.h> 25#include <ctype.h> 26#include <sys/types.h> 27#include <sys/mount.h> 28#include <sys/ioctl.h> 29#include <sys/stat.h> 30#include <sys/vfs.h> 31#include <fcntl.h> 32#include <getopt.h> 33#include <errno.h> 34#include <string.h> 35#include <mntent.h> 36#include <limits.h> 37#include "mount.h" 38 39#define UNMOUNT_CIFS_VERSION_MAJOR "0" 40#define UNMOUNT_CIFS_VERSION_MINOR "6" 41 42#ifndef UNMOUNT_CIFS_VENDOR_SUFFIX 43 #ifdef _SAMBA_BUILD_ 44 #include "version.h" 45 #ifdef SAMBA_VERSION_VENDOR_SUFFIX 46 #define UNMOUNT_CIFS_VENDOR_SUFFIX "-"SAMBA_VERSION_OFFICIAL_STRING"-"SAMBA_VERSION_VENDOR_SUFFIX 47 #else 48 #define UNMOUNT_CIFS_VENDOR_SUFFIX "-"SAMBA_VERSION_OFFICIAL_STRING 49 #endif /* SAMBA_VERSION_OFFICIAL_STRING and SAMBA_VERSION_VENDOR_SUFFIX */ 50 #else 51 #define UNMOUNT_CIFS_VENDOR_SUFFIX "" 52 #endif /* _SAMBA_BUILD_ */ 53#endif /* UNMOUNT_CIFS_VENDOR_SUFFIX */ 54 55#ifndef MNT_DETACH 56#define MNT_DETACH 0x02 57#endif 58 59#ifndef MNT_EXPIRE 60#define MNT_EXPIRE 0x04 61#endif 62 63#ifndef MOUNTED_LOCK 64#define MOUNTED_LOCK "/etc/mtab~" 65#endif 66#ifndef MOUNTED_TEMP 67#define MOUNTED_TEMP "/etc/mtab.tmp" 68#endif 69 70#define CIFS_IOC_CHECKUMOUNT _IO(0xCF, 2) 71#define CIFS_MAGIC_NUMBER 0xFF534D42 /* the first four bytes of SMB PDU */ 72 73static struct option longopts[] = { 74 { "all", 0, NULL, 'a' }, 75 { "help",0, NULL, 'h' }, 76 { "read-only", 0, NULL, 'r' }, 77 { "ro", 0, NULL, 'r' }, 78 { "verbose", 0, NULL, 'v' }, 79 { "version", 0, NULL, 'V' }, 80 { "expire", 0, NULL, 'e' }, 81 { "force", 0, 0, 'f' }, 82 { "lazy", 0, 0, 'l' }, 83 { "no-mtab", 0, 0, 'n' }, 84 { NULL, 0, NULL, 0 } 85}; 86 87const char * thisprogram; 88int verboseflg = 0; 89 90static void umount_cifs_usage(void) 91{ 92 printf("\nUsage: %s <remotetarget> <dir>\n", thisprogram); 93 printf("\nUnmount the specified directory\n"); 94 printf("\nLess commonly used options:"); 95 printf("\n\t-r\tIf mount fails, retry with readonly remount."); 96 printf("\n\t-n\tDo not write to mtab."); 97 printf("\n\t-f\tAttempt a forced unmount, even if the fs is busy."); 98 printf("\n\t-l\tAttempt lazy unmount, Unmount now, cleanup later."); 99 printf("\n\t-v\tEnable verbose mode (may be useful for debugging)."); 100 printf("\n\t-h\tDisplay this help."); 101 printf("\n\nOptions are described in more detail in the manual page"); 102 printf("\n\tman 8 umount.cifs\n"); 103 printf("\nTo display the version number of the cifs umount utility:"); 104 printf("\n\t%s -V\n",thisprogram); 105 printf("\nInvoking the umount utility on cifs mounts, can execute"); 106 printf(" /sbin/umount.cifs (if present and umount -i is not specified.\n"); 107} 108 109static int umount_check_perm(char * dir) 110{ 111 int fileid; 112 int rc; 113 114 /* allow root to unmount, no matter what */ 115 if(getuid() == 0) 116 return 0; 117 118 /* presumably can not chdir into the target as we do on mount */ 119 fileid = open(dir, O_RDONLY | O_DIRECTORY | O_NOFOLLOW, 0); 120 if(fileid == -1) { 121 if(verboseflg) 122 printf("error opening mountpoint %d %s",errno,strerror(errno)); 123 return errno; 124 } 125 126 rc = ioctl(fileid, CIFS_IOC_CHECKUMOUNT, NULL); 127 128 if(verboseflg) 129 printf("ioctl returned %d with errno %d %s\n",rc,errno,strerror(errno)); 130 131 if(rc == ENOTTY) { 132 printf("user unmounting via %s is an optional feature of",thisprogram); 133 printf(" the cifs filesystem driver (cifs.ko)"); 134 printf("\n\tand requires cifs.ko version 1.32 or later\n"); 135 } else if (rc != 0) 136 printf("user unmount of %s failed with %d %s\n",dir,errno,strerror(errno)); 137 close(fileid); 138 139 return rc; 140} 141 142static int remove_from_mtab(char * mountpoint) 143{ 144 int rc; 145 int num_matches; 146 FILE * org_fd; 147 FILE * new_fd; 148 struct mntent * mount_entry; 149 struct stat statbuf; 150 151 /* If it is a symlink, e.g. to /proc/mounts, no need to update it. */ 152 if ((lstat(MOUNTED, &statbuf) == 0) && (S_ISLNK(statbuf.st_mode))) 153 return 0; 154 155 /* Do we first need to check if it is writable? */ 156 157 atexit(unlock_mtab); 158 if (lock_mtab()) { 159 printf("Mount table locked\n"); 160 return -EACCES; 161 } 162 163 if(verboseflg) 164 printf("attempting to remove from mtab\n"); 165 166 org_fd = setmntent(MOUNTED, "r"); 167 if(org_fd == NULL) { 168 printf("Can not open %s\n",MOUNTED); 169 unlock_mtab(); 170 return -EIO; 171 } 172 173 new_fd = setmntent(MOUNTED_TEMP,"w"); 174 if(new_fd == NULL) { 175 printf("Can not open temp file %s", MOUNTED_TEMP); 176 endmntent(org_fd); 177 unlock_mtab(); 178 return -EIO; 179 } 180 181 /* BB fix so we only remove the last entry that matches BB */ 182 num_matches = 0; 183 while((mount_entry = getmntent(org_fd)) != NULL) { 184 if(strcmp(mount_entry->mnt_dir, mountpoint) == 0) { 185 num_matches++; 186 } 187 } 188 if(verboseflg) 189 printf("%d matching entries in mount table\n", num_matches); 190 191 /* Is there a better way to seek back to the first entry in mtab? */ 192 endmntent(org_fd); 193 org_fd = setmntent(MOUNTED, "r"); 194 195 if(org_fd == NULL) { 196 printf("Can not open %s\n",MOUNTED); 197 unlock_mtab(); 198 return -EIO; 199 } 200 201 while((mount_entry = getmntent(org_fd)) != NULL) { 202 if(strcmp(mount_entry->mnt_dir, mountpoint) != 0) { 203 addmntent(new_fd, mount_entry); 204 } else { 205 if(num_matches != 1) { 206 addmntent(new_fd, mount_entry); 207 num_matches--; 208 } else if(verboseflg) 209 printf("entry not copied (ie entry is removed)\n"); 210 } 211 } 212 213 if(verboseflg) 214 printf("done updating tmp file\n"); 215 rc = fchmod (fileno (new_fd), S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); 216 if(rc < 0) { 217 printf("error %s changing mode of %s\n", strerror(errno), 218 MOUNTED_TEMP); 219 } 220 endmntent(new_fd); 221 222 rc = rename(MOUNTED_TEMP, MOUNTED); 223 224 if(rc < 0) { 225 printf("failure %s renaming %s to %s\n",strerror(errno), 226 MOUNTED_TEMP, MOUNTED); 227 unlock_mtab(); 228 return -EIO; 229 } 230 231 unlock_mtab(); 232 233 return rc; 234} 235 236/* Make a canonical pathname from PATH. Returns a freshly malloced string. 237 It is up the *caller* to ensure that the PATH is sensible. i.e. 238 canonicalize ("/dev/fd0/.") returns "/dev/fd0" even though ``/dev/fd0/.'' 239 is not a legal pathname for ``/dev/fd0'' Anything we cannot parse 240 we return unmodified. */ 241static char * 242canonicalize(char *path) 243{ 244 char *canonical; 245 246 if (path == NULL) { 247 return NULL; 248 } 249 250 if (strlen(path) > PATH_MAX) { 251 fprintf(stderr, "Mount point string too long\n"); 252 return NULL; 253 } 254 255 canonical = (char *)malloc (PATH_MAX + 1); 256 257 if (!canonical) { 258 fprintf(stderr, "Error! Not enough memory!\n"); 259 return NULL; 260 } 261 262 if (realpath (path, canonical)) 263 return canonical; 264 265 strncpy (canonical, path, PATH_MAX); 266 canonical[PATH_MAX] = '\0'; 267 return canonical; 268} 269 270int main(int argc, char ** argv) 271{ 272 int c; 273 int rc; 274 int flags = 0; 275 int nomtab = 0; 276 int retry_remount = 0; 277 struct statfs statbuf; 278 char * mountpoint; 279 280 if(argc && argv) { 281 thisprogram = argv[0]; 282 } else { 283 umount_cifs_usage(); 284 return -EINVAL; 285 } 286 287 if(argc < 2) { 288 umount_cifs_usage(); 289 return -EINVAL; 290 } 291 292 if(thisprogram == NULL) 293 thisprogram = "umount.cifs"; 294 295 /* add sharename in opts string as unc= parm */ 296 297 while ((c = getopt_long (argc, argv, "afhilnrvV", 298 longopts, NULL)) != -1) { 299 switch (c) { 300/* No code to do the following option yet */ 301/* case 'a': 302 ++umount_all; 303 break; */ 304 case '?': 305 case 'h': /* help */ 306 umount_cifs_usage(); 307 exit(1); 308 case 'n': 309 ++nomtab; 310 break; 311 case 'f': 312 flags |= MNT_FORCE; 313 break; 314 case 'l': 315 flags |= MNT_DETACH; /* lazy unmount */ 316 break; 317 case 'e': 318 flags |= MNT_EXPIRE; /* gradually timeout */ 319 break; 320 case 'r': 321 ++retry_remount; 322 break; 323 case 'v': 324 ++verboseflg; 325 break; 326 case 'V': 327 printf ("umount.cifs version: %s.%s%s\n", 328 UNMOUNT_CIFS_VERSION_MAJOR, 329 UNMOUNT_CIFS_VERSION_MINOR, 330 UNMOUNT_CIFS_VENDOR_SUFFIX); 331 exit (0); 332 default: 333 printf("unknown unmount option %c\n",c); 334 umount_cifs_usage(); 335 exit(1); 336 } 337 } 338 339 /* move past the umount options */ 340 argv += optind; 341 argc -= optind; 342 343 mountpoint = canonicalize(argv[0]); 344 345 if((argc < 1) || (argv[0] == NULL)) { 346 printf("\nMissing name of unmount directory\n"); 347 umount_cifs_usage(); 348 return -EINVAL; 349 } 350 351 if(verboseflg) 352 printf("optind %d unmount dir %s\n",optind, mountpoint); 353 354 /* check if running effectively root */ 355 if(geteuid() != 0) { 356 printf("Trying to unmount when %s not installed suid\n",thisprogram); 357 if(verboseflg) 358 printf("euid = %d\n",geteuid()); 359 return -EACCES; 360 } 361 362 /* fixup path if needed */ 363 364 /* Trim any trailing slashes */ 365 while ((strlen(mountpoint) > 1) && 366 (mountpoint[strlen(mountpoint)-1] == '/')) 367 { 368 mountpoint[strlen(mountpoint)-1] = '\0'; 369 } 370 371 /* make sure that this is a cifs filesystem */ 372 rc = statfs(mountpoint, &statbuf); 373 374 if(rc || (statbuf.f_type != CIFS_MAGIC_NUMBER)) { 375 printf("This utility only unmounts cifs filesystems.\n"); 376 return -EINVAL; 377 } 378 379 /* check if our uid was the one who mounted */ 380 rc = umount_check_perm(mountpoint); 381 if (rc) { 382 printf("Not permitted to unmount\n"); 383 return rc; 384 } 385 386 if(umount2(mountpoint, flags)) { 387 /* remember to kill daemon on error */ 388 switch (errno) { 389 case 0: 390 printf("unmount failed but no error number set\n"); 391 break; 392 default: 393 printf("unmount error %d = %s\n",errno,strerror(errno)); 394 } 395 printf("Refer to the umount.cifs(8) manual page (man 8 umount.cifs)\n"); 396 return -1; 397 } else { 398 if(verboseflg) 399 printf("umount2 succeeded\n"); 400 if(nomtab == 0) 401 remove_from_mtab(mountpoint); 402 } 403 404 return 0; 405} 406 407