Previous Up Next

Reading Directories and Classifying Files

בתרגיל זה נלמד לקרוא תוכן מדריכים ולבחון תכונות של קבצים בלינוקס ויוניקס.

קריאת תוכן מדריך:  

#include <windows.h>
...
HANDLE FindFirstFile(LPCTSTR pattern,
                     WIN32_FIND_DATA* find_data);
BOOL   FindNextFile (HANDLE search_handle,
                     WIN32_FIND_DATA* find_data);
BOOL   FindClose    (HANDLE search_handle);
קריאת המערכת FindFirstFile מתחילה חיפוש של קבצים במדריך. הארגומנט הראשון שלה הוא תבנית של שמות קבצים שמבקשים לחפש. התבנית כוללת תחילית של שם מדריך וסיומת של תבנית של שם קובץ, שיכולה להכיל את התוים * ו--?, כמו למשל C:\Temp\*.doc, C:\*, או \\home-fs\stoledo\*. יש לשים לב לכך שהמחרוזת צריכה תמיד להסתיים בתבנית של שמות קבצים, כך שאם היא מסתיימת בשם שם מדריך, ללא סיומת כגון \*, החיפוש ימצא אך ורק קובץ אחד, המדריך עצמו, ולא את כל הקבצים שבמדריך. ארגומנט שמצביע על מדריך השורש של אות כונן, כמו C:, אינו חוקי. הארגומנט השני הוא מצביע למבנה שבו מוחזר מידע אודות קובץ אחד שנמצא בחיפוש. השגרה מחזירה מזהה שבעזרתו ניתן לקבל מידע אודות שאר הקבצים שנמצאו בחיפוש, ויש לסגור את המזהה הזה על ידי קריאה ל--FindClose.

קריאת המערכת FindNextFile מחזירה מידע על קובץ נוסף שעונה לתנאי החיפוש. הארגומנט הראשון שלה הוא המזהה שהוחזר על ידי FindFirstFile, והשני הוא מצביע למבנה שיכיל מידע אודות הקובץ. לקריאה הזו קוראים בדרך כלל בלולאה, למציאת כל הקבצים שעונים לחיפוש. כאשר אין יותר קבצים שעונים לתנאי החיפוש, הקריאה נכשלת והערך שיוחזר מ--GetLastError() הוא ERROR_NO_MORE_FILES.

מידע אודות קובץ: מנגנון חיפוש הקבצים מחזיר מידע אודות קובץ או מדריך במבנה מסוג WIN32_FIND_DATA,

typedef struct {
  DWORD    dwFileAttributes;
  FILETIME ftCreationTime;
  FILETIME ftLastAccessTime;
  FILETIME ftLastWriteTime;
  DWORD    nFileSizeHigh;
  DWORD    nFileSizeLow;
  DWORD    dwReserved0;
  DWORD    dwReserved1;
  TCHAR    cFileName[MAX_PATH];
  TCHAR    cAlternateFileName[14];
} WIN32_FIND_DATA;
השדה הראשון מכיל מספר ביטים שכל אחד מהם מייצג תכונה אחרת של הקובץ, כגון FILE_ATTRIBUTE_READONLY, FILE_ATTRIBUTE_DIRECTORY, ו--FILE_ATTRIBUTE_REPARSE_POINT, )על משמעות התכונה האחרונה נעמוד בהמשך(. שלושת השדות הבאים מייצגים את הזמן שבו הקובץ נוצר, שבו נגשו אליו לאחרונה, ושבו כתבו אליו לאחרונה. תוכנית יכולה לשנות את השדות הללו של קובץ, כך שהמידע אינו בהכרח אמין. ניתן לחלץ מהשדות הללו ייצוג קריא )ב--utc, השעה הסטנדרטית בגריניץ'( בעזרת השגרה FileTimeToSystemTime. שני השדות הבאים מייצגים את גודל הקובץ בבתים בעזרת שני שדות של 32 סיביות כל אחד. יש לשים לב שגודל הקובץ יכול להיות גדול מכדי שניתן יהיה לייצג אותו בעזרת משתנה של 32 סיביות, כמו int. הנוסחה שבעזרתה ממירים את שני השדות הללו לגודל קובץ היא (nFileSizeHigh * (MAXDWORD+1)) + nFileSizeLow. כאמור, יש להיזהר מגלישה בחישוב הזה )למשל על ידי שימוש במשתנים מסוג double או ULARGE_INTEGER(. שם הקובץ שנמצא מופיע בשדה cFileName, ואם יש לו שם נרדף קצר )8 תוים לכל היותר לפני הנקודה ועוד 3 לכל היותר אחריה(, הוא יופיע בשדה האחרון.

