major() and minor() give different numbers from ls - c

Part of my C program is to output whether two device files are equal (i.e., same kind of device file and same major and minor numbers). It outputted that tty and tty2 are the same device files while I think they are not.
I added code to print out the retrieved major and minor numbers for each file and it printed out different numbers from what I got when I did ls -l /dev/tty and ls -l /dev/tty2. The major and minor numbers printed out for both tty and tty2 are 0 and 6, while using ls, they are 5 and 0 for tty and 4 and 2 for tty2.
I'm new to Linux and C.
I have double-checked the manpage for major() and minor() and it seemed that I used these functions correctly. So, I don't know what went wrong.
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#define report_error(x) puts("error")
#define BUFFER_SIZE 1<<16
int main(int argc, char *argv[])
{
struct stat statbuf1;
struct stat statbuf2;
char *fn1;
char *fn2;
if (argc < 3) {
if (argc < 1) {
report_error("no command line");
} else {
report_error("Not enough arguments");
}
}
fn1 = argv[1];
fn2 = argv[2];
if (lstat(fn1, &statbuf1)) {
report_error(strerror(errno));
}
if (lstat(fn2, &statbuf2)) {
report_error(strerror(errno));
}
if (S_ISCHR(statbuf1.st_mode) && S_ISCHR(statbuf2.st_mode)) {
unsigned int major1 = major(statbuf1.st_dev);
unsigned int major2 = major(statbuf2.st_dev);
unsigned int minor1 = minor(statbuf1.st_dev);
unsigned int minor2 = minor(statbuf2.st_dev);
printf("%d %d\n%d %d\n", major1, major2, minor1, minor2);
if (major1 == major2 && minor1 == minor2) {
printf("the two device files are equal\n");
exit(0);
}
}
return 0;
}

st_dev is the ID of device containing file, according to the man page. In other words, the device where the file's name resides. So it's the same as for your /dev directory, as you'll see if you use the stat command from your shell.
You're interested in st_rdev, which is Device ID (if special file) (again, from the man page).
The stat command shows both:
stat /dev/tty /dev/tty1
File: /dev/tty
Size: 0 Blocks: 0 IO Block: 4096 character special file
Device: 6h/6d Inode: 1035 Links: 1 Device type: 5,0
Access: (0620/crw--w----) Uid: ( 0/ root) Gid: ( 5/ tty)
....
File: /dev/tty1
Size: 0 Blocks: 0 IO Block: 4096 character special file
Device: 6h/6d Inode: 1044 Links: 1 Device type: 4,1
Access: (0620/crw--w----) Uid: ( 0/ root) Gid: ( 5/ tty)
Fixed code
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <unistd.h>
int get_char_device(const char *name,
unsigned *dev_major, unsigned *dev_minor)
{
struct stat buf;
if (stat(name, &buf)) {
perror(name);
return 1;
}
if (!S_ISCHR(buf.st_mode)) {
fprintf(stderr, "%s: not a char device\n", name);
return 1;
}
*dev_major = major(buf.st_rdev);
*dev_minor = minor(buf.st_rdev);
return 0;
}
int main(void)
{
unsigned int major1, minor1, major2, minor2;
if (get_char_device("/dev/tty1", &major1, &minor1) ||
get_char_device("/dev/tty2", &major2, &minor2)) {
return 1;
}
printf("%d %d\n%d %d\n", major1, major2, minor1, minor2);
if (major1 == major2 && minor1 == minor2) {
puts("the two device files are equal");
return 1;
}
}

Related

program that checks if the files match

