OSDN Git Service

Merge branch 'master' of https://github.com/hengband/hengband
[hengbandforosx/hengbandosx.git] / src / util / angband-files.cpp
1 #include "util/angband-files.h"
2 #include "locale/japanese.h"
3 #include "system/angband-exceptions.h"
4 #include "util/string-processor.h"
5 #include <sstream>
6 #include <string>
7
8 #ifdef SET_UID
9
10 #ifndef HAVE_USLEEP
11
12 /*
13  * For those systems that don't have "usleep()" but need it.
14  *
15  * Fake "usleep()" function grabbed from the inl netrek server -cba
16  */
17 int usleep(ulong usecs)
18 {
19     struct timeval timer;
20
21     int nfds = 0;
22
23     fd_set *no_fds = nullptr;
24     if (usecs > 4000000L) {
25         core(_("不当な usleep() 呼び出し", "Illegal usleep() call"));
26     }
27
28     timer.tv_sec = (usecs / 1000000L);
29     timer.tv_usec = (usecs % 1000000L);
30     if (select(nfds, no_fds, no_fds, no_fds, &timer) < 0) {
31         if (errno != EINTR) {
32             return -1;
33         }
34     }
35
36     return 0;
37 }
38 #endif
39
40 /*
41  * Hack -- External functions
42  */
43 #ifdef SET_UID
44 struct passwd *getpwuid(uid_t uid);
45 struct passwd *getpwnam(concptr name);
46 #endif
47
48 /*
49  * Find a default user name from the system.
50  */
51 void user_name(char *buf, int id)
52 {
53     struct passwd *pw;
54     if ((pw = getpwuid(id))) {
55         (void)strcpy(buf, pw->pw_name);
56         buf[16] = '\0';
57
58 #ifdef JP
59         if (!iskanji(buf[0]))
60 #endif
61             if (islower(buf[0])) {
62                 buf[0] = toupper(buf[0]);
63             }
64
65         return;
66     }
67
68     strcpy(buf, "PLAYER");
69 }
70
71 #endif /* SET_UID */
72
73 std::filesystem::path path_parse(const std::filesystem::path &path)
74 #ifdef SET_UID
75 {
76     /*
77      * Extract a "parsed" path from an initial filename
78      * Normally, we simply copy the filename into the buffer
79      * But leading tilde symbols must be handled in a special way
80      * Replace "~user/" by the home directory of the user named "user"
81      * Replace "~/" by the home directory of the current user
82      */
83     const auto &file = path.string();
84     if (file.empty() || (file[0] != '~')) {
85         return file;
86     }
87
88     auto u = file.data() + 1;
89     auto s = angband_strstr(u, PATH_SEP);
90     constexpr auto user_size = 128;
91     char user[user_size]{};
92     if ((s != nullptr) && (s >= u + user_size)) {
93         THROW_EXCEPTION(std::runtime_error, "User name is too long!");
94     }
95
96     if (s != nullptr) {
97         int i;
98         for (i = 0; u < s; ++i) {
99             user[i] = *u++;
100         }
101
102         u = user;
103     }
104
105     if (u[0] == '\0') {
106         u = getlogin();
107     }
108
109     struct passwd *pw;
110     if (u != nullptr) {
111         pw = getpwnam(u);
112     } else {
113         pw = getpwuid(getuid());
114     }
115
116     if (pw == nullptr) {
117         THROW_EXCEPTION(std::runtime_error, "Failed to get User ID!");
118     }
119
120     if (s == nullptr) {
121         return pw->pw_dir;
122     }
123
124     std::stringstream ss;
125     ss << pw->pw_dir << s;
126     return ss.str();
127 }
128 #else
129 {
130     return path;
131 }
132 #endif /* SET_UID */
133
134 #ifndef HAVE_MKSTEMP
135
136 /*
137  * Hack -- acquire a "temporary" file name if possible
138  *
139  * This filename is always in "system-specific" form.
140  */
141 static errr path_temp(char *buf, int max)
142 {
143     auto s = tmpnam(nullptr);
144     if (!s) {
145         return -1;
146     }
147
148 #if !defined(WIN32) || (defined(_MSC_VER) && (_MSC_VER >= 1900))
149     (void)strnfmt(buf, max, "%s", s);
150 #else
151     (void)strnfmt(buf, max, ".%s", s);
152 #endif
153
154     return 0;
155 }
156 #endif
157
158 /*!
159  * @brief OSの差異を吸収しつつ、絶対パスを生成する.
160  * @param path file 引数があるディレクトリ
161  * @param file ファイル名またはディレクトリ名
162  */
163 std::filesystem::path path_build(const std::filesystem::path &path, std::string_view file)
164 {
165     if ((file[0] == '~') || (prefix(file, PATH_SEP)) || path.empty()) {
166         return file;
167     }
168
169     auto parsed_path = path_parse(path);
170     const auto &path_ret = parsed_path.append(file);
171     constexpr auto max_path_length = 1024;
172     const auto path_str = path_ret.string();
173     if (path_str.length() > max_path_length) {
174         THROW_EXCEPTION(std::runtime_error, format("Path is too long! %s", path_str.data()));
175     }
176
177     return path_ret;
178 }
179
180 static std::string make_file_mode(const FileOpenMode mode, const bool is_binary)
181 {
182     std::stringstream ss;
183     switch (mode) {
184     case FileOpenMode::READ:
185         ss << 'r';
186         break;
187     case FileOpenMode::WRITE:
188         ss << 'w';
189         break;
190     case FileOpenMode::APPEND:
191         ss << 'a';
192         break;
193     default:
194         THROW_EXCEPTION(std::logic_error, "Invalid file mode is specified!");
195     }
196
197     if (is_binary) {
198         ss << 'b';
199     }
200
201     return ss.str();
202 }
203
204 /*!
205  * @brief OSごとの差異を吸収してファイルを開く
206  * @param path ファイルの相対パスまたは絶対パス
207  * @param mode ファイルを開くモード
208  * @param is_binary バイナリモードか否か (無指定の場合false:テキストモード)
209  * @return ファイルポインタ
210  */
211 FILE *angband_fopen(const std::filesystem::path &path, const FileOpenMode mode, const bool is_binary)
212 {
213     const auto &parsed_path = path_parse(path);
214     const auto &open_mode = make_file_mode(mode, is_binary);
215     return fopen(parsed_path.string().data(), open_mode.data());
216 }
217
218 /*
219  * Hack -- replacement for "fclose()"
220  */
221 errr angband_fclose(FILE *fff)
222 {
223     if (!fff) {
224         return -1;
225     }
226     if (fclose(fff) == EOF) {
227         return 1;
228     }
229     return 0;
230 }
231
232 #ifdef HAVE_MKSTEMP
233 FILE *angband_fopen_temp(char *buf, int max)
234 {
235     strncpy(buf, "/tmp/anXXXXXX", max);
236     int fd = mkstemp(buf);
237     if (fd < 0) {
238         return nullptr;
239     }
240
241     return fdopen(fd, "w");
242 }
243 #else /* HAVE_MKSTEMP */
244 FILE *angband_fopen_temp(char *buf, int max)
245 {
246     if (path_temp(buf, max)) {
247         return nullptr;
248     }
249     return angband_fopen(buf, FileOpenMode::WRITE);
250 }
251 #endif /* HAVE_MKSTEMP */
252
253 /*!
254  * @brief ファイルから改行かEOFまでの文字列を読み取り、システムのエンコーディングに変換した結果を返す
255  *
256  * @param fp ファイルポインタ
257  * @return 読み取った文字列。1バイトも読み取らずファイルの終端に達した場合はstd::nullopt
258  */
259 static std::optional<std::string> read_line(FILE *fp)
260 {
261     std::string line_buf;
262
263     char buf[1024];
264     while (fgets(buf, sizeof(buf), fp) != nullptr) {
265         std::string_view sv(buf);
266
267         line_buf.append(sv.begin(), sv.end());
268         if (sv.back() == '\n') {
269             break;
270         }
271     }
272
273     if (line_buf.empty()) {
274         return std::nullopt;
275     }
276
277     if (line_buf.back() == '\n') {
278         line_buf.pop_back();
279     }
280
281 #ifdef JP
282     const int len = guess_convert_to_system_encoding(line_buf.data(), line_buf.size());
283     line_buf.erase(len);
284 #endif
285
286     return line_buf;
287 }
288
289 /*!
290  * @brief ファイルから1行読み込む
291  *
292  * ファイルから1行読み込み、読み込んだ文字列に以下の処理を行い最大n-1バイトまでの文字列を返す。
293  * - タブをスペースに変換する
294  * - プリント可能文字以外を'?'に変換する
295  * - 行末の改行を削除する
296  *
297  * 日本語版ではさらに以下の処理も行う。
298  * - 文字コードをシステムの文字コードに変換する
299  * - マルチバイト文字の前半のみが残らないようにする
300  *
301  * 読み込んだ行で上記の変換を行いn-1バイトを超えた分は読み捨てられる。
302  *
303  * @param fp 読み込むファイルポインタ
304  * @param n 読み込む文字列の最大サイズ。
305  * 呼び出し側で終端文字を扱う可能性を考慮し、文字列長としては最大n-1バイトまでの文字列を返す。
306  * デフォルト引数(std::string::npos)の場合は巨大な値であるため実質的にサイズ制限なし。
307  * @return 読み取った文字列。1バイトも読み取らずファイルの終端に達した場合はstd::nullopt
308  */
309 std::optional<std::string> angband_fgets(FILE *fp, size_t n)
310 {
311     // Reserve for null termination
312     --n;
313
314     const auto line = read_line(fp);
315     if (!line) {
316         return std::nullopt;
317     }
318
319     std::string str;
320
321     for (const auto *s = line->data(); *s; s++) {
322         if (*s == '\t') {
323             constexpr auto tab_width = 8;
324             if (str.length() + tab_width >= n) {
325                 break;
326             }
327
328             const auto space_count = tab_width - (str.length() % tab_width);
329             str.append(space_count, ' ');
330         }
331 #ifdef JP
332         else if (iskanji(*s)) {
333             if (str.length() + 1 >= n || s[1] == '\0') {
334                 break;
335             }
336             str.push_back(*s++);
337             str.push_back(*s);
338         } else if (iskana(*s)) {
339             /* 半角かなに対応 */
340             str.push_back(*s);
341         }
342 #endif
343         else if (isprint((unsigned char)*s)) {
344             str.push_back(*s);
345         } else {
346             str.push_back('?');
347         }
348
349         if (str.length() >= n) {
350             break;
351         }
352     }
353
354     return str;
355 }
356
357 /*
358  * Hack -- replacement for "fputs()"
359  * Dump a string, plus a newline, to a file
360  * Process internal weirdness?
361  */
362 errr angband_fputs(FILE *fff, concptr buf, ulong n)
363 {
364     n = n ? n : 0;
365     (void)fprintf(fff, "%s\n", buf);
366     return 0;
367 }
368
369 /*
370  * Several systems have no "O_BINARY" flag
371  */
372 #ifndef O_BINARY
373 #define O_BINARY 0
374 #endif /* O_BINARY */
375
376 /*!
377  * @brief OSごとの差異を吸収してファイルを削除する
378  * @param file ファイルの相対パスまたは絶対パス
379  */
380 void fd_kill(const std::filesystem::path &path)
381 {
382     const auto &parsed_path = path_parse(path);
383
384     std::error_code ec;
385     std::filesystem::remove(parsed_path, ec);
386 }
387
388 /*!
389  * @brief OSごとの差異を吸収してファイルを移動する
390  * @param path_from 移動元のファイルの相対パスまたは絶対パス
391  * @param path_to 移動先のファイルの相対パスまたは絶対パス
392  */
393 void fd_move(const std::filesystem::path &path_from, const std::filesystem::path &path_to)
394 {
395     const auto &abs_path_from = path_parse(path_from);
396     const auto &abs_path_to = path_parse(path_to);
397
398     std::error_code ec;
399     std::filesystem::rename(abs_path_from, abs_path_to, ec);
400 }
401
402 /*!
403  * @brief OSごとの差異を吸収してファイルを作成する
404  * @param path 作成先ファイルの相対パスまたは絶対パス
405  * @param can_write_group グループに書き込みを許可するか否か
406  */
407 int fd_make(const std::filesystem::path &path, bool can_write_group)
408 {
409     const auto permission = can_write_group ? 0644 : 0664;
410     const auto &parsed_path = path_parse(path);
411     return open(parsed_path.string().data(), O_CREAT | O_EXCL | O_WRONLY | O_BINARY, permission);
412 }
413
414 /*
415  * @brief OSごとの差異を吸収してファイルを開く
416  * @param path ファイルの相対パスまたは絶対パス
417  * @param mode ファイルのオープンモード (読み書き、Append/Trunc等)
418  */
419 int fd_open(const std::filesystem::path &path, int mode)
420 {
421     const auto &path_abs = path_parse(path);
422     return open(path_abs.string().data(), mode | O_BINARY, 0);
423 }
424
425 /*
426  * Hack -- attempt to lock a file descriptor
427  *
428  * Legal lock types -- F_UNLCK, F_RDLCK, F_WRLCK
429  */
430 errr fd_lock(int fd, int what)
431 {
432     what = what ? what : 0;
433     if (fd < 0) {
434         return -1;
435     }
436
437 #if defined(SET_UID) && defined(LOCK_UN) && defined(LOCK_EX)
438     if (what == F_UNLCK) {
439         (void)flock(fd, LOCK_UN);
440     } else {
441         if (flock(fd, LOCK_EX) != 0) {
442             return 1;
443         }
444     }
445 #endif
446
447     return 0;
448 }
449
450 /*
451  * Hack -- attempt to seek on a file descriptor
452  */
453 errr fd_seek(int fd, ulong n)
454 {
455     if (fd < 0) {
456         return -1;
457     }
458
459     ulong p = lseek(fd, n, SEEK_SET);
460     if (p != n) {
461         return 1;
462     }
463
464     return 0;
465 }
466
467 /*
468  * Hack -- attempt to read data from a file descriptor
469  */
470 errr fd_read(int fd, char *buf, ulong n)
471 {
472     if (fd < 0) {
473         return -1;
474     }
475 #ifndef SET_UID
476     while (n >= 16384) {
477         if (read(fd, buf, 16384) != 16384) {
478             return 1;
479         }
480
481         buf += 16384;
482         n -= 16384;
483     }
484 #endif
485
486     if (read(fd, buf, n) != (int)n) {
487         return 1;
488     }
489
490     return 0;
491 }
492
493 /*
494  * Hack -- Attempt to write data to a file descriptor
495  */
496 errr fd_write(int fd, concptr buf, ulong n)
497 {
498     if (fd < 0) {
499         return -1;
500     }
501
502 #ifndef SET_UID
503     while (n >= 16384) {
504         if (write(fd, buf, 16384) != 16384) {
505             return 1;
506         }
507
508         buf += 16384;
509         n -= 16384;
510     }
511 #endif
512
513     if (write(fd, buf, n) != (int)n) {
514         return 1;
515     }
516
517     return 0;
518 }
519
520 /*
521  * Hack -- attempt to close a file descriptor
522  */
523 errr fd_close(int fd)
524 {
525     if (fd < 0) {
526         return -1;
527     }
528
529     (void)close(fd);
530     return 0;
531 }