יצירת וספירת מצביעים לקבצים: מערכת הקבצים ntfs תומכת ביצירת מצביעים נוספים לקבצים. המצביעים הללו הם למעשה שמות נרדפים לקובץ. ניתן ליצור שם נרדף לקובץ במדריך אחר מזה שבו הקובץ שוכן )אבל רק באותה מערכת קבצים(, וניתן להעביר כל מצביע ממקום למקום, או לשנות את שמו, באופן בלתי תלוי. בין השמות הנרדפים של קובץ אין סדר של בכירות: ניתן למשל למחוק את המצביע המקורי של הקובץ אבל הקובץ ימשיך להישמר במערכת הקבצים תחת השם הנרדף. ניתן ליצור מצביע כזה בעזרת הפקודה fsutil create hardlink בתוכנת מעטפת )cmd.exe(. הריצו את הפקודה בלי ארגומנטים נוספים על מנת לקבל פירוט של משמעות הארגומנטים הנוספים. ניתן גם ליצור מצביע בעזרת קריאת המערכת CreateHardLink. לא ניתן ליצור מצביע למדריך, על מנת שלא יווצרו מעגלים במרחב השמות.

את מספר המצביעים לקובץ ניתן לברר באמצעות קריאת המערכת הבאה.

#include <windows.h>
...
BOOL GetFileInformationByHandle(HANDLE hFile,
        BY_HANDLE_FILE_INFORMATION* finfo);
הקריאה מקבלת מזהה של קובץ פתוח ומחזירה מידע אודות הקובץ במבנה שכתובתו מועברת בארגומנט השני. המבנה הזה דומה למדי ל--WIN32_FIND_DATA, פרט לשדה nNumberOfLinks שמתאר את מספר המצביעים לקובץ, ושלושה שדות אחרים שמאפשרים לדעת האם שני מזהים פתוחים )HANDLE's( מתייחסים לאותו קובץ, אולי דרך מצביעים אחרים. )יש לשים לב שבניגוד למידע שמוחזר על ידי FindFirstFile ו--FindNextFile, שעבורו אין צורך לפתוח את הקובץ, על מנת לקבל את המידע הזה צריך לפתוח את הקובץ(.

מצביעים סימבוליים ונקודות הצבה: מערכת הקבצים ntfs תומכת בעוד שני סוגים של מצביעים. שני הסוגים הללו משתמשים במנגנון שנקרא reparse point. המנגנון הזה מאפשר לשנות את ההתנהגות של מערכת ההפעלה בזמן שתוכנית פותחת קובץ או מדריך מסויים. המנגנון פועל על ידי הוספת רשומת מידע לקובץ או למדריך. הרשומה כוללת את סוג המידע ואת המידע עצמו. כאשר פותחים את הקובץ/מדריך, מערכת ההפעלה מזהה את המידע הנוסף, מבררת את סוגו, ומפעילה שגרה של מערכת ההפעלה שהיא מיוחדת לאותו סוג מידע. המנגנון הזה מאפשר להוסיף למערכת ההפעלה יכולות כגון העברה של קבצים שהשימוש בהם נדיר לספריות רובוטיות של קלטות או דיסקים נשלפים, למשל.

