LINUX环境(四)--临时文件与用户信息

临时文件

通常,程序需要以文件的形式使用临时存储。这也许是存储计算的中间结果,或者是在实际操作之前所做的文件拷贝备份。例如,一个数据程序在删除记录时会使用临时文件。文件会收集需要保存的数据库实体,然后在操作结束时,临时文件会成为新的数据库而原始的会被删除。

临时文件的大量使用隐藏了他的一个缺点。我们必须小心来确保程序会选择一个唯一的名字来使用临时文件。如果不是这样,因为Linux是一个多任务系统,也许会有另一个程序选择了相同的名字,而这两个彼此之间会相互影响。

一个唯一的临时文件名可以由tmpnam函数生成:

#include <stdio.h>
char *tmpnam(char *s);

tmpnam 函数会返回一个与现存的文件不同的可用的文件名。如果字符串不为null,文件名也就会被写入其中。后续的tmpnam函数调用会覆盖返回值所用的表态存 储区,所以如果tmpnam函数被调用多次,实际上是使用一个字符串参数。这个字符串假定至少为L_tmpnam字符长。一个程序中,tmpnam至多可 以被调用TMP_MAX次,而且每次都会生成一个不同的文件名。

如果临时文件被立即使用,那么我们可以同时使用tmpfile函数来对其命名并且打开。这一点很重要,因为另一个程序会使用tmpnam返回的相同的名字创建一个文件。tmpfile函数避免这种情况的发生:

#include <stdio.h>
FILE *tmpfile(void);

tmpfile函数会返回一个指向唯一的临时文件的流指针。这个文件会为读和写打开,而且在所有到文件的引用被关闭以后,这个文件会被自动删除。

如果发生错误,tmpfile会返回一个空指针,并且设置errno变量。

试验--tmpnam与tmpfile

下面让我们实际的看一下这两个函数的使用:

#include <stdio.h>
int main()
{
    char tmpname[L_tmpnam];
    char *filename;
    FILE *tmpfp;
    filename = tmpnam(tmpname);
    printf(“Temporary file name is: %s\n”, filename);
    tmpfp = tmpfile();
    if(tmpfp)
        printf(“Opened a temporary file OK\n”);
    else
        perror(“tmpfile”);
    exit(0);
}

当我们编译运行程序tmpnam.c时,我们可以看到由tmpnam生成的唯一的文件名:

$ ./tmpnam
Temporary file name is: /tmp/file2S64zc
Opened a temporary file OK

工作原理

这 个程序调用tmpnam生成一个唯一文件名的临时文件。如果我们要使用这个临时文件,我们可以立即打开,从而来减小另一个程序会使用同一个文件名打开这个 文件的风险。tmpfile调用会同时创建并打开一个临时文件,从而避免了这种风险。实际上,当编译一个使用这个函数的程序时,GNU C编译器也会给出一个使用tmpnam的警告。

老版本Unix系统还有另一个使用mktemp与mkstemp函数来生成临时文件名的方法。这些与被Linux系统支持,并且与tmpnam相似,所不同的是我们可以为临时文件名指定一个模板,这样我们就可以更好的控制其位置与名字:

#include <stdlib.h>
char *mktemp(char *template);
int mkstemp(char *template);

mktemp函数由指定的模板生成一个唯一的文件名。template必须以6个x开始。mktemp函数使用唯一可用的文件名字符来替换这些x字符。他返回一个指向所生成的字符串的指针,如果不可以生成一个唯一的文件名,则会返回一个null指针。

mkstemp函数在创建与打开临时文件方面与tmpfile相类似。文件名由与mktemp相同的方式生成的,但是返回的结果是一个打开的,底层文件描述符。

通常,我们应使用创建与打开函数tmpfile与mkstemp,而不是tmpnam与mktemp。

 

使用临时文件要考虑几个问题:

  1. 保证临时文件间的文件名不互助冲突。
  2. 保证临时文件中内容不被其他用户或者黑客偷看、删除和修改。

Linux中提供了mkstemp 和 tmpfile 函数来处理临时文件。

mkstemp函数

int mkstemp(char *template);

mkstemp函数在系统中以唯一的文件名创建一个文件并打开,而且只有当前用户才能访问这个临时文件,并进行读、写操作。mkstemp函数只有一个参数,这个参数是个以“XXXXXX”结尾的非空字符串。mkstemp函数会用随机产生的字符串替换“XXXXXX”,保证了文件名的唯一性。 函数返回一个文件描述符,如果执行失败返回-1。在glibc 2.0.6 以及更早的glibc库中这个文件的访问权限是0666,glibc 2.0.7以后的库这个文件的访问权限是0600。

