fread returns 0 并且无法在 Windows 机器上打开 db 文件。找不到文件?或者是权限?
fread returns 0 and cannot open db file on Windows machine. Can the file not be found? Or is it the permissions?
我正在做一个关于 C (Learn C The Hard Way: Ex17) 的教程,并且在 Database_load 函数中当 fread 加载数据库文件时 "ERROR! Failed to load database." 被抛出。为什么不能读取数据库?该文件位于目录中。找不到文件?或者是否需要更改权限?
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#define MAX_DATA 512
#define MAX_ROWS 100
struct Address {
int id;
int set;
char name[MAX_DATA];
char email[MAX_DATA];
};
struct Database {
struct Address rows [MAX_ROWS];
};
struct Connection {
FILE *file;
struct Database *db;
};
void die (const char *message)
{
if(errno) {
perror(message);
} else {
printf("ERROR!\t%s\n", message);
}
exit(1);
}
void Address_print(struct Address *addr)
{
printf("%d %s %s\n", addr->id, addr->name, addr->email);
}
void Database_load(struct Connection *conn)
{
// Error shows up here!
int rc = fread(conn->db, sizeof(struct Database), 1, conn->file);
if(rc != 1) {
die("Failed to load database.");
}
}
struct Connection *Database_open(const char *filename, char mode)
{
struct Connection *conn = malloc(sizeof(struct Connection));
if(!conn){
die("Memory error.");
}
conn->db = malloc(sizeof(struct Database));
if (!conn->db) {
die("Memory error");
}
if(mode == 'c') {
conn->file = fopen(filename, "w"); // Problem is here! See @fluter's solution
if (conn->file == NULL) {
fprintf(stderr, "open failed, errno %d: %s\n", errno, strerror(errno));
}
} else {
conn->file = fopen(filename, "r+"); // And the problem is here!
if (conn->file == NULL) {
fprintf(stderr, "open failed, errno %d: %s\n", errno, strerror(errno));
} else {
Database_load(conn);
}
}
if(!conn->file){
die("Failed to open the file.");
}
return conn;
}
void Database_close(struct Connection *conn)
{
if(conn) {
if(conn->file) fclose(conn->file);
if(conn->db) free(conn->db);
free(conn);
}
}
void Database_write(struct Connection *conn)
{
rewind(conn->file);
int rc = fwrite(conn->db, sizeof(struct Database), 1, conn->file);
if(rc != 1) {
die("Failed to write database.");
}
rc = fflush(conn->file);
if(rc == -1) {
die("Cannot flush database.");
}
}
void Database_create(struct Connection *conn)
{
int i = 0;
for (i = 0; i < MAX_ROWS; i++) {
// make a prototype to initialize the address
struct Address addr = {.id = i, .set = 0};
// then assign the prototype to the database
conn->db->rows[i] = addr;
}
}
void Database_set(struct Connection *conn, int id, const char *name, const char *email)
{
struct Address *addr = &conn->db->rows[id];
if (addr->set) {
die("Address already set, delete it first");
}
addr->set = 1;
// WARNING!! Strncpy has a bug - needs to be fixed ...
char *res = strncpy(addr->name, email, MAX_DATA);
if(!res) {
die("Name copy failed.");
}
// ... by adding NULL to the end of the array
addr->name[sizeof(addr->name)-1] = '[=12=]';
res = strncpy(addr->email, email, MAX_DATA);
if(!res) {
die("Email copy failed.");
}
}
void Database_list(struct Connection *conn)
{
int i = 0;
struct Database *db = conn->db;
for(i = 0; i < MAX_ROWS; i++) {
struct Address *cur = &db->rows[i];
if(cur->set) {
Address_print(cur);
}
}
}
int main (int argc, char *argv[]){
if(argc < 3) {
die("USAGE: ex17.exe <dbfile> <action> [action params]");
}
char *filename = argv[1];
char action = argv[2][0];
struct Connection *conn = Database_open(filename, action);
int id = 0;
if(argc > 3) {
id = atoi(argv[3]);
}
if(argc > MAX_ROWS) {
die("There aren't that many records");
}
switch(action) {
case 'c':
Database_create(conn);
Database_write(conn);
break;
case 's':
if(argc != 4) {
die("Id, name, and email address are needed.");
}
Database_set(conn, id, argv[4], argv[5]);
Database_write(conn);
break;
case 'l':
Database_list(conn);
break;
default:
die("\tInvalid action.\n\
\tPlease use:\n\
\tPlease use:\n\
\tc = create\n\
\td = delete\n\
\tl = list\n\
\ts = set\n");
}
Database_close(conn);
return 0;
}
问题是,即使错误显示 "Failed to load database.",文件肯定在文件夹中。
我想知道是不是权限问题(见上面的屏幕截图)。对我来说,我有 rw,但 Admin 只有 r。因为我是 运行 终端机,所以不应该是因为 Admin 没有 w,不是吗?
首先,你打印的是conn->file的地址,而不是conn->file本身,当然conn->file的地址不是NULL。所以请尝试以下操作:
printf("conn->file address: %p\n", conn->file);
如果打开失败会打印NULL,具体错误,打印errno找出错误的根本原因:
if (conn->file == NULL) {
fprintf(stderr, "open failed, errno %d: %s\n", errno, strerror(errno);
} else {
// open db
}
对于windows上的失败,是因为Windows直接处理文本和二进制打开的文件,而在Linux上它们是一样的。如果文件以文本模式打开,windows 会将 CRLF 转换为 LF,并将 ctrl-z 控制字符视为 EOF 标记,这正是您在 windows 上看到的错误,因此在 Database_open
, 使用以下方式打开文件:
if(mode == 'c') {
conn->file = fopen(filename, "wb");
} else {
conn->file = fopen(filename, "rb+");
if(conn->file) {
Database_load(conn);
}
}
这也适用于 Linux,因为文件在 Linux 上是二进制模式。
我正在做一个关于 C (Learn C The Hard Way: Ex17) 的教程,并且在 Database_load 函数中当 fread 加载数据库文件时 "ERROR! Failed to load database." 被抛出。为什么不能读取数据库?该文件位于目录中。找不到文件?或者是否需要更改权限?
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#define MAX_DATA 512
#define MAX_ROWS 100
struct Address {
int id;
int set;
char name[MAX_DATA];
char email[MAX_DATA];
};
struct Database {
struct Address rows [MAX_ROWS];
};
struct Connection {
FILE *file;
struct Database *db;
};
void die (const char *message)
{
if(errno) {
perror(message);
} else {
printf("ERROR!\t%s\n", message);
}
exit(1);
}
void Address_print(struct Address *addr)
{
printf("%d %s %s\n", addr->id, addr->name, addr->email);
}
void Database_load(struct Connection *conn)
{
// Error shows up here!
int rc = fread(conn->db, sizeof(struct Database), 1, conn->file);
if(rc != 1) {
die("Failed to load database.");
}
}
struct Connection *Database_open(const char *filename, char mode)
{
struct Connection *conn = malloc(sizeof(struct Connection));
if(!conn){
die("Memory error.");
}
conn->db = malloc(sizeof(struct Database));
if (!conn->db) {
die("Memory error");
}
if(mode == 'c') {
conn->file = fopen(filename, "w"); // Problem is here! See @fluter's solution
if (conn->file == NULL) {
fprintf(stderr, "open failed, errno %d: %s\n", errno, strerror(errno));
}
} else {
conn->file = fopen(filename, "r+"); // And the problem is here!
if (conn->file == NULL) {
fprintf(stderr, "open failed, errno %d: %s\n", errno, strerror(errno));
} else {
Database_load(conn);
}
}
if(!conn->file){
die("Failed to open the file.");
}
return conn;
}
void Database_close(struct Connection *conn)
{
if(conn) {
if(conn->file) fclose(conn->file);
if(conn->db) free(conn->db);
free(conn);
}
}
void Database_write(struct Connection *conn)
{
rewind(conn->file);
int rc = fwrite(conn->db, sizeof(struct Database), 1, conn->file);
if(rc != 1) {
die("Failed to write database.");
}
rc = fflush(conn->file);
if(rc == -1) {
die("Cannot flush database.");
}
}
void Database_create(struct Connection *conn)
{
int i = 0;
for (i = 0; i < MAX_ROWS; i++) {
// make a prototype to initialize the address
struct Address addr = {.id = i, .set = 0};
// then assign the prototype to the database
conn->db->rows[i] = addr;
}
}
void Database_set(struct Connection *conn, int id, const char *name, const char *email)
{
struct Address *addr = &conn->db->rows[id];
if (addr->set) {
die("Address already set, delete it first");
}
addr->set = 1;
// WARNING!! Strncpy has a bug - needs to be fixed ...
char *res = strncpy(addr->name, email, MAX_DATA);
if(!res) {
die("Name copy failed.");
}
// ... by adding NULL to the end of the array
addr->name[sizeof(addr->name)-1] = '[=12=]';
res = strncpy(addr->email, email, MAX_DATA);
if(!res) {
die("Email copy failed.");
}
}
void Database_list(struct Connection *conn)
{
int i = 0;
struct Database *db = conn->db;
for(i = 0; i < MAX_ROWS; i++) {
struct Address *cur = &db->rows[i];
if(cur->set) {
Address_print(cur);
}
}
}
int main (int argc, char *argv[]){
if(argc < 3) {
die("USAGE: ex17.exe <dbfile> <action> [action params]");
}
char *filename = argv[1];
char action = argv[2][0];
struct Connection *conn = Database_open(filename, action);
int id = 0;
if(argc > 3) {
id = atoi(argv[3]);
}
if(argc > MAX_ROWS) {
die("There aren't that many records");
}
switch(action) {
case 'c':
Database_create(conn);
Database_write(conn);
break;
case 's':
if(argc != 4) {
die("Id, name, and email address are needed.");
}
Database_set(conn, id, argv[4], argv[5]);
Database_write(conn);
break;
case 'l':
Database_list(conn);
break;
default:
die("\tInvalid action.\n\
\tPlease use:\n\
\tPlease use:\n\
\tc = create\n\
\td = delete\n\
\tl = list\n\
\ts = set\n");
}
Database_close(conn);
return 0;
}
问题是,即使错误显示 "Failed to load database.",文件肯定在文件夹中。
我想知道是不是权限问题(见上面的屏幕截图)。对我来说,我有 rw,但 Admin 只有 r。因为我是 运行 终端机,所以不应该是因为 Admin 没有 w,不是吗?
首先,你打印的是conn->file的地址,而不是conn->file本身,当然conn->file的地址不是NULL。所以请尝试以下操作:
printf("conn->file address: %p\n", conn->file);
如果打开失败会打印NULL,具体错误,打印errno找出错误的根本原因:
if (conn->file == NULL) {
fprintf(stderr, "open failed, errno %d: %s\n", errno, strerror(errno);
} else {
// open db
}
对于windows上的失败,是因为Windows直接处理文本和二进制打开的文件,而在Linux上它们是一样的。如果文件以文本模式打开,windows 会将 CRLF 转换为 LF,并将 ctrl-z 控制字符视为 EOF 标记,这正是您在 windows 上看到的错误,因此在 Database_open
, 使用以下方式打开文件:
if(mode == 'c') {
conn->file = fopen(filename, "wb");
} else {
conn->file = fopen(filename, "rb+");
if(conn->file) {
Database_load(conn);
}
}
这也适用于 Linux,因为文件在 Linux 上是二进制模式。