/* * MountMgr Unix interface * * Copyright 2021 Alexandre Julliard * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #include "config.h" #include #include #include #include #include #include #include #include "unixlib.h" static char *get_dosdevices_path( const char *dev ) { const char *home = getenv( "HOME" ); const char *prefix = getenv( "WINEPREFIX" ); size_t len = (prefix ? strlen(prefix) : strlen(home) + strlen("/.wine")) + sizeof("/dosdevices/") + strlen(dev); char *path = malloc( len ); if (path) { if (prefix) strcpy( path, prefix ); else { strcpy( path, home ); strcat( path, "/.wine" ); } strcat( path, "/dosdevices/" ); strcat( path, dev ); } return path; } static BOOL is_valid_device( struct stat *st ) { #if defined(linux) || defined(__sun__) return S_ISBLK( st->st_mode ); #else /* disks are char devices on *BSD */ return S_ISCHR( st->st_mode ); #endif } static void detect_devices( const char **paths, char *names, ULONG size ) { while (*paths) { char unix_path[32]; unsigned int i = 0; for (;;) { int len = sprintf( unix_path, *paths, i++ ); if (len + 2 > size) break; if (access( unix_path, F_OK ) != 0) break; strcpy( names, unix_path ); names += len + 1; size -= len + 1; } paths++; } *names = 0; } /* find or create a DOS drive for the corresponding Unix device */ NTSTATUS add_drive( const char *device, enum device_type type, int *letter ) { char *path, *p; char in_use[26]; struct stat dev_st, drive_st; int drive, first, last, avail = 0; if (stat( device, &dev_st ) == -1 || !is_valid_device( &dev_st )) return STATUS_NO_SUCH_DEVICE; if (!(path = get_dosdevices_path( "a::" ))) return STATUS_NO_MEMORY; p = path + strlen(path) - 3; memset( in_use, 0, sizeof(in_use) ); switch (type) { case DEVICE_FLOPPY: first = 0; last = 2; break; case DEVICE_CDROM: case DEVICE_DVD: first = 3; last = 26; break; default: first = 2; last = 26; break; } while (avail != -1) { avail = -1; for (drive = first; drive < last; drive++) { if (in_use[drive]) continue; /* already checked */ *p = 'a' + drive; if (stat( path, &drive_st ) == -1) { if (lstat( path, &drive_st ) == -1 && errno == ENOENT) /* this is a candidate */ { if (avail == -1) { p[2] = 0; /* if mount point symlink doesn't exist either, it's available */ if (lstat( path, &drive_st ) == -1 && errno == ENOENT) avail = drive; p[2] = ':'; } } else in_use[drive] = 1; } else { in_use[drive] = 1; if (!is_valid_device( &drive_st )) continue; if (dev_st.st_rdev == drive_st.st_rdev) goto done; } } if (avail != -1) { /* try to use the one we found */ drive = avail; *p = 'a' + drive; if (symlink( device, path ) != -1) goto done; /* failed, retry the search */ } } free( path ); return STATUS_OBJECT_NAME_COLLISION; done: free( path ); *letter = drive; return STATUS_SUCCESS; } NTSTATUS get_dosdev_symlink( const char *dev, char *buffer, ULONG size ) { char *path; int ret; if (!(path = get_dosdevices_path( dev ))) return STATUS_NO_MEMORY; ret = readlink( path, buffer, size ); free( path ); if (ret == -1) return STATUS_NO_SUCH_DEVICE; if (ret == size) return STATUS_BUFFER_TOO_SMALL; buffer[ret] = 0; return STATUS_SUCCESS; } NTSTATUS set_dosdev_symlink( const char *dev, const char *dest ) { char *path; NTSTATUS status = STATUS_SUCCESS; if (!(path = get_dosdevices_path( dev ))) return STATUS_NO_MEMORY; if (dest && dest[0]) { unlink( path ); if (symlink( dest, path ) == -1) status = STATUS_ACCESS_DENIED; } else unlink( path ); free( path ); return status; } NTSTATUS get_volume_dos_devices( const char *mount_point, unsigned int *dosdev ) { struct stat dev_st, drive_st; char *path; int i; if (stat( mount_point, &dev_st ) == -1) return STATUS_NO_SUCH_DEVICE; if (!(path = get_dosdevices_path( "a:" ))) return STATUS_NO_MEMORY; *dosdev = 0; for (i = 0; i < 26; i++) { path[strlen(path) - 2] = 'a' + i; if (stat( path, &drive_st ) != -1 && drive_st.st_rdev == dev_st.st_rdev) *dosdev |= 1 << i; } free( path ); return STATUS_SUCCESS; } NTSTATUS read_volume_file( const char *volume, const char *file, void *buffer, ULONG *size ) { int ret, fd = -1; char *name = malloc( strlen(volume) + strlen(file) + 2 ); sprintf( name, "%s/%s", volume, file ); if (name[0] != '/') { char *path = get_dosdevices_path( name ); if (path) fd = open( path, O_RDONLY ); free( path ); } else fd = open( name, O_RDONLY ); free( name ); if (fd == -1) return STATUS_NO_SUCH_FILE; ret = read( fd, buffer, *size ); close( fd ); if (ret == -1) return STATUS_NO_SUCH_FILE; *size = ret; return STATUS_SUCCESS; } BOOL match_unixdev( const char *device, ULONGLONG unix_dev ) { struct stat st; return !stat( device, &st ) && st.st_rdev == unix_dev; } NTSTATUS detect_serial_ports( char *names, ULONG size ) { static const char *paths[] = { #ifdef linux "/dev/ttyS%u", "/dev/ttyUSB%u", "/dev/ttyACM%u", #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) "/dev/cuau%u", #elif defined(__DragonFly__) "/dev/cuaa%u", #endif NULL }; detect_devices( paths, names, size ); return STATUS_SUCCESS; } NTSTATUS detect_parallel_ports( char *names, ULONG size ) { static const char *paths[] = { #ifdef linux "/dev/lp%u", #endif NULL }; detect_devices( paths, names, size ); return STATUS_SUCCESS; } NTSTATUS set_shell_folder( const char *folder, const char *backup, const char *link ) { struct stat st; const char *home; char *homelink = NULL; NTSTATUS status = STATUS_SUCCESS; if (link && (!strcmp( link, "$HOME" ) || !strncmp( link, "$HOME/", 6 )) && (home = getenv( "HOME" ))) { link += 5; homelink = malloc( strlen(home) + strlen(link) + 1 ); strcpy( homelink, home ); strcat( homelink, link ); link = homelink; } /* ignore nonexistent link targets */ if (link && (stat( link, &st ) || !S_ISDIR( st.st_mode ))) { status = STATUS_OBJECT_NAME_NOT_FOUND; goto done; } if (!lstat( folder, &st )) /* move old folder/link out of the way */ { if (S_ISLNK( st.st_mode )) { unlink( folder ); } else if (link && S_ISDIR( st.st_mode )) { if (rmdir( folder )) /* non-empty dir, try to make a backup */ { if (!backup || rename( folder, backup )) { status = STATUS_OBJECT_NAME_COLLISION; goto done; } } } else goto done; /* nothing to do, folder already exists */ } if (link) symlink( link, folder ); else { if (backup && !lstat( backup, &st ) && S_ISDIR( st.st_mode )) rename( backup, folder ); else mkdir( folder, 0777 ); } done: free( homelink ); return status; } NTSTATUS get_shell_folder( const char *folder, char *buffer, ULONG size ) { int ret = readlink( folder, buffer, size - 1 ); if (ret < 0) return STATUS_OBJECT_NAME_NOT_FOUND; buffer[ret] = 0; return STATUS_SUCCESS; }