מצביעים סימבוליים ונקודות הצבה הם שני סוגים של reparse points. נקודת הצבה מאפשרת להציב מערכת קבצים שלמה על גבי מדריך )שצריך להיות קיים וריק(. זה מאפשר להשתמש במערכת הקבצים כאילו היא הייתה חלק מממערכת קבצים אחרת, ולא דרך אות כונן. התוכנית mountvol מאפשרת ליצור נקודות הצבה ולהסיר אותן.

מצביע סימבולי, שנקרא בחלונות צומת )junction( הוא מדריך דמה שה--reparse point שלו גורמת למערכת ההפעלה לפתוח מדריך אחר. זהו למעשה שם נרדף למדריך. בניגוד למצביעים שתיארנו קודם, יש יחס בכירות בין המדריך עצמו ובין מצביע סימבולי אליו. למשל, ניתן למחוק מדריך שיש אליו מצביע סימבולי. במקרה כזה המצביע הסימבולי פשוט יפסיק לעבוד. מיקרוסופט לא מספקת ביחד עם מערכת ההפעלה כלי שמאפשר ליצור ולמחוק מצביעים סימבוליים, אבל ניתן לנהל מצביעים כאלה בעזרת התוכנית junction שניתן להוריד חינם מ--www.sysinternals.com ובעזרת התוכנית linkd שהיא חלק מה--resource kit של חלונות )חבילת תוכנה של מיקרוסופט שאינה מופצת יחד עם חלונות(. למעשה, מערכת ההפעלה מתייחסת לנקודות הצבה ולמצביעים סימבוליים כאילו היו בדיוק אותו סוג של reparse point.

על מנת למצוא את סוג ה--reparse point ששייך למדריך ועל מנת לקרוא את המידע ששמור בו )כולל שם המדריך או מערכת הקבצים שמצביע סימבולי או נקודת הצבה מצביעים אליהם(, צריך להשתמש בקריאת המערכת הבאה.

#define _WIN32_WINNT 0x0500 /* Windows 2000 and up */
#include <windows.h>
#include <winioctl.h>
#define FSCTL_GET_REPARSE_POINT 0x000900a8
typedef struct {
    DWORD  ReparseTag;
    WORD   ReparseDataLength;
    WORD   Reserved;
    union {
        struct {
            WORD   SubstituteNameOffset;
            WORD   SubstituteNameLength;
            WORD   PrintNameOffset;
            WORD   PrintNameLength;
            WCHAR  PathBuffer[1]; /* can be larger! */
        } MountPointReparseBuffer;
        struct {
            BYTE   DataBuffer[1];
        } GenericReparseBuffer;
    };
} MY_REPARSE_DATA_BUFFER
...
BOOL DeviceIoControl(HANDLE file,
                     DWORD  command,
                     void*  input_buffer,
                     DWORD  input_buffer_size,
                     void*  output_buffer,
                     DWORD  output_buffer_size,
                     DWORD* bytes_returned,
                     OVERLAPPED* overlapped);
לקריאת המערכת הזו צריך להעביר מזהה של קובץ פתוח ואת הפקודה FSCTL_GET_REPARSE_POINT. לקריאה הזו הרבה שימושים, כמו למשל יצירת reparse point. בשימוש שלנו, אין צורך בקלט נוסף, כך שהארגומנט השלישי יכול להיות NULL והרביעי 0. בשני הארגומנטים הבאים יש להעביר שטח זיכרון שבו יכתב המידע שב--reparse point ואת אורכו. אורך המידע עשוי להגיע ל--16384 בתים, ולכן יש להעביר שטח זכרון בגודל מספיק.

אם ה--reparse point הוא מסוג מצביע סימבולי או נקודת הצבה, אז השדה ReparseTag במבנה שיוחזר יכיל את הערך IO_REPARSE_TAG_MOUNT_POINT. במקרה כזה השדות SubstituteNameOffset ו--SubsituteNameLength יכילו את המיקום והאורך של מה שמוצב במדריך הדמה )מיקום כהיסט בבתים מתחילת השדה PathBuffer(. השם של מה שמוצב מקודד תמיד ב--Unicode, ולכן יש לוודא שהתוכנית היא תוכנית Unicode או לפחות שמתייחסים למחרוזת הזו באופן מתאים.

כאשר פותחים קובץ או מדריך מתוך כוונה להעביר את במזהה שלו לקריאת המערכת הזו על מנת לקרוא reparse point, יש לפתוח את הקובץ עם הדגלים FILE_FLAG_OPEN_REPARSE_POINT ו--FILE_FLAG_BACKUP_SEMANTICS זה גורם לפתיחה של קובץ או מדריך הדמה שה--reparse point מוצמד אליו, ולא לפתיחה של מה שמצביעים עליו.

ההגדרות של FSCTL_GET_REPARSE_POINT ושל MY_REPARSE_DATA_BUFFER נחוצות משום שסביבת הפיתוח של Win32 לא תמיד מגדירה אותן.

התרגיל.
  1. צרו נקודת הצבה עבור כונן התקליטורים וודאו שהיא פועלת.
  2. צרו מדריך f ובו שני קבצים, a ו--b. כעת צרו בהורה של המדריך מצביע c )hard link( ל--b ומחקו את b מ--f. האם תוכנו עדיין זמין מ--c?
  3. צרו מדריך g בהורה של f וצרו אליו מצביע סימבולי gj ב--f. האם אתם יכולים ליצור מעגל על ידי יצירת מצביע fj ל--f בתוך g? מה קורה כאשר מחפשים בקבצים במדריכים הללו )דרך תוכנת חיפוש הקבצים של מערכת ההפעלה(? מה קורה אם מוחקים את המדריך g?
  4. כעת כתבו תוכנית בשם ex-win32dir שמדווחת על תוכן מדריך, בדומה לפקודה dir. עבור כל קובץ במדריך, התוכנית צריכה להדפיס את זמן הכתיבה האחרונה לקובץ, תו שמציין האם הקובץ הוא מדריך או לא, את מספר המצביעים אל הקובץ, את גודלו, את שמו הקצר )אם יש(, את שמו הארוך, ואם הוא נקודת הצבה או מצביע סימבולי, את מה שהוא מצביע עליו. הפלט בהמשך מדגים את פעולת התוכנית הדרושה. יש להגיש, בנוסף לתוכנית ולתשובות על השאלות הקודמות, פלט של התוכנית שלכם על מדריך שיש בו קובץ עם יותר ממצביע אחד, מדריך עם נקודת הצבה, ומדריך עם מצביע סימבולי )מותר שזה יהיה אותו מדריך(.
C:\os\exercises\ex-win32dir\Debug> ex-win32dir .
22/04/2004 09:00 D (1)      0           .
22/04/2004 09:00 D (1)      0           ..
21/04/2004 12:09 - (1)     17 THISIS~1  This is a long name
21/04/2004 12:09 - (2)     17           b
21/04/2004 12:09 D (1)      0           gj -> ..\g
...

Copyright Sivan Toledo 2004
Previous Up Next