My program check if two files (with a different path / name) match or not. The hard and symbolic links between the files are followed to determine it.
How can I modify the program to see what happens if the files I want to compare are device files? Or directories? Both seem valid uses. Also checking the device
#include <unistd.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
void printUsage()
{
printf("Use: ./leg <name_file1> <name_file2>.\n");
}
int createStat(const char *file, struct stat *fileStat)
{
if (stat(file, fileStat) < 0)
{
fprintf(stderr,"Error! File %s does not exist.\n", file);
return 0;
}
return 1;
}
int main(int argc, char **argv)
{
if (argc < 3)
{
printf("Insufficient number of parameters.\n");
printUsage();
return -1;
}
struct stat fileStat1;
struct stat fileStat2;
if (createStat(argv[1], &fileStat1) == 0)
{
return -1;
}
if (createStat(argv[2], &fileStat2) == 0)
{
return -1;
}
if (S_ISREG(fileStat1.st_mode) && S_ISREG(fileStat2.st_mode)) {
if ((fileStat1.st_dev == fileStat2.st_dev) && (fileStat1.st_ino == fileStat2.st_ino)) {
printf("Files '%s' and '%s' coincide.\n", argv[1], argv[2]);
} else
printf("Files '%s' and '%s' do not match.\n", argv[1], argv[2]);
} else
fprintf(stderr,"'%s' and '%s' there are no files.\n", argv[1], argv[2]);
return 0;
}
For regular files and directories, comparing the st_dev and st_ino fields is sufficient.
For paths representing character or block devices, comparing the st_dev and st_ino fields will tell you if they are the same file, ie: different paths to the same directory entry, including symbolic link indirections. Comparing the st_rdev fields will tell if they represent the same device, which is also useful but a different thing.
Also note that fprintf(stderr,"Error! File %s does not exist.\n", file); may produce a misleading message: access failure may be caused by other reasons. It is simple and efficient to produce the correct message this way:
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int createStat(const char *file, struct stat *fileStat) {
if (stat(file, fileStat) < 0) {
fprintf(stderr, "Cannot stat file %s: %s\n", file, strerror(errno));
return 0;
}
return 1;
}

How do I fix "copy: Bad file descriptor" in my C program