临时文件使用完成后应及时删除,否则临时文件目录会塞满垃圾。由于mkstemp函数创建的临时文件不能自动删除,所以执行完mkstemp函数后要调用unlink函数,unlink函数删除文件的目录入口,但临时文件还可以通过文件描述符进行访问,直到最后一个打开的进程关闭文件操作符,或者程序退出后临时文件被自动彻底地删除。

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

int write_temp_file(char* buffer,size_t length) {
    int len=length;
    char filename_template[]="/tmp/temp_file.XXXXXX";
    int fd=mkstemp(filename_template);
    unlink(filename_template);//Unlink the file, so it'll be removed when close
    printf("Template file name:%s\n",filename_template);
    write(fd,&len,sizeof(len));
    write(fd,buffer,len);
    return fd;
}

char* read_temp_file(int fd, size_t* length) {
    char* buffer;
    lseek(fd,0,SEEK_SET);
    read(fd,length,sizeof(size_t));
    buffer=(char*)malloc(*length);
    read(fd,buffer,*length);
    close(fd); // Temp file will be deleted
    return buffer;
}

int main(int argc, char** argv) {
    char buffer[]="Test template files";
    int fd=write_temp_file(buffer,strlen(buffer));
    int len=0;
    char* result=read_temp_file(fd,&len);
    printf("Len:%d\nContent:%s\n",len,result);
    free(result);
    return 0;
}

tmpfile函数

如果您使用C library I/O函数,并且并没有另一个程序使用这个临时文件,有个更简洁的函数——tmpfile。tmpfile函数创建并打开一个临时文件,并且自动执行了unlink。tmpfile函数返回一个文件描述符,如果执行失败返回NULL。当程序执行了fclose或者退出时,资源被释放。

另外,linux系统中还提供mktemp、 tmpnam、 和tempnam等函数,但是由于健壮性和安全性的问题,不建议使用。

 



用户信息

所 有了Linux程序,除了init之外,都是由其他程序或用户启动的。我们将会在第11章中了解更多的如何运行程序,或进程,交互等内容。用户通常由一个 负责命令的shell来启动程序。我们已经看到,程序可以通过检测其环境变量以及读取系统时钟来确定其环境信息。一个程序也可以查看正使用他的用户信息。

当一个用户登陆进Linux系统时,他就有一个用户名与密码。如果这些通过验证,系统就会用户提供一个shell。通常,用户具有一个称之为UID的唯一的用户标识。Linux运行的所有程序都是运行在用户的行为以及与其相关的UID上的。

我 们可以设置一个程序的运行,使其看起来就像是另一个用户启动的。当一个程序具有其UID的权限集合时,他的运行看起来就像是可执行文件的拥有者启动的。当 执行su命令时,程序的运行看起来就像是超级用户启动的。然后他会验证用户的访问权限,将其UID改变为目标用户的UID,然后执行目标用户的登陆 shell。这会允许一个程序的运行看起来就像是另一个不同的用户启动的,而这通常为系统管理员用来执行维护任务。

因为UID是用户标识的关键,我们就从这里开始讨论。

UID有其自己的类型--uid_t--在sys/types.h中进行了定义。他通常是一个小整数。一些是由系统预先定义的;另一个些是当有新用户要添加到系统时,由系统管理员创建的。通常,用户的UID值大于100。

#include <sys/types.h>
#include <unistd.h>
uid_t getuid(void);
char *getlogin(void);

getuid函数会返回与程序相关的UID值。这通常是启动程序的用户的UID值。

getlogin函数会返回与当前用户相关联的登陆名。

系统文件/etc/passwd包含一个处理用户帐户的数据库。每个用户一行,其中包含用户名,加密密码,用户标识UID,组标识GID,全名,主目录,以及默认的shell。下面是其中的一个例子:

neil:zBqxfqedfpk:500:100:Neil Matthew:/home/neil:/bin/bash

如 果我们要编写一个程序来确定启动程序的用户UID,我们会对其进行扩展来查看密码文件以查找用户的登陆名与全名。我们并不推荐这样做,因为现在的类 Unix系统都由使用简单的密码文件迁移到加强的系统安全。许多系统,包括Linux,有一个选项可以使用影子密码(shadwo password)文件,其中根本就不包含任何有用的加密密码信息(这通常存放在用户不可读取的/etc/shadow中)。正是因为这个原因,系统定义 了一系列函数来为用户信息提供标准而高效的程序接口:

#include <sys/types.h>
#include <pwd.h>
struct passwd *getpwuid(uid_t uid);
struct passwd *getpwnam(const char *name);

密码数据库结构,passwd,定义在pwd.h中,包含下列成员:

成员        描述
char *pw_name    用户登陆名
uid_t pw_uid    UID值
gid_t pw_gid    GID值
char *pw_dir    用户主目录
char *pw_gecos    用户全名
char *pw_shell    用户默认shell

一些Unix系统会使用一个不同的名字来表示用户全名域:在一些系统上例如Linux为pw_gecos,而在另一个系统上为pw_comment。这就意味着我们并不推荐使用这个域。

getpwuid与getpwnam函数都会返回一个指向对应于一个用户的密码结构指针。对于getpwuid函数,用户是由UID标识的,而对于getpwnam函数,用户是由登陆来标识的。如果出错,他们都会返回一个空指针并且设置errno变量。

试验--用户信息

这里有一个程序,user.c,这会由密码数据库中得到一些用户信息:

#include <sys/types.h>
#include <pwd.h>
#include <stdio.h>
#include <unistd.h>
int main()
{
    uid_t uid;
    gid_t gid;
    struct passwd *pw;
    uid = getuid();
    gid = getgid();
    printf(“User is %s\n”, getlogin());
    printf(“User IDs: uid=%d, gid=%d\n”, uid, gid);
    pw = getpwuid(uid);
    printf(“UID passwd entry:\n name=%s, uid=%d, gid=%d, home=%s, shell=%s\n”,
        pw->pw_name, pw->pw_uid, pw->pw_gid, pw->pw_dir, pw->pw_shell);
    pw = getpwnam(“root”);
    printf(“root passwd entry:\n”);
    printf(“name=%s, uid=%d, gid=%d, home=%s, shell=%s\n”,
        pw->pw_name, pw->pw_uid, pw->pw_gid, pw->pw_dir, pw->pw_shell);
    exit(0);
}

其程序输出如下所示,也许在不同的系统上其输出结果会略有不同:

$ ./user
User is neil
User IDs: uid=500, gid=100
UID passwd entry:
 name=neil, uid=500, gid=100, home=/home/neil, shell=/bin/bash
root passwd entry:
name=root, uid=0, gid=0, home=/root, shell=/bin/bash

工作原理

这个程序会调用getuid函数来得到当前用户的UID值。这个UID会用在getpwuid函数中来得到详细的密码文件信息。作为一个对照,我们演示了如何在getpwnam函数中指定用户名root来得到用户信息。

要遍历密码文件信息,我们可以使用getpwent函数。这会取得连续的文件实体:

#include <pwd.h>
#include <sys/types.h>
void endpwent(void);
struct passwd *getpwent(void);
void setpwent(void);

 

可以循环调用该函数,而依次获得各项数据,知道无任何数据可读时函数返回 NULL 。

#include <stdio.h>
#include <sys/types.h>
#include <pwd.h>
 
int main()
{
     struct passwd *user;
 
     while ((user = getpwent()))
           printf ( "%s:%d:%d:%s:%s:%s\n" , user->pw_name, user->pw_uid,
                   user->pw_gid, user->pw_gecos, user->pw_dir, user->pw_shell);
           
     endpwent();
 
     return 0;
}


运行输出效果相当于执行命令:cat /etc/passwd 。



getpwent 函数会依次返回每个用户信息。当到达文件结尾时,他会返回一个空指针。一旦搜索完毕所有的有效实体,我们可以使用endpwent函数来结束处理过程。 setpwent函数可以在密码文件中重新设置指针使其指向文件的开头,这样当下次调用getpwent函数时可以重新开始遍历。这些函数的操作与我们在 第3章所讨论的目录遍历函数opendir,readdir,closedir相类似。

用户与组标识可由其他的一个通常不用的函数来得到:

#include <sys/types.h>
#include <unistd.h>
uid_t geteuid(void);
gid_t getgid(void);
gid_t getegid(void);
int setuid(uid_t uid);
int setgid(gid_t gid);

我们可以查看系统手册页来得到关于组标识与有效用户标识的更为详细的内容,尽管我们会发现我们根本就不需要来操作这些函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值