propagator/lomount.c
Alexey Sheplyakov db44ebdc74 Avoid races between init mounting loopback device and udev probing it
LOOP_SET_FD, LOOP_SET_STATUS ioctls trigger a `change` event with
loopback device in question. udev handles those events with
(builtin) blkid command. Probing a device with blkid takes a while,
so init might try to mount the loopback device in question while
`blkid` is still running. As a result init and udev block each other.
Eventually (after 3 minutes or whatever udev event timeout is)
`blkid` gets killed and boot proceeds. However such long delays are
very annoying. Therefore run `udev_settle` after each loop related
ioctl to avoid the concurrent access to the same loopback device.

Closes: #40687
2021-08-19 20:00:34 +04:00

193 lines
4.1 KiB
C

/*
* Guillaume Cottenceau (gc@mandrakesoft.com)
*
* Copyright 2000 MandrakeSoft
*
* This software may be freely redistributed under the terms of the GNU
* public license.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
/* This code comes from util-linux-2.10n (mount/lomount.c)
* (this is a simplified version of this code)
*/
#include <sys/types.h>
#include <sys/mount.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "stage1.h"
#include "frontend.h"
#include "log.h"
#include "mount.h"
#include "modules.h"
#include "udev.h"
#include "lomount.h"
#define LO_NAME_SIZE 64
#define LO_KEY_SIZE 32
struct loop_info
{
int lo_number; /* ioctl r/o */
dev_t lo_device; /* ioctl r/o */
unsigned long lo_inode; /* ioctl r/o */
dev_t lo_rdevice; /* ioctl r/o */
int lo_offset;
int lo_encrypt_type;
int lo_encrypt_key_size; /* ioctl w/o */
int lo_flags; /* ioctl r/o */
char lo_name[LO_NAME_SIZE];
unsigned char lo_encrypt_key[LO_KEY_SIZE]; /* ioctl w/o */
unsigned long lo_init[2];
char reserved[4];
};
#define LOOP_SET_FD 0x4C00
#define LOOP_CLR_FD 0x4C01
#define LOOP_SET_STATUS 0x4C02
#define LOOP_GET_STATUS 0x4C03
int
set_loop (const char *device, const char *file)
{
struct loop_info loopinfo;
int i, fd, ffd, mode;
mode = O_RDONLY;
if ((ffd = open (file, mode)) < 0)
return 1;
for (i=3; i && (fd = open(device, mode)) < 0; --i, sleep(1));
if (fd < 0) {
close(ffd);
return 1;
}
memset(&loopinfo, 0, sizeof (loopinfo));
strncpy(loopinfo.lo_name, file, LO_NAME_SIZE);
loopinfo.lo_name[LO_NAME_SIZE - 1] = 0;
loopinfo.lo_offset = 0;
#ifdef MCL_FUTURE
/*
* Oh-oh, sensitive data coming up. Better lock into memory to prevent
* passwd etc being swapped out and left somewhere on disk.
*/
if(mlockall(MCL_CURRENT|MCL_FUTURE)) {
log_message("CRITICAL Couldn't lock into memory! %s (memlock)", strerror(errno));
return 1;
}
#endif
if (ioctl(fd, LOOP_SET_FD, ffd) < 0) {
close(fd);
close(ffd);
return 1;
}
/* Note: LOOP_SET_FD triggers change event. While processing it
* udev reads the loop device with builtin blkid. This can race
* with subsequent access by kernel due to LOOP_SET_STATUS (or
* mounting the loop device). Therefore give udev a chance to
* process the event without concurrent access to the loop device
*/
udev_settle();
if (ioctl(fd, LOOP_SET_STATUS, &loopinfo) < 0) {
(void) ioctl (fd, LOOP_CLR_FD, 0);
close(fd);
close(ffd);
return 1;
}
/* Same here: LOOP_SET_STATUS triggers change event, give udev
* a chance to process it without concurrent access to the loop
* device. In other words, prevent the caller of this function
* from mounting the device while udev still running blkid on it
*/
udev_settle();
close(fd);
close(ffd);
/* udev might be monitoring loopback device (with fanotify) and
* will synthesize a change event on close. Give udev some time
* to process without racing with the caller of this function
* (which is going to mount the newly configured loopback device)
*/
udev_settle();
return 0;
}
char * loopdev = "/dev/loop3"; /* Ugly. But do I care? */
void del_loop(char *device)
{
int fd;
if ((fd = open (device, O_RDONLY)) < 0)
return;
if (ioctl (fd, LOOP_CLR_FD, 0) < 0)
return;
close (fd);
}
static char * where_mounted = NULL;
int
lomount(char *loopfile, char *where)
{
long int flag;
flag = MS_MGC_VAL;
flag |= MS_RDONLY;
my_insmod("loop", NULL);
if (set_loop(loopdev, loopfile)) {
log_message("set_loop failed on %s (%s)", loopdev, strerror(errno));
return 1;
}
if (my_mount(loopdev, where, "iso9660", 0)) {
del_loop(loopdev);
return 1;
}
where_mounted = strdup(where);
log_message("lomount succeeded for %s on %s", loopfile, where);
return 0;
}
int
loumount()
{
if (where_mounted) {
umount(where_mounted);
where_mounted = NULL;
}
del_loop(loopdev);
return 0;
}