Hello I'm working on a program that prints all the characters like a cat program by using the POSIX functions, the program has to get more than one file when it does it writes the characters of all files in the destination file.
For example;
mycat.exe x.txt y.txt z.txt dest.txt
means write all the characters from x.txt, y.txt and z.txt in order to the file destination dest.txt that it's created after we run the program.
If any file not exits the program will not exit it prints a report that
file not exists
If any error occur, you have to exit the program.
When I compile it it compiles without any error(I'm compiling it in Windows 10), but when i try to run it it shows an error.
This is how I compile it:
gcc -o mycp.exe mycp.c
This is how i run it:
mycp.exe x.txt y.txt z.txt dest.txt
This is the error:
copy: Bad file descriptor
This is the code inside the mycp.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#define BUFSIZE 1024
void exit_sys(const char* msg)
{
perror(msg);
exit(EXIT_FAILURE);
}
void exit_fail(const char* msg)
{
fprintf(stderr, "%s\n", msg);
exit(EXIT_FAILURE);
}
void remove_dest_file(int fd, char** argv, int i)
{
_close(fd);
unlink(argv[i]);
}
int copy_file(int fdd, int fds)
{
char buf[BUFSIZE];
int n;
while ((n = read(fds, buf, BUFSIZE)) > 0)
{
_write(fdd, buf, n);
}
if (n < 0)
{
return n;
}
return 0;
}
int main(int argc, char** argv)
{
int fds, fdd;
int n;
char buf[BUFSIZE];
int ch;
int flags;
int argcm1;
int i;
flags = _O_WRONLY | _O_CREAT;
if (argc < 2)
exit_fail("usage:mycp.exe file1.exe file2.exe file3.exe ... filen filedest.exe");
argcm1 = argc - 1;
if (!access(argv[argcm1], F_OK)) {
printf("The file %s exists. Do you want to overwrite?[y]\n", argv[argcm1]);
ch = getchar();
if (ch == 'y' || ch == 'Y')
flags |= _O_TRUNC;
else
exit(EXIT_SUCCESS);
}
if ((fdd = _open(argv[argcm1], flags, _S_IREAD | _S_IWRITE)) < 0)
{
exit_sys("open for destination");
}
for (i = 0; i < argcm1; ++i)
{
if (fds = _open(argv[i], _O_RDONLY) < 0)
{
remove_dest_file(fdd, argv, argcm1);
exit_sys("open");
}
if (copy_file(fds, fdd) < 0)
{
remove_dest_file(fdd, argv, argcm1);
exit_sys("copy");
}
_close(fds);
}
printf("Succes");
_close(fdd);
return 0;
}

How to get device name on which a file is located from its path in c?

Let's say I have a file in Linux with this path:
/path/to/file/test.mp3
I want to know the path to its device. For example I want to get something like:
/dev/sdb1
How do I do this with the C programming language?
I know the terminal command to do it, but I need C functions that will do the job.
EDIT:
I have read this question before asking mine. It doesn't concretly mention code in C, it's more related to bash than to the C language.
Thanks.
You need to use stat on the file path, and get the device ID st_dev and match that to a device in /proc/partitions
Read this for how to interpret st_dev: https://web.archive.org/web/20171013194110/http://www.makelinux.net:80/ldd3/chp-3-sect-2
I just needed that inside a program I am writing...
So instead of running "df" and parsing the output, I wrote it from scratch.
Feel free to contribute!
To answer the question:
You first find the device inode using stat() then iterate and parse /proc/self/mountinfo to find the inode and get the device name.
/*
Get physical device from file or directory name.
By Zibri <zibri AT zibri DOT org>
https://github.com/Zibri/get_device
*/
#include <stdio.h>
#include <unistd.h>
#include <stdint.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <libgen.h>
int get_device(char *name)
{
struct stat fs;
if (stat(name, &fs) < 0) {
fprintf(stderr, "%s: No such file or directory\n", name);
return -1;
}
FILE *f;
char sline[256];
char minmaj[128];
sprintf(minmaj, "%d:%d ", (int) fs.st_dev >> 8, (int) fs.st_dev & 0xff);
f = fopen("/proc/self/mountinfo", "r");
if (f == NULL) {
fprintf(stderr, "Failed to open /proc/self/mountinfo\n");
exit(-1);
}
while (fgets(sline, 256, f)) {
char *token;
char *where;
token = strtok(sline, "-");
where = strstr(token, minmaj);
if (where) {
token = strtok(NULL, " -:");
token = strtok(NULL, " -:");
printf("%s\n", token);
break;
}
}
fclose(f);
return -1;
}
int main(int argc, char **argv)
{
if (argc != 2) {
fprintf(stderr, "Usage:\n%s FILE OR DIRECTORY...\n", basename(argv[0]));
return -1;
}
get_device(argv[1]);
return 0;
}
output is just the device name.
Example:
$ gcc -O3 getdevice.c -o gd -Wall
$ ./gd .
/dev/sda4
$ ./gd /mnt/C
/dev/sda3
$ ./gd /mnt/D
/dev/sdb1
$
Use this command to print the partition path:
df -P <pathname> | awk 'END{print $1}'

How not to open a file twice in linux?

I have a linked list with an fd and a string I used to open this file in each entry. I want to open and add files to this list only if this file is not already opened, because I open and parse this files and do not want to do it twice. My idea was to compare the filename with every single name in this list, but my program do it multiple times and one file in Linux can have multiple names (soft/hard links). I think it should not be so complicated, because its easy for the OS to check, whether I already used a inode or not, r?
I already tried to open the same file with and without flock, but I always get a new fd.
When you successfully open a file use fstat on the file. Check to see if the st_ino and st_dev of the struct stat filed in by fstat have already been recorded in your linked list. If so then close the file descriptor and move on to the next file. Otherwise add the file descriptor, the file name and st_ino and st_dev values to the list.
You can instead use stat to check before opening the file, but using fstat after will be slightly faster if the usual case is that file hasn't already been opened.
In situations like this, it's often useful to consider your data structures. Change to a data structure which does not allow duplicates, such as a hash table.
Maintain a set of which data you've seen before. I've used a hash table for this set. As per #RossRidge's answer, use the inode and device as the key. This allows duplicates to be discovered in O(1) time.
Here is an example implementation.
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <glib.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
static int get_fd(GHashTable *fds, const char *filename, int mode) {
int fd;
struct stat stat;
int keysize = 33;
char key[keysize]; /* Two 64 bit numbers as hex and a separator */
/* Resolve any symlinks */
char *real_filename = realpath(filename, NULL);
if( real_filename == NULL ) {
printf("%s could not be resolved.\n", filename);
return -1;
}
/* Open and stat */
fd = open( real_filename, mode );
if( fd < 0 ) {
printf("Could not open %s: %s.\n", real_filename, strerror(errno));
return -1;
}
if( fstat(fd, &stat) != 0 ) {
printf("Could not stat %s: %s.\n", real_filename, strerror(errno));
return -1;
}
/* Make a key for tracking which data we've processed.
This uses both the inode and the device it's on.
It could be done more efficiently as a bit field.
*/
snprintf(key, keysize, "%lx|%lx", (long int)stat.st_ino, (long int)stat.st_dev);
/* See if we've already processed that */
if( g_hash_table_contains(fds, key) ) {
return 0;
}
else {
/* Note that we've processed it */
g_hash_table_add(fds, key);
return fd;
}
}
int main(int argc, char** argv) {
int mode = O_RDONLY;
int fd;
GHashTable *fds = g_hash_table_new(&g_str_hash, &g_str_equal);
for(int i = 1; i < argc; i++) {
char *filename = argv[i];
fd = get_fd(fds, filename, mode);
if( fd == 0 ) {
printf("%s has already been processed.\n", filename);
}
else if( fd < 0 ) {
printf("%s could not be processed.\n", filename);
}
else {
printf("%s: %d\n", filename, fd);
}
}
}
And here's a sample result.
$ touch one two three
$ ln one one_link
$ ln -s two two_sym
$ ./test one* two* three*
one: 3
one_link has already been processed.
two: 5
two_sym has already been processed.
three: 7
As long as you don't close the successfully and intentionally opened files, you can use nonblocking flock to prevent another lock on the same file:
#include <unistd.h>
#include <sys/file.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <assert.h>
int openAndLock(const char* fn){
int fd = -1;
if(((fd = open(fn, O_RDONLY)) >= 0) && (flock(fd, LOCK_EX|LOCK_NB) == 0)){
fprintf(stderr, "Successfully opened and locked %s\n", fn);
return fd;
}else{
fprintf(stderr, "Failed to open or lock %s\n", fn);
close(fd);
return -1;
}
}
int main(int argc, char** argv){
for(int i=1; i<argc; i++){
openAndLock(argv[i]);
}
return 0;
}
Example:
$ touch foo
$ ln foo bar
$ ./a.out foo foo
Successfully opened and locked foo
Failed to open or lock foo
$ ./a.out foo bar
Successfully opened and locked foo
Failed to open or lock bar

Applying fork() and pipe() (or fifo()) on counting words code

I've completed writing of counting words code finally. It counts total number of words in files. (i.e. txt). Now, I want to use multiple fork() to access and read every file. I studied in the last week. Besides, I use global variable to hold number of counted words. As far as I know, If I apply fork(), used global variables are assigned as 0. To avoid it, I tried to use mmap() and similar functions this is okey. But, I also want to use pipe() also (fifo() if it is possible) to communicate (hold values of numbers).
I use nftw() function to go in folders and files. My logic is on the below picture. How can use fork() and pipe() (fifo()) on this code ? fork() is really complicated for me because of my inexperience. I'm new using of pipe() and fork(). According to my idea logic of the code is that if I can use fork() and pipe(), there will be fork() every file(i.e. txt) and access them by using fork. If there is another folder and there are files, again creates fork() from one of created forks , then access file. I try to explain also drawing below. Thank you. I want to learn using of them.
int countInEveryFolder(const char *dir)
is used because I don't know how to count files until the next folder in nftw() function. Number of files is necessary because it is number of fork.
Every folder should be parent of files. The files are included by the folder.
Code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <dirent.h>
#include <errno.h>
#include <ftw.h>
#include <ctype.h>
#include <sys/mman.h>
#include <locale.h>
#include <errno.h>
#define MAX_PATH_LEN 2048
unsigned long total_words = 0UL;
unsigned long total_dirs = 0UL;
unsigned long total_files = 0UL;
// Just proves counting number of file in a folder
int countInEveryFolder(const char *dir) {
struct stat stDirInfo;
struct dirent * stFiles;
DIR * stDirIn;
char szFullName[MAX_PATH_LEN];
char szDirectory[MAX_PATH_LEN];
struct stat stFileInfo;
int numOfFile = 0;
strncpy( szDirectory, dir, MAX_PATH_LEN - 1 );
if (lstat( szDirectory, &stDirInfo) < 0)
{
perror (szDirectory);
return 0;
}
if (!S_ISDIR(stDirInfo.st_mode))
return 0;
if ((stDirIn = opendir( szDirectory)) == NULL)
{
perror( szDirectory );
return 0;
}
while (( stFiles = readdir(stDirIn)) != NULL)
{
if (!strcmp(stFiles->d_name, ".") || !strcmp(stFiles->d_name, ".."))
continue;
sprintf(szFullName, "%s/%s", szDirectory, stFiles -> d_name );
if (lstat(szFullName, &stFileInfo) < 0)
perror ( szFullName );
/* is the file a directory? */
if (S_ISREG(stFileInfo.st_mode))
{
printf( "Filename: %s\n", szFullName );
numOfFile++;
}
} // end while
closedir(stDirIn);
return numOfFile;
}
// Count words in files.
unsigned long count_words_in_file(const char *const filename)
{
unsigned long count = 0UL;
int errnum = 0;
int c;
FILE *in;
in = fopen(filename, "rt");
if (in == NULL) {
errnum = errno;
fprintf(stderr, "%s: %s.\n", filename, strerror(errnum));
errno = errnum;
return 0UL;
}
/* Skip leading whitespace. */
do {
c = getc(in);
} while (isspace(c));
/* Token loop. */
while (c != EOF) {
/* This token is a word, if it starts with a letter. */
if (isalpha(c))
count++;
/* Skip the rest of this token. */
while (!isspace(c) && c != EOF)
c = getc(in);
/* Skip the trailing whitespace. */
while (isspace(c))
c = getc(in);
}
/* Paranoid checking for I/O errors. */
if (!feof(in) || ferror(in)) {
fclose(in);
fprintf(stderr, "Warning: %s: %s.\n", filename, strerror(EIO));
errnum = EIO;
} else
if (fclose(in)) {
fprintf(stderr, "Warning: %s: %s.\n", filename, strerror(EIO));
errnum = EIO;
}
errno = errnum;
return count;
}
// Recursively go in folders
int nftw_callback(const char *filepath, const struct stat *sb, int typeflag, struct FTW *ftwbuf)
{
// Directory
if (typeflag == FTW_DP || typeflag == FTW_D)
{
total_dirs++;
printf("%*s%s\n", ftwbuf->level * 4, "", filepath);
//countInEveryFolder(filepath);
}
// Folder
else if (typeflag == FTW_F)
{
total_files++;
total_words += count_words_in_file(filepath);
printf("%*s%s\n", ftwbuf->level * 4, "", filepath);
}
return 0;
}
/* Error message */
void err_sys(const char *msg)
{
perror(msg);
fflush(stdout);
exit(EXIT_FAILURE);
}
int main(int argc, char *argv[])
{
total_files = total_dirs = total_words = 0UL;
if (nftw(argv[1], nftw_callback, 15, FTW_PHYS) == 0) {
/* Success! */
printf("%s: %lu files, %lu directories, %lu words total.\n",
argv[1], total_files, total_dirs, total_words);
} else {
/* Failed... */
err_sys("ntfw");
}
putchar('\n');
//printf( "\nTotal words = %d\n\n", *wordCount);
//printf( "\nTotal folders = %d\n\n", *folderCount);
//printf( "\nTotal childs = %d\n\n", *childCount); //fork()
return 0;
}
To start I would write the program with two phases. A single-process phase in which all the file-paths are queued up (into a linked-list or dequeue), and a multi-process phase in which the worker processes receive work via their pipe() and send counts back to the main process via their pipe(). The main process would use select() to multiplex the input from its children.
Once you understand how to use select() with pipe()s, then work on having the filepath discovery be concurrent.
This design would be much easier to implement in Go, node.js, or greenlet with Python, but learning how to do it in C gives you a level of understanding for the underlying operations that you don't get with newer languages.

Resources