OSDN Git Service

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