As a part of an alignement at university I have to modify the function unlink_file located in /usr/src/minix/fs/mfs/link.c, so (under certain conditions) instead of removing files it just changes their name.
I have parent directory's inode, file's inode and its name passed to the function as parameters:
static int unlink_file(dirp, rip, file_name)
struct inode *dirp; /* parent directory of file */
struct inode *rip; /* inode of file, may be NULL too. */
char file_name[MFS_NAME_MAX]; /* name of file to be removed */
I thought of using the rename(2) syscall (which implementation is located in the same file in the function fs_rename), but I need an absolute path of the file in order to do so. Unfortunately, I don't know how to retrieve it from inode structure.
My question is: how can I retrieve an absolute path to the file by its inode? Or is there another way to rename a file from inside the unlink_file function?
notice what fs_rename does with message and that it acquires pointer to inodes.
unlink_file already has pointer to inode of file and pointer to directory where file is located. If you only have to rename it, you can check how fs_rename() acts, when both old_dirp and new_dirp are same
same_pdir == (old_dip == new_dirp); //somewhere in fs_rename()
(bunch of error checks..)
if(same_pdir){
r = search_dir(old_dirp, old_name, NULL, DELETE, IGN_PERM); // this deletes the file from directory
if(r == OK)
(void)search_dir(old_dirp, new_name, &numb, ENTER, IGN_PERM); //this creates file with new_name in the directory
}
Keep in mind this part of code assumes no file with name new_name currently exists in the directory (as in the error checks I've skipped such file gets removed)
Related
I'm trying to display the files of my current working directory in C, however, I also need to include the details of these files such as the date created and their file sizes.
Is there a function that does this in C?
So far here is my code:
int main(
DIR *d;
struct dirent *dir;
d = opendir(".");
if (d) {
while ((dir = readdir(d)) != NULL) {
printf("%s\n", dir->d_name);
}
closedir(d);
}
}
File information is handled not by dirent functions, but by stat().
Note stat() requires a pathname; combining the directory path with the file name from the dirent structure (using strncat) will give you the necessary argument. In this particular case, since file information is only needed for files in the current working directory, the path can be left off and the file name can be used directly as the argument (stat() will then look for the file in the current directory).
The stat struct has the various entries you're interested in, such as st_size for file size and st_ctime for change time (note the latter is a UNIX time, which you'll likely want to convert to a readable string).
In my code, I create a file with a random name using mkstemp() function (Im on Linux). What this function returns is an int being a file descriptor.
int fd;
char temp[] = "tempXXXXXX";
fd = mkstemp(temp);
Later I can access the file using fdopen() through that int file descriptor.
FILE *file_ptr = NULL;
file_ptr = fdopen(fd);
But at the end of my program, I would like to see if the file still exists with the random name it was given when I created it (the program should change that file name if successful). I can set a flag if the rename() function run on that file is successful, but I still don't know how to delete it when I only have its file descriptor.
if rename files => remove the temp file
How can I do that? Or is there a way to get the files name if I have its file descriptor?
Neither C nor POSIX (since you are using POSIX library functions) defines a way to delete a file via an open file descriptor. And that makes sense, because the kind of deletion you're talking about is actually to remove a directory entry, not the file itself. The same file can be hard linked into the directory tree in multiple places, with multiple names. The OS takes care of removing its data from storage, or at least marking it as available for reuse, after the last hard link to it is removed from the directory tree and no process any longer has it open.
A file descriptor is associated directly with a file, not with any particular path, notwithstanding the fact that under many circumstances, you obtain one via a path. This has several consequences, among them that once a process opens a file, that file cannot be pulled out from under it by manipulating the directory tree. And that is the basis for one of the standard approaches to your problem: unlink (delete) it immediately after opening it, before losing its name. Example:
#include <stdlib.h>
#include <unistd.h>
int make_temp_file() {
char filename[] = "my_temp_file_XXXXXX";
int fd;
fd = mkstemp(filename);
if (fd == -1) {
// handle failure to open ...
} else {
// file successfully opened, now unlink it
int result = unlink(filename);
// ... check for and handle error conditions ...
}
return fd;
}
Not only does that (nearly) ensure that the temp file does not outlive the need for it, but it also prevents the contents from being accessible to users and processes to which the owning process does not explicitly grant access.
Even though this doesn't exactly answer the question you're asking about mkstemp, consider creating a temporary file that will automatically be deleted, unless you rename it.
Instead of mkstemp you could call open combined with the creation flag O_TMPFILE to create a temporary, unnamed file that is automatically deleted when file is closed.
See open(2):
O_TMPFILE (since Linux 3.11)
Create an unnamed temporary regular file. The pathname argu‐
ment specifies a directory; an unnamed inode will be created
in that directory's filesystem. Anything written to the
resulting file will be lost when the last file descriptor is
closed, unless the file is given a name.
Instead of a filename, you call open with the path where you prefer to place the temporary file, like:
temp_fd = open("/path/to/dir", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
If you like to give the temporary file a permanent location/name, you can call linkat on it later:
linkat(temp_fd, NULL, AT_FDCWD, "/path/for/file", AT_EMPTY_PATH);
Note: Filesystem support is required for O_TMPFILE, but mainstream Linux filesystems do support it.
readlink provide you the name of your file depending of the file descriptor if you use the path /proc/self/fd/ adding you fd.
Then use remove for deleting the file passing the name readlink gave you
ssize_t readlink(const char *path, char *buf, size_t bufsiz); (also load ernno)
int remove(const char *filename); (returns zero is successful, otherwise nonzero)
I hope something like that could helped you ?
⚠ Don't copy/past this you must edit "filename"; _BUFFER, _BUFSIZE ⚠
#include<stdio.h>
#include <unistd.h>
#include <stdlib.h>
int delete_file(int fd) {
char *str_fd = itoa(fd, str_fd, 10);
char *path = strcat("/proc/self/fd/", str_fd);
if (read_link(path, buffer, bufsize) == -1)
return -1;
int del = remove(filename);
if (!del)
printf("The file is Deleted successfully");
else
printf("The file is not Deleted");
return 0;
}
(feel free to edit this, i didn't test the code and i let you handel the buffer and buffer size)
So i tried something and everything works fine if the file i'm searching doesn't have an extension in its name (i have a txt file named "instructions" and a fifo file named "myfifo" and they were found).
struct dirent *de=NULL;
DIR *dirr=NULL;
dirr=opendir(".");
while((de=readdir(dirr)) != NULL)
{
printf("%s\n",de->d_name);
if (strcmp(de->d_name,file)==0) printf("Found!\n");
// else printf("kkbesa\n");
}
the variable "file" is declared as char file[100] and i read it from the keyboard.
As i mentioned above, for the files "myfifo" and "instructions" it printed "Found", but when i go for something like "code.c" or "code.exe" it doesn't print anything(Yes, they exist in the current folder).
I am trying to rename a bunch of files in a user specified directory, but it only seems to be working when the user specifies the directory that the program is running from. For example, when running from the command line:
./a.out . "NewName.txt" will work, while
./a.out .. "NewName.txt" will not work. Is there a reason for this? It's on Linux, by the way.
int main(int argc, char** argv){
char* dirpath = argv[1];
char* newName = argv[2];
DIR *d;
struct dirent *dir;
d = opendir(dirpath);
if (d){
while ((dir = readdir(d)) != NULL){
char* filename = dir->d_name;
if (rename(filename,newName) == 0){
printf("Renaming %s -> %s\n",filename,newName);
} else {
printf("Could not rename %s\n",filename);
}
}
}
closedir(d);
}
I have also tried (while running the program from outside of Desktop):
if (rename("~/Desktop/test.txt","~/Desktop/test2.txt") == 0){
printf("Renaming %s -> %s\n",filename,newName);
} else {
printf("Could not rename %s\n",filename);
}
and it still fails.
While readdir() is reading file names from the other directory, your program's current directory is still in a different location. Unless you prefix the source file name with the path to the directory (and the destination file name too) you're trying to rename non-existent files in the current directory, in general.
In pseudo-code:
dir = opendir(remote_directory)
foreach name from dir
rename "remote_directory/name" to "remote_directory/othername"
end for
Note that the pseudo-code works if 'remote_directory' happens to be ., the current directory; you don't need to special-case that code.
I believe that your main problem is that the result from readdir is just the filename. It doesn't include the directory. You need to paste the directory name and the filename from dir->d_name together in your program.
From the documentation:
The old argument points to the pathname of the file to be renamed.
The new argument points to the new pathname of the file.
If the new argument does not resolve to an existing directory entry for a
file of type directory and the new argument contains at least one non-<slash>
character and ends with one or more trailing <slash> characters after all symbolic
links have been processed, rename() shall fail
Looks like you're not referring to an existing element when you use any path other than '.', which is likely why it's failing.
Check the specific errno value to see why.
I am writing a C program like,
void printdir (char*);
int main () {
printf ("Directory scan of /home: \n");
printdir ("/home/fahad/");
exit (0);
}
void printdir (char *dir) {
struct dirent *entry;
DIR *dp = opendir (dir);
if (dp == NULL) {
fprintf (stderr, "Cannot open dir:%s\n", dir);
return;
}
chdir (dir);
while ((entry = readdir(dp)) != NULL)
printf ("%s\n",entry -> d_name);
closedir (dp);
}
Interestingly, it shows output in an unexpected way.
Considering the fact that whenever a directory is created in UNIX. First two entries are created inside this directory one is . and other is ... So basically their inode numbers should be less than the directory entries created through mkdir () or open () (for directory and file respectively).
My question is, in what order readdir () system call reads the directory entries? Because I don't get first who entries . and ...
Why is that so?
Try skipping the "." and ".." entries, as follows:
DIR* dirp;
struct dirent *dp=NULL;
char* fname;
if( !(dirp=opendir(dname)) ) {
int ec=errno;
printf("completed:-1:cannot opendir %s (%d)\n",dname,ec);
return(-1);
}
while ((dp = readdir(dirp)) != NULL) {
if( strcmp(dp->d_name,".")==0 ) continue;
if( strcmp(dp->d_name,"..")==0 ) continue;
fname=dp->d_name;
sprintf(pathname,"%s/%s",dname,fname);
}
See this answer which notes that since the order is not stated as predictable, one should not assume any order. The above code will gives a sample of how to handle (avoid) these entries (in the typical use-case of traversing a directory hierarchy). The order is probably based upon the order of the files appearing in the directory inodes.
readdir() doesn't return entries in any particular order. As others mentioned, the order will depend on the particular file system in question.
For example, the Berkeley UFS file system uses an unsorted linked-list. See the description of the direct structure on page 744 of http://ptgmedia.pearsoncmg.com/images/0131482092/samplechapter/mcdougall_ch15.pdf. The binary content of a directory consists of a stream of variable-length records, each of which contains the inode number, record length, string length (of the filename) and the string data itself. readdir() works by walking the linked list (using the record length to know where each record begins relative to the previous record) and returning whatever it finds.
The list of records is not typically optimized, so filenames appear on the list (more or less) in the order the files were created. But not quite, because holes (resulting from deleted files) will be filled with new filenames if they are small enough to fit.
Now, not all file systems represent directories the way UFS does. A file system that keeps directory data in a binary tree may choose to implement readdir() as an in-order traversal of that tree, which would present files sorted by whatever attributes it uses as key for the tree. Or it might use a pre-order traversal, which would not return the records in a sorted order.
Because applications can not know the nature of the file system's implementation (and that each mounted volume can potentially use a different file system), applications should never assume anything about the order of entries that readdir() returns. If they require the entries to be sorted, they must read the entire directory into memory and do their own sorting.
This is why, for example, the ls command can take a long time to display output when run against a large directory. It needs to sort the entire list of names (and determine the longest name, in order to compute the column width) before it can display any output. This is also why ls -1U (disable sorting and display in one column) will produce output immediately on such directories.
First two entries are created inside this directory one is . and other is ... So basically their inode numbers should be less than the directory entries created through mkdir () or open ()(for directory and file respectively).
Yes, your understanding about the inode numbers is correct. To validate this we can write
simple c++ program to store the inode/name in map.
std::map<ino_t, std::string> entries;
std::pair<ino_t, std::string> en;
while ((entry = readdir(dp)) != NULL) {
en.first = entry->d_ino;
en.second = entry->d_name;
entries.insert(en);
printf ("%s\n",entry -> d_name);
}
"entries in GDB"
================
[5114862] = "..",
[5114987] = ".",
[5115243] = "taop",
[5115623] = "c++11_study",
[5115651] = "volume-3",
[5115884] = "gtkmm",
[5116513] = "basic",
[5116733] = "program",
[5116794] = "bakwas",
[5116813] = "a.out",
[5116818] = "foo",
This way we can validate about the order of inode number and "." & ".." are the less than
other directory & file entry.
My question is, in what order readdir () system call reads the directory entries? Because I don't get first who entries . and ... Why is that so?
From The Book "Advanced Programming in the UNIX® Environment by W. Richard Stevens",
we can get the following:
The opendir function initializes things so that the first readdir reads the first entry in the directory. The ordering of entries within the directory is implementation dependent and is usually not alphabetical. So their order are not defined and for the above program, readdir() gave in the following order.
Output from readdir()
=====================
c++11_study
taop
volume-3
basic
.
gtkmm
foo
program
a.out
..
bakwas