/* * ipl_tools - Linux for System z reipl and shutdown tools * * Scanner for /proc files * * Copyright IBM Corp. 2001, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #include #include #include #include #include #include #include #include #include "ipl_tools.h" #include "proc.h" static const char proc_part_filename[] = "/proc/partitions"; static const char proc_dev_filename[] = "/proc/devices"; struct file_buffer { char* buffer; off_t pos; size_t length; }; #define INITIAL_FILE_BUFFER_SIZE 1024 /* Read file into buffer without querying its size (necessary for reading files * from /proc or /sys). Upon success, return zero and set BUFFER to point to * the file buffer and SIZE (if non-null) to contain the file size. Return * non-zero otherwise. Add a null-byte at the end of the buffer if * NIL_TERMINATE is non-zero. */ static int misc_read_special_file(const char* filename, char** buffer, size_t* size, int nil_terminate) { FILE* file; char* data; char* new_data; size_t count; size_t current_size; int current; file = fopen(filename, "r"); if (file == NULL) ERR_EXIT_ERRNO("Could not open \"%s\"", filename); current_size = INITIAL_FILE_BUFFER_SIZE; count = 0; data = (char *) malloc(current_size); if (data == NULL) { fclose(file); return -1; } current = fgetc(file); while (current != EOF || nil_terminate) { if (current == EOF) { current = 0; nil_terminate = 0; } data[count++] = (char) current; if (count >= current_size) { new_data = (char *) malloc(current_size * 2); if (new_data == NULL) { free(data); fclose(file); return -1; } memcpy(new_data, data, current_size); free(data); data = new_data; current_size *= 2; } current = fgetc(file); } fclose(file); *buffer = data; if (size) *size = count; return 0; } /* Get the contents of a file and fill in the respective fields of * FILE. Return 0 on success, non-zero otherwise. */ static int get_file_buffer(struct file_buffer* file, const char *filename) { int rc; rc = misc_read_special_file(filename, &file->buffer, &file->length, 0); file->pos = 0; return rc; } /* Free resources allocated for file buffer identified by * FILE. */ static void free_file_buffer(struct file_buffer* file) { free(file->buffer); file->buffer = NULL; file->pos = 0; file->length = 0; } /* Return character at current FILE buffer position or EOF if at end of * file. */ static int current_char(struct file_buffer* file) { if (file->buffer != NULL) if (file->pos < (off_t) file->length) return file->buffer[file->pos]; return EOF; } /* Advance the current file pointer of file buffer FILE until the current * character is no longer a whitespace or until the end of line or file is * reached. Return 0 if at least one whitespace character was encountered, * non-zero otherwise. */ static int skip_whitespaces(struct file_buffer* file) { int rc; rc = -1; while ((current_char(file) != '\n') && isspace(current_char(file))) { rc = 0; file->pos++; } return rc; } /* Scan a positive integer number at the current position of file buffer FILE * and advance the position respectively. Upon success, return zero and set * NUMBER to contain the scanned number. Return non-zero otherwise. */ static int scan_number(struct file_buffer* file, size_t* number) { int rc; size_t old_number; *number=0; rc = -1; while (isdigit(current_char(file))) { rc = 0; old_number = *number; *number = *number * 10 + current_char(file) - '0'; /* Check for overflow */ if (old_number > *number) { rc = -1; break; } file->pos++; } return rc; } /* Scan a device node name at the current position of file buffer FILE and * advance the position respectively. Upon success, return zero and set * NAME to contain a copy of the scanned name. Return non-zero otherwise. */ static int scan_name(struct file_buffer* file, char** name) { off_t start_pos; start_pos = file->pos; while (!isspace(current_char(file)) && (current_char(file) != EOF)) file->pos++; if (file->pos > start_pos) { *name = (char *) malloc(file->pos - start_pos + 1); if (*name == NULL) return -1; memcpy((void *) *name, (void *) &file->buffer[start_pos], file->pos - start_pos); (*name)[file->pos - start_pos] = 0; return 0; } else return -1; } /* Scan for the specified STRING at the current position of file buffer FILE * and advance the position respectively. Upon success, return zero. Return * non-zero otherwise. */ static int scan_string(struct file_buffer* file, const char *string) { int i; for (i=0; string[i] && (current_char(file) == string[i]); i++, file->pos++); if (string[i] == '\0') return 0; return -1; } /* Advance the current file position to beginning of next line in file buffer * FILE or to end of file. */ static void skip_line(struct file_buffer* file) { while ((current_char(file) != '\n') && (current_char(file) != EOF)) file->pos++; if (current_char(file) == '\n') file->pos++; } /* Return non-zero if the current file position of file buffer FILE is at the * end of file. Return zero otherwise. */ static int eof(struct file_buffer* file) { return file->pos >= (off_t) file->length; } /* Scan a line of the specified /proc/partitions FILE buffer and advance the * current file position pointer respectively. If the current line matches * the correct pattern, fill in the corresponding data into ENTRY and return 0. * Return non-zero otherwise. */ static int scan_part_entry(struct file_buffer* file, struct proc_part_entry* entry) { int rc; size_t dev_major; size_t dev_minor; size_t blockcount; char* name; /* Scan for: (\s*)(\d+)(\s+)(\d+)(\s+)(\d+)(\s+)(\S+)(\.*)$ */ skip_whitespaces(file); rc = scan_number(file, &dev_major); if (rc) return rc; rc = skip_whitespaces(file); if (rc) return rc; rc = scan_number(file, &dev_minor); if (rc) return rc; rc = skip_whitespaces(file); if (rc) return rc; rc = scan_number(file, &blockcount); if (rc) return rc; rc = skip_whitespaces(file); if (rc) return rc; rc = scan_name(file, &name); if (rc) return rc; skip_line(file); entry->device = makedev(dev_major, dev_minor); entry->blockcount = blockcount; entry->name = name; return 0; } /* Release resources associated with ENTRY. */ void proc_part_free_entry(struct proc_part_entry* entry) { free(entry->name); entry->name = NULL; } /* Scan a line of the specified /proc/devices FILE buffer and advance the * current file position pointer respectively. If the current line matches * the correct pattern, fill in the corresponding data into ENTRY and return 0. * Return non-zero otherwise. */ static int scan_dev_entry(struct file_buffer* file, struct proc_dev_entry* entry, int blockdev) { int rc; size_t dev_major; char* name; /* Scan for: (\s*)(\d+)(\s+)(\S+)(\.*)$ */ skip_whitespaces(file); rc = scan_number(file, &dev_major); if (rc) return rc; rc = skip_whitespaces(file); if (rc) return rc; rc = scan_name(file, &name); if (rc) return rc; skip_line(file); entry->device = makedev(dev_major, 0); entry->name = name; entry->blockdev = blockdev; return 0; } /* Release resources associated with ENTRY. */ void proc_dev_free_entry(struct proc_dev_entry* entry) { free(entry->name); entry->name = NULL; } /* Scan /proc/partitions for an entry matching DEVICE. When there is a match, * store entry data in ENTRY and return 0. Return non-zero otherwise. */ int proc_part_get_entry(dev_t device, struct proc_part_entry* entry) { struct file_buffer file; int rc; rc = get_file_buffer(&file, proc_part_filename); if (rc) return rc; rc = -1; while (!eof(&file)) { if (scan_part_entry(&file, entry) == 0) { if (entry->device == device) { rc = 0; break; } proc_part_free_entry(entry); } else skip_line(&file); } free_file_buffer(&file); return rc; } /* Scan /proc/devices for a blockdevice (BLOCKDEV is 1) or a character * device (BLOCKDEV is 0) with a major number matching the major number of DEV. * When there is a match, store entry data in ENTRY and return 0. Return * non-zero otherwise. */ int proc_dev_get_entry(dev_t device, int blockdev, struct proc_dev_entry* entry) { struct file_buffer file; int rc; int scan_blockdev = 0; rc = get_file_buffer(&file, proc_dev_filename); if (rc) return rc; rc = -1; while (!eof(&file)) { if (scan_string(&file, "Block") == 0) { skip_line(&file); scan_blockdev = 1; continue; } else if (scan_dev_entry(&file, entry, scan_blockdev) == 0) { if ((major(entry->device) == major(device)) && blockdev == scan_blockdev) { rc = 0; break; } proc_dev_free_entry(entry); } else skip_line(&file); } free_file_buffer(&file); return